From 47510724d7cc37089ee443da60a1c1ed1064e38d Mon Sep 17 00:00:00 2001 From: Alan-CRL Date: Fri, 29 Aug 2025 16:21:03 +0800 Subject: [PATCH 1/5] 20250829 1621 update --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 9639d957..ee9acc65 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,25 @@ Windows 屏幕批注工具,拥有高效批注和丰富功能, ## 集锦(软件介绍) + +
+
+ +
+
+ **[Bilibili 上的 25H2 版本介绍视频](https://www.bilibili.com/video/BV17duZzYEsE/)** [Bilibili 上的 24H2 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1Tz421z72e/) [Bilibili 上的 24H1 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1vJ4m147rN/) From 605c5a51facb9f91a7ad696110f4e9fb7d06532d Mon Sep 17 00:00:00 2001 From: Alan-CRL Date: Fri, 29 Aug 2025 16:35:21 +0800 Subject: [PATCH 2/5] 20250829 1635 update --- README.md | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index ee9acc65..589454b5 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,9 @@ [![交流群](https://img.shields.io/badge/-%E4%BA%A4%E6%B5%81%E7%BE%A4%20618720802-blue?style=flat&logo=TencentQQ)](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) ![GitHub issues](https://img.shields.io/github/issues/Alan-CRL/IDT?logo=github&color=green) ![GitHub stars](https://img.shields.io/github/stars/Alan-CRL/IDT) -Windows 屏幕批注工具,拥有高效批注和丰富功能, -让屏幕演示变得简单,让教学授课变得高效,适用于触摸设备和PC端。 +将你的创意随心所欲地书写在屏幕的任意角落。 +智绘教Inkeys 拥有丝滑流畅的高性能画笔、丰富强大的功能,以及众多贴心小设计,全面提升你的效率与使用体验。 +软件基于 C++20 编写,专为 Windows 平台打造。 原名 `Intelligent-Drawing-Teaching`(简称 IDT) @@ -29,25 +30,21 @@ Windows 屏幕批注工具,拥有高效批注和丰富功能, ## 集锦(软件介绍) -
-
- -
-
+ + + + + +
+ 封面 + + + + Bilibili 上的 25H2 版本介绍视频 + + +
-**[Bilibili 上的 25H2 版本介绍视频](https://www.bilibili.com/video/BV17duZzYEsE/)** [Bilibili 上的 24H2 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1Tz421z72e/) [Bilibili 上的 24H1 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1vJ4m147rN/) [Codebus 上的介绍推文(较旧)](https://codebus.cn/alancrl/intelligent-painting-teaching) @@ -103,12 +100,15 @@ Windows 屏幕批注工具,拥有高效批注和丰富功能, - `dev`:分支仓库,每日及时更新,存储自动保存的源码,可能无法构建 ## 项目引用 -[Dear Imgui](https://github.com/ocornut/imgui) -[DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker) -[Hashlib++](https://github.com/aksalj/hashlibpp) -[HiEasyX](https://github.com/zouhuidong/HiEasyX) -[JsonCpp](https://github.com/open-source-parsers/jsoncpp) -[libcuckoo](https://github.com/efficient/libcuckoo) -[Stb](https://github.com/nothings/stb) -[WinToast](https://github.com/mohabouje/WinToast) -[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win) \ No newline at end of file +[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp) +[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker) +[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue) +[efficient/libcuckoo](https://github.com/efficient/libcuckoo) +[martinus/unordered_dense](https://github.com/martinus/unordered_dense) +[mohabouje/WinToast](https://github.com/mohabouje/WinToast) +[nothings/stb](https://github.com/nothings/stb) +[ocornut/imgui](https://github.com/ocornut/imgui) +[open-source-parsers/jsoncpp](https://github.com/open-source-parsers/jsoncpp) +[sammycage/lunasvg](https://github.com/sammycage/lunasvg) +[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win) +[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX) \ No newline at end of file From 7ef3e847bfe19f2a22d8ef134fd63e92350675f1 Mon Sep 17 00:00:00 2001 From: Alan-CRL Date: Thu, 4 Dec 2025 13:29:42 +0800 Subject: [PATCH 3/5] 20251204 1329 update --- .github/workflows/msbuild.yml | 34 +- GithubRes/CompilationProcess_en-US.md | 13 +- GithubRes/CompilationProcess_zh-CN.md | 11 +- NOTICE | 79 +- PptCOM/.vs/PptCOM.csproj.dtbcache.json | 2 +- PptCOM/PptCOM.cs | 77 +- README.md | 97 +- README_EN.md | 126 +- TOS/en-US.md | 417 ++ TOS/zh-CN.md | 416 ++ ThirdpartyLicenses/Apache License 2.0 | 202 + ThirdpartyLicenses/MIT License | 19 + ThirdpartyLicenses/concurrentqueue License | 19 + ThirdpartyLicenses/hashlibpp License | 25 + ThirdpartyLicenses/libcuckoo License | 13 + "\346\231\272\347\273\230\346\225\231.sln" | 15 + .../CrashHandler/CrashHandler.cpp" | 4 +- .../HiEasyX/HiWindow.cpp" | 31 +- .../HiEasyX/HiWindow.h" | 4 +- .../IdtConfiguration.cpp" | 34 +- .../IdtConfiguration.h" | 20 +- .../IdtD2DPreparation.cpp" | 14 +- .../IdtD2DPreparation.h" | 288 +- .../IdtDisplayManagement.cpp" | 35 +- .../IdtDrawpad.cpp" | 81 +- .../IdtDrawpad.h" | 1 - .../IdtFloating.cpp" | 250 +- .../IdtMain.cpp" | 161 +- .../IdtMain.h" | 20 +- .../IdtNet.cpp" | 26 +- .../IdtNet.h" | 4 +- .../IdtPlug-in.cpp" | 304 +- .../IdtRts.cpp" | 108 +- .../IdtRts.h" | 12 +- .../IdtSetting.cpp" | 753 ++- .../IdtStart.cpp" | 12 +- .../IdtStart.h" | 9 +- .../IdtText.cpp" | 24 +- .../IdtUpdate.cpp" | 167 +- .../IdtUpdate.h" | 36 +- .../IdtWindow.cpp" | 4 + .../Inkeys/Load/IdtFontLoad.cpp" | 46 + .../Inkeys/Load/IdtFontLoad.h" | 377 ++ .../Inkeys/Other/IdtGesture.cpp" | 44 + .../Inkeys/Other/IdtGesture.h" | 12 + .../Inkeys/Other/IdtInputs.cpp" | 26 + .../Inkeys/Other/IdtInputs.h" | 15 + .../PptCOM.dll" | Bin 14848 -> 14848 bytes .../PptCOM.tlb" | Bin 4856 -> 4876 bytes .../additional/imgui/imconfig.h" | 23 +- .../additional/imgui/imgui.cpp" | 4827 +++++++++++------ .../additional/imgui/imgui.h" | 1344 +++-- .../additional/imgui/imgui_draw.cpp" | 3750 +++++++++---- .../additional/imgui/imgui_impl_dx9.cpp" | 333 +- .../additional/imgui/imgui_impl_dx9.h" | 9 +- .../additional/imgui/imgui_impl_win32.cpp" | 221 +- .../additional/imgui/imgui_impl_win32.h" | 6 +- .../additional/imgui/imgui_internal.h" | 1000 +++- .../additional/imgui/imgui_tables.cpp" | 257 +- .../additional/imgui/imgui_toggle.cpp" | 1 + .../imgui/imgui_toggle_renderer.cpp" | 814 +-- .../additional/imgui/imgui_toggle_renderer.h" | 161 +- .../additional/imgui/imgui_widgets.cpp" | 1983 ++++--- .../additional/imgui/imstb_textedit.h" | 168 +- .../additional/imgui/imstb_truetype.h" | 4 +- .../exe/DesktopDrawpadBlocker.exe" | Bin 499200 -> 496128 bytes .../resource.h" | 3 +- .../src/IslandCaller.png" | Bin 917 -> 2636 bytes .../src/NamePicker.png" | Bin 0 -> 1160 bytes .../src/SecRandom.png" | Bin 0 -> 1251 bytes .../src/i18n/en-US.jsonc" | 35 +- .../src/i18n/zh-CN.jsonc" | 30 +- .../src/i18n/zh-TW.jsonc" | 38 +- .../src/setting/Home1_zh-TW.png" | Bin 147593 -> 148307 bytes .../\346\231\272\347\273\230\346\225\231.aps" | Bin 20848284 -> 20865376 bytes .../\346\231\272\347\273\230\346\225\231.rc" | 4 + ...6\231\272\347\273\230\346\225\231.vcxproj" | 16 +- ...2\347\273\230\346\225\231.vcxproj.filters" | 45 +- 78 files changed, 13493 insertions(+), 6066 deletions(-) create mode 100644 TOS/en-US.md create mode 100644 TOS/zh-CN.md create mode 100644 ThirdpartyLicenses/Apache License 2.0 create mode 100644 ThirdpartyLicenses/MIT License create mode 100644 ThirdpartyLicenses/concurrentqueue License create mode 100644 ThirdpartyLicenses/hashlibpp License create mode 100644 ThirdpartyLicenses/libcuckoo License create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.cpp" create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.h" create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.cpp" create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.h" create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.cpp" create mode 100644 "\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.h" create mode 100644 "\346\231\272\347\273\230\346\225\231/src/NamePicker.png" create mode 100644 "\346\231\272\347\273\230\346\225\231/src/SecRandom.png" diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index 1c9ae520..c27f125d 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -7,6 +7,8 @@ on: branches: - main - insider + - canary + - inkeys2-final permissions: actions: write @@ -57,7 +59,7 @@ jobs: matrix: platform: [Win32, x64, ARM64] fail-fast: false - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Checkout code @@ -162,7 +164,7 @@ jobs: package: name: Package - runs-on: windows-latest + runs-on: windows-2022 needs: build steps: @@ -280,15 +282,15 @@ jobs: run: | $branchName = $Env:GITHUB_REF_NAME - if ($branchName -ieq 'main' -or $branchName -ieq 'insider') + if ($branchName -ieq 'dev') { - "signing_policy_slug=release-signing" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - "**使用正式证书签名**" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append + "signing_policy_slug=test-signing" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append + "**使用测试证书签名**" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append } else { - "signing_policy_slug=test-signing" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append - "**使用测试证书签名**" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append + "signing_policy_slug=release-signing" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append + "**使用正式证书签名**" | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append } - name: 提交给 SignPath 签名 @@ -413,6 +415,7 @@ jobs: "${{ steps.softwareChannel.outputs.VALUE }}": { "edition_date": "${{ steps.softwareDate.outputs.VALUE }}", "edition_code": "", + "inkeys3": false, "hash": { "md5": "$md5_1", "sha256": "$sha256_1", @@ -422,16 +425,16 @@ jobs: "sha256 Arm64": "$sha256_3" }, "path": [ - "https://vip.123pan.cn/1709404/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip", - "http://home.alan-crl.top/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip" + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip" ], "path64": [ - "https://vip.123pan.cn/1709404/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip", - "http://home.alan-crl.top/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip" + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip" ], "pathArm64": [ - "https://vip.123pan.cn/1709404/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip", - "http://home.alan-crl.top/version_identification/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip" + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip" ], "size": { "file": $size1, @@ -607,7 +610,7 @@ jobs: - name: Upload to WebDAV with rclone id: upload_webdav - timeout-minutes: 60 + timeout-minutes: 120 continue-on-error: true shell: pwsh run: | @@ -621,7 +624,7 @@ jobs: # 开始上传 Write-Host "开始上传到 WebDAV..." Write-Host "进度将每 10 秒更新一次" - Write-Host "超时时间: 15 分钟" + Write-Host "超时时间: 120 分钟" Write-Host "目标路径: /Inkeys/PackageHistory/$Env:GITHUB_REF_NAME" Write-Host "" @@ -675,6 +678,7 @@ jobs: - name: Verify upload shell: pwsh + continue-on-error: true run: | if ("${{ secrets.WEBDAV_API_TOKEN }}" -ne "") { diff --git a/GithubRes/CompilationProcess_en-US.md b/GithubRes/CompilationProcess_en-US.md index 34e4b0b2..5e25a005 100644 --- a/GithubRes/CompilationProcess_en-US.md +++ b/GithubRes/CompilationProcess_en-US.md @@ -2,7 +2,7 @@ For general build requirements, you only need to build `智绘教.vcxproj`. There is an associated project, `PptCOM.csproj`, which serves as the PPT integration module for 智绘教Inkeys. The `智绘教.vcxproj` project depends on the library (dll/tlb) generated by `PptCOM.csproj`, but `PptCOM.csproj` has already been built, so you can directly build `智绘教.vcxproj`. This means that only a C++ desktop environment is needed; there's no need to set up a C# environment. -Note: If you only need to build `智绘教.vcxproj`, you should uncheck `PptCOM` under `Solution Configuration->Project dependencies->智绘教` +**Note: If you only need to build `智绘教.vcxproj`, you should uncheck `PptCOM` under `Solution Configuration->Project dependencies->智绘教`** ### Build the Main Project `智绘教.vcxproj` 智绘教Inkeys is completely open-source, with all source code and resources available. @@ -25,6 +25,17 @@ Note: If you only need to build `智绘教.vcxproj`, you should uncheck `PptCOM` --- +### FAQ + +In certain special environments, compilation may fail. + +- **error LNK2001: unresolved external symbol `std_search_1`** + This occurs in VS 2022 17.8.x (MSVC v143.35 – v143.41). + +We recommend using **17.14.x** to match the version installed by the project developers (optional). + +--- + ### Build the Subsidiary Project `PptCOM.csproj` #### Environment Preparation diff --git a/GithubRes/CompilationProcess_zh-CN.md b/GithubRes/CompilationProcess_zh-CN.md index a91a7ff3..816e4369 100644 --- a/GithubRes/CompilationProcess_zh-CN.md +++ b/GithubRes/CompilationProcess_zh-CN.md @@ -2,7 +2,7 @@ 对于一般的构建需求来说,你只需要构建 `智绘教.vcxproj` 即可,而该项目有一个附属项目 `PptCOM.csproj` 是 智绘教Inkeys 的 PPT 联动模块。 `智绘教.vcxproj` 依赖于 `PptCOM.csproj` 生成的类库(dll/tlb),但 `PptCOM.csproj` 已经被编译好了,可以直接构建 `智绘教.vcxproj`。这意味着只需要 C++ 桌面环境,而不用准备 C# 环境。 -注意:如果只需要编译 `智绘教.vcxproj` 你需要在 `解决方案配置->项目依赖项->智绘教` 中取勾 `PptCOM`! +**注意:如果只需要编译 `智绘教.vcxproj` 你需要在 `解决方案配置->项目依赖项->智绘教` 中取勾 `PptCOM`!** ### 编译主项目 `智绘教.vcxproj` 智绘教Inkeys 采用完全开源方式,所有源码和资源全部开源 @@ -25,6 +25,15 @@ --- +### FAQ +某些特殊环境可能会导致无法编译。 +- **error LNK2001: 无法解析的外部符号 `std_search_1`** +在 VS 2022 17.8.x(MSVC v143.35 ~ v143.41)中会导致无法编译。 + +推荐使用 **17.14.x**,与项目开发者所安装的版本保持一致。(非必须) + +--- + ### 编译附属项目 `PptCOM.csproj` #### 准备环境 diff --git a/NOTICE b/NOTICE index c8803be8..7b02f4ff 100644 --- a/NOTICE +++ b/NOTICE @@ -1,10 +1,77 @@ -This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (https://www.openssl.org/) -and licensed under the Apache License 2.0. +The development of this software makes use of a number of excellent third-party open source libraries. We would like to express our sincere gratitude to the contributors of these open source projects. Below is a non-exhaustive list of third-party components included in this software and their license information: -Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved. +Component: abseil/abseil-cpp (https://github.com/abseil/abseil-cpp) +License: Apache License 2.0 +Copyright / Notes: ------ +Component: aksalj/hashlibpp (https://github.com/aksalj/hashlibpp) +License: See accompanying file(s) +Copyright / Notes: Copyright (c) 2007-2011 Benjamin Gr¸delbach -This product includes software developed by Carnegie Mellon University and Intel Corporation as part of the libcuckoo project (https://github.com/efficient/libcuckoo), and licensed under the Apache License, Version 2.0. +Component: Alan-CRL/DesktopDrawpadBlocker (https://github.com/Alan-CRL/DesktopDrawpadBlocker) +License: GNU General Public License v3.0 +Copyright / Notes: -Copyright 2013, Carnegie Mellon University and Intel Corporation. \ No newline at end of file +Component: cameron314/concurrentqueue (https://github.com/cameron314/concurrentqueue) +License: See accompanying file(s) +Copyright / Notes: Copyright (c) 2013-2016, Cameron Desrochers. All rights reserved. + +Component: efficient/libcuckoo (https://github.com/efficient/libcuckoo) +License: See accompanying file(s) +Copyright / Notes: Copyright (C) 2013, Carnegie Mellon University and Intel Corporation + +Component: gabime/spdlog (https://github.com/gabime/spdlog) +License: MIT License +Copyright / Notes: Copyright (c) 2016 Gabi Melman. + +Component: google/ink-stroke-modeler (https://github.com/google/ink-stroke-modeler) +License: Apache License 2.0 +Copyright / Notes: + +Component: martinus/unordered_dense (https://github.com/martinus/unordered_dense) +License: MIT License +Copyright / Notes: Copyright (c) 2022 Martin Leitner-Ankerl + +Component: mohabouje/WinToast (https://github.com/mohabouje/WinToast) +License: MIT License +Copyright / Notes: Copyright (C) 2016-2023 WinToast v1.3.0 - Mohammed Boujemaoui + +Component: nothings/stb (https://github.com/nothings/stb) +License: MIT License +Copyright / Notes: Copyright (c) 2017 Sean Barrett + +Component: ocornut/imgui (https://github.com/ocornut/imgui) +License: MIT License +Copyright / Notes: Copyright (c) 2014-2025 Omar Cornut + +Component: openssl/openssl (https://github.com/openssl/openssl) +License: Apache License 2.0 +Copyright / Notes: Copyright (c) 1998-2025 The OpenSSL Project Authors. Copyright (c) 1995-1998 Eric A. Young, Tim J. Hudson. All rights reserved. + +Component: sammycage/lunasvg (https://github.com/sammycage/lunasvg) +License: MIT License +Copyright / Notes: Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Component: sammycage/plutovg (https://github.com/sammycage/plutovg) +License: MIT License +Copyright / Notes: Copyright (c) 2020-2025 Samuel Ugochukwu + +Component: yhirose/cpp-httplib (https://github.com/yhirose/cpp-httplib) +License: MIT License +Copyright / Notes: Copyright (c) 2017 yhirose + +Component: Zip Utils (https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win) +License: +Copyright / Notes: + +Component: zouhuidong/HiEasyX (https://github.com/zouhuidong/HiEasyX) +License: MIT License +Copyright / Notes: Copyright (c) 2022 zouhuidong + +For the complete list of all third-party libraries used by this software and their corresponding license files, please refer to the following links: + +Main component notices (NOTICE): +./NOTICE + +Third-party license files: +./ThirdpartyLicenses/ \ No newline at end of file diff --git a/PptCOM/.vs/PptCOM.csproj.dtbcache.json b/PptCOM/.vs/PptCOM.csproj.dtbcache.json index 06283c9e..09dcc4bd 100644 --- a/PptCOM/.vs/PptCOM.csproj.dtbcache.json +++ b/PptCOM/.vs/PptCOM.csproj.dtbcache.json @@ -1 +1 @@ -{"RootPath":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\PptCOM","ProjectFileName":"PptCOM.csproj","Configuration":"Release|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"PptCOM.cs"},{"SourceFile":"obj\\Release\\.NETFramework,Version=v4.0.AssemblyAttributes.cs"}],"References":[{"Reference":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\packages\\Microsoft.Office.Interop.PowerPoint.15.0.4420.1018\\lib\\net20\\Microsoft.Office.Interop.PowerPoint.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\packages\\MicrosoftOfficeCore.15.0.0\\lib\\net35\\Office.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Windows.Forms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\PptCOM\\bin\\Release\\PptCOM.dll","OutputItemRelativePath":"PptCOM.dll"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file +{"RootPath":"D:\\Downloads\\Inkeys-inkeys2-final\\PptCOM","ProjectFileName":"PptCOM.csproj","Configuration":"Release|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"PptCOM.cs"},{"SourceFile":"obj\\Release\\.NETFramework,Version=v4.0.AssemblyAttributes.cs"}],"References":[{"Reference":"D:\\Downloads\\Inkeys-inkeys2-final\\packages\\Microsoft.Office.Interop.PowerPoint.15.0.4420.1018\\lib\\net20\\Microsoft.Office.Interop.PowerPoint.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"D:\\Downloads\\Inkeys-inkeys2-final\\packages\\MicrosoftOfficeCore.15.0.0\\lib\\net35\\Office.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Windows.Forms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"D:\\Downloads\\Inkeys-inkeys2-final\\PptCOM\\bin\\Release\\PptCOM.dll","OutputItemRelativePath":"PptCOM.dll"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file diff --git a/PptCOM/PptCOM.cs b/PptCOM/PptCOM.cs index a70da6a9..f21fe97d 100644 --- a/PptCOM/PptCOM.cs +++ b/PptCOM/PptCOM.cs @@ -41,7 +41,7 @@ namespace PptCOM [Guid("65F6E9C1-63EC-4003-B89F-8F425A3C2FEA")] public interface IPptCOMServer { - unsafe bool Initialization(int* TotalPage, int* CurrentPage, bool autoCloseWPSTarget); + unsafe bool Initialization(int* TotalPage, int* CurrentPage/*, bool autoCloseWPSTarget*/); string CheckCOM(); unsafe int IsPptOpen(); @@ -56,6 +56,7 @@ public interface IPptCOMServer IntPtr GetPptHwnd(); void EndSlideShow(); + void ViewSlideShow(); } [ComVisible(true)] @@ -75,18 +76,18 @@ public class PptCOMServer : IPptCOMServer private DateTime updateTime; // 更新时间点 private bool bindingEvents; - private bool autoCloseWPS = false; - private bool hasWpsProcessID; - private Process wpsProcess; + //private bool autoCloseWPS = false; + //private bool hasWpsProcessID; + //private Process wpsProcess; // 初始化函数 - public unsafe bool Initialization(int* TotalPage, int* CurrentPage, bool autoCloseWPSTarget) + public unsafe bool Initialization(int* TotalPage, int* CurrentPage/*, bool autoCloseWPSTarget*/) { try { pptTotalPage = TotalPage; pptCurrentPage = CurrentPage; - autoCloseWPS = autoCloseWPSTarget; + //autoCloseWPS = autoCloseWPSTarget; return true; } @@ -98,7 +99,7 @@ public unsafe bool Initialization(int* TotalPage, int* CurrentPage, bool autoClo } public string CheckCOM() { - string ret = "20250714a"; + string ret = "20250830a"; try { @@ -155,8 +156,12 @@ private unsafe void SlideShowBegin(Microsoft.Office.Interop.PowerPoint.SlideShow } // 获取页数 - *pptTotalPage = pptActDoc.Slides.Count; - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + try + { + *pptTotalPage = pptActDoc.Slides.Count; + *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + } + catch { } } private unsafe void SlideShowShowEnd(Microsoft.Office.Interop.PowerPoint.Presentation Wn) @@ -178,13 +183,13 @@ private void PresentationBeforeClose(Microsoft.Office.Interop.PowerPoint.Present bindingEvents = false; } // 对于延迟未关闭的 WPP,先记录进程 ID,待所有结束事件处理完毕后强制关闭 - if (autoCloseWPS && Wn.Application.Path.Contains("Kingsoft\\WPS Office\\") && Wn.Application.Presentations.Count <= 1) - { - uint processId; - GetWindowThreadProcessId((IntPtr)Wn.Application.HWND, out processId); - wpsProcess = Process.GetProcessById((int)processId); - hasWpsProcessID = true; - } + //if (autoCloseWPS && Wn.Application.Path.Contains("Kingsoft\\WPS Office\\") && Wn.Application.Presentations.Count <= 1) + //{ + // uint processId; + // GetWindowThreadProcessId((IntPtr)Wn.Application.HWND, out processId); + // wpsProcess = Process.GetProcessById((int)processId); + // hasWpsProcessID = true; + //} cancel = false; } @@ -193,7 +198,7 @@ public unsafe int IsPptOpen() { int ret = 0; bindingEvents = false; - hasWpsProcessID = false; + //hasWpsProcessID = false; // 通用尝试,获取 Active 的 Application 并检测是否正确 try @@ -324,11 +329,30 @@ public unsafe int IsPptOpen() bindingEvents = false; } // 关闭未正确关闭的 WPP 进程 - if (hasWpsProcessID == true && !wpsProcess.HasExited) + //if (hasWpsProcessID == true && !wpsProcess.HasExited) + //{ + // wpsProcess.Kill(); + // hasWpsProcessID = false; + //} + + // 释放 COM + if (pptActWindow != null) { - wpsProcess.Kill(); - hasWpsProcessID = false; + Marshal.ReleaseComObject(pptActWindow); + pptActWindow = null; } + if (pptActDoc != null) + { + Marshal.ReleaseComObject(pptActDoc); + pptActDoc = null; + } + if (pptApp != null) + { + Marshal.ReleaseComObject(pptApp); + pptApp = null; + } + GC.Collect(); + GC.WaitForPendingFinalizers(); } catch { } } @@ -417,7 +441,7 @@ public unsafe void NextSlideShow(int check) { try { - int temp_SlideIndex = pptActWindow.View.Slide.SlideIndex; + int temp_SlideIndex = *pptCurrentPage; if (temp_SlideIndex != check && check != -1) return; // 下一页 @@ -478,5 +502,16 @@ public void EndSlideShow() { } } + public void ViewSlideShow() + { + try + { // 打开 ppt 浏览视图 + pptActWindow.SlideNavigation.Visible = true; + } + catch + { + } + return; + } } } \ No newline at end of file diff --git a/README.md b/README.md index 589454b5..e8377891 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@
-**Copyright © 2023-2025 AlanCRL(陈润林)工作室** -新版 Readme 尚未完善 访问[旧版 Readme 页面](https://github.com/Alan-CRL/IDT/blob/1d63b4ba18e01f7ac45abb0e470d2748380b4407/README.md) +**Copyright © 2023-2025 AlanCRL(陈润林)工作室** 默认仓库位于 [Github](https://github.com/Alan-CRL/Inkeys)(https://github.com/Alan-CRL/Inkeys) 备用仓库位于 [GitCode](https://gitcode.com/alan16356/Inkeys)(https://gitcode.com/alan16356/Inkeys) @@ -13,9 +12,9 @@ # 智绘教Inkeys **简体中文** | [English](README_EN.md) -[官网下载](https://www.inkeys.top/col.jsp?id=106) | **[官方网站](https://www.inkeys.top)** | 常见问题 +[下载](https://www.inkeys.top/download) | **[官方网站](https://www.inkeys.top)** | 常见问题 -[![交流群](https://img.shields.io/badge/-%E4%BA%A4%E6%B5%81%E7%BE%A4%20618720802-blue?style=flat&logo=TencentQQ)](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) ![GitHub issues](https://img.shields.io/github/issues/Alan-CRL/IDT?logo=github&color=green) ![GitHub stars](https://img.shields.io/github/stars/Alan-CRL/IDT) +[![交流群](https://img.shields.io/badge/-%E4%BA%A4%E6%B5%81%E7%BE%A4%20618720802-blue?style=flat&logo=TencentQQ)](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) ![GitHub issues](https://img.shields.io/github/issues/Alan-CRL/IDT?logo=github&color=green) ![GitHub stars](https://img.shields.io/github/stars/Alan-CRL/IDT) Featured|HelloGitHub 将你的创意随心所欲地书写在屏幕的任意角落。 智绘教Inkeys 拥有丝滑流畅的高性能画笔、丰富强大的功能,以及众多贴心小设计,全面提升你的效率与使用体验。 @@ -43,31 +42,22 @@ - - -[Bilibili 上的 24H2 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1Tz421z72e/) -[Bilibili 上的 24H1 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1vJ4m147rN/) -[Codebus 上的介绍推文(较旧)](https://codebus.cn/alancrl/intelligent-painting-teaching) - -## 好消息 !!! -智绘教 UI3 已经开始制作,这将是一个史诗级更新。 -针对 大屏触摸环境 和 PC鼠标环境 等,推出多种 UI 样式。 -让智绘教在各个设备上,都更易用,更好用! + ## 下载 -[官网下载页](https://www.inkeys.top/col.jsp?id=106) | [免登录云盘](https://www.123pan.com/s/duk9-n4dAd.html) | [Github Release 附件](https://github.com/Alan-CRL/IDT/releases) +[官网下载页](https://www.inkeys.top/download) | [免登录云盘](https://www.123pan.com/s/duk9-n4dAd.html) | [Github Release 附件](https://github.com/Alan-CRL/Inkeys/releases) #### 要求 -最低支持 Windows 7 (sp0),支持 32位 / 64位 / Arm64 系统。 +最低支持 Windows 7 (RTM, sp0),支持 32位 / 64位 / Arm64 系统。 -#### 提示 -获取 公测版本 请加 [官方用户QQ群](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) 下载。 +## 使用条款 +使用 智绘教Inkeys 需同意我们的 [智绘教Inkeys 使用条款](https://www.inkeys.top/tos/zh-cn) | [备用链接](./TOS/zh-CN.md) ## 反馈 问题报告与功能建议:[点击此处](https://www.wjx.cn/vm/mqNTTRL.aspx#) [官方用户QQ群](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6):`618720802` 作者QQ:`2685549821` -作者邮箱:`alan-crl@foxmail.com` +作者邮箱:`alan-crl@foxmail.com` `alancrl1007@gmail.com` ## 许可证 本项目基于 [GNU General Public License v3.0](LICENSE) 获得许可。 @@ -92,7 +82,7 @@ --- ## 编译说明 -[编译流程](GithubRes/CompilationProcess_zh-CN.md) +[编译步骤](GithubRes/CompilationProcess_zh-CN.md) ### 分支说明 - `main`:主仓库,存储上一发布版本稳定的可构建的程序源码 @@ -100,15 +90,58 @@ - `dev`:分支仓库,每日及时更新,存储自动保存的源码,可能无法构建 ## 项目引用 -[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp) -[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker) -[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue) -[efficient/libcuckoo](https://github.com/efficient/libcuckoo) -[martinus/unordered_dense](https://github.com/martinus/unordered_dense) -[mohabouje/WinToast](https://github.com/mohabouje/WinToast) -[nothings/stb](https://github.com/nothings/stb) -[ocornut/imgui](https://github.com/ocornut/imgui) -[open-source-parsers/jsoncpp](https://github.com/open-source-parsers/jsoncpp) -[sammycage/lunasvg](https://github.com/sammycage/lunasvg) -[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win) -[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX) \ No newline at end of file + +### 第三方开源组件 + +我们在此对这些开源项目及其贡献者表示感谢。以下为部分主要第三方组件及其许可信息(仅为摘要,具体约束以各组件随附许可证文本为准): + +| 组件名称 | 许可证 | 版权信息 / 说明 | +| :--- | :--- | :--- | +| **[abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)** | Apache License 2.0 | | +| **[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp)** | 参见库内 LICENSE / 说明文件 | Copyright (c) 2007–2011 Benjamin Gr¸delbach | +| **[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)** | GNU General Public License v3.0 | | +| **[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue)** | 参见库内 LICENSE / 说明文件 | Copyright (c) 2013–2016, Cameron Desrochers. All rights reserved. | +| **[efficient/libcuckoo](https://github.com/efficient/libcuckoo)** | 参见库内 LICENSE / 说明文件 | Copyright (C) 2013, Carnegie Mellon University and Intel Corporation | +| **[gabime/spdlog](https://github.com/gabime/spdlog)** | MIT License | Copyright (c) 2016 Gabi Melman. | +| **[google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler)** | Apache License 2.0 | | +| **[martinus/unordered_dense](https://github.com/martinus/unordered_dense)** | MIT License | Copyright (c) 2022 Martin Leitner-Ankerl | +| **[mohabouje/WinToast](https://github.com/mohabouje/WinToast)** | MIT License | Copyright (C) 2016–2023 WinToast v1.3.0 - Mohammed Boujemaoui | +| **[nothings/stb](https://github.com/nothings/stb)** | MIT License | Copyright (c) 2017 Sean Barrett | +| **[ocornut/imgui](https://github.com/ocornut/imgui)** | MIT License | Copyright (c) 2014–2025 Omar Cornut | +| **[openssl/openssl](https://github.com/openssl/openssl)** | Apache License 2.0 | Copyright (c) 1998–2025 The OpenSSL Project Authors. Copyright (c) 1995–1998 Eric A. Young, Tim J. Hudson. All rights reserved. | +| **[open-source-parsers/jsoncpp](https://github.com/open-source-parsers/jsoncpp)** | MIT License | Copyright (c) 2007–2010 Baptiste Lepilleur and The JsonCpp Authors | +| **[sammycage/lunasvg](https://github.com/sammycage/lunasvg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[sammycage/plutovg](https://github.com/sammycage/plutovg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[yhirose/cpp-httplib](https://github.com/yhirose/cpp-httplib)** | MIT License | Copyright (c) 2017 yhirose | +| **[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win)** | 参见项目页面及随附许可证 | | +| **[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX)** | MIT License | Copyright (c) 2022 zouhuidong | + +关于本软件使用的所有第三方库的**完整列表**及其对应的许可证文本,请查阅: + +- **主要组件声明(NOTICE)**: + +- **第三方许可证文件汇总**: + + +> 当本使用条款与上述第三方组件的许可证存在冲突或不一致时, +> 就相关第三方组件的使用、复制、修改和分发事宜,以相应第三方许可证的具体条款为准。 + +### 第三方组件 + +除上述开源组件外,本软件在开发和运行过程中还可能使用部分以其他许可方式授权的第三方库或工具。我们同样对这些项目的作者和维护者表示感谢。 + +以下为目前使用的部分非(或不完全)开源组件及其授权信息摘要(如有): + +| 组件名称 | 许可授权 / 使用说明 | +| :--- | :--- | +| **[EasyX](https://easyx.cn/)** | 根据 EasyX 官方网站公布的授权条款使用。具体权利义务以 EasyX 官方的最新授权条款为准。 | + +> 我们对第三方组件的使用不构成对其项目的任何所有权声明。 +> 您在独立使用上述第三方软件、库或服务时,仍应仔细阅读其各自的使用条款和许可证,并自行遵守。 + +## 其他内容 + +访问[旧版 Readme 页面](https://github.com/Alan-CRL/IDT/blob/1d63b4ba18e01f7ac45abb0e470d2748380b4407/README.md) + +[Bilibili 上的 24H2 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1Tz421z72e/) +[Bilibili 上的 24H1 版本介绍视频(较旧)](https://www.bilibili.com/video/BV1vJ4m147rN/) diff --git a/README_EN.md b/README_EN.md index f9093442..2af07e82 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,6 +1,6 @@
- -**Copyright(©) 2023-2025 AlanCRL(Chen YunLam) Studio** + +**Copyright © 2023-2025 AlanCRL(Chen YunLam) Studio** The default repository is located at [Github](https://github.com/Alan-CRL/Inkeys)(https://github.com/Alan-CRL/Inkeys) The backup repository is located at [GitCode](https://gitcode.com/alan16356/Inkeys)(https://gitcode.com/alan16356/Inkeys) @@ -9,43 +9,51 @@ The backup repository is located at [GitCode](https://gitcode.com/alan16356/Inke [![LOGO](GithubRes/logo.png?raw=true "LOGO")](# "LOGO") -# 智绘教Inkeys +# Inkeys [简体中文](README.md) | **English** -[Download from Github Release Assets](https://github.com/Alan-CRL/IDT/releases) | **[Official Website](https://en.inkeys.top)** | FAQ +[Download](https://en.inkeys.top/download) | **[Official Website](https://en.inkeys.top)** | FAQ -[![Communication Group](https://img.shields.io/badge/-QQ%20Group%20618720802-blue?style=flat&logo=TencentQQ)](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) ![GitHub issues](https://img.shields.io/github/issues/Alan-CRL/IDT?logo=github&color=green) ![GitHub stars](https://img.shields.io/github/stars/Alan-CRL/IDT) +[![Communication Group](https://img.shields.io/badge/-QQ%20Group%20618720802-blue?style=flat&logo=TencentQQ)](https://qm.qq.com/cgi-bin/qm/qr?k=9V2l83dc0yP4UYeDF-NkTX0o7_TcYqlh&jump_from=webapi&authKey=LsLLUhb1KSzHYbc8k5nCQDqTtRcRUCEE3j+DdR9IgHaF/7JF7LLpY191hsiYEBz6) ![GitHub issues](https://img.shields.io/github/issues/Alan-CRL/IDT?logo=github&color=green) ![GitHub stars](https://img.shields.io/github/stars/Alan-CRL/IDT) Featured|HelloGitHub -Windows screen annotation tool with efficient annotation and rich features, -Make screen demonstrations simpler, teaching more efficient, -suitable for touch devices and computers. +Unleash your creativity by writing anywhere on your screen with ease. +Inkeys brings you ultra-smooth, high-performance brushes, a powerful array of features, and numerous thoughtful design touches to elevate your efficiency and user experience. +Built with C++20, Inkeys is designed exclusively for Windows. -Old name `Intelligent-Drawing-Teaching`(referred to as `IDT`) +Old name `Intelligent-Drawing-Teaching` (referred to as `IDT`) -![](GithubRes/cover1.png?raw=true#gh-dark-mode-only) -![](GithubRes/cover2.png?raw=true#gh-light-mode-only) +![](GithubRes/cover1.png?raw=true#gh-dark-mode-only)![](GithubRes/cover2.png?raw=true#gh-light-mode-only)
## Collection (Introduction) -**[Introduction video of 25H2 version on _bilibili.com_ (Chinese)](https://www.bilibili.com/video/BV17duZzYEsE/)** -[Introduction video of 24H2 version on _bilibili.com_ (Chinese)(older)](https://www.bilibili.com/video/BV1Tz421z72e/) -[Introduction video of 24H1 version on _bilibili.com_ (Chinese)(older)](https://www.bilibili.com/video/BV1vJ4m147rN/) -[Introduction blog on _codebus.cn_ (Chinese)(VPN required)(older)](https://codebus.cn/alancrl/intelligent-painting-teaching) -## News !!! -We will create new UI interface for Inkeys, which will be an epic change. -We have launched various UI style for large screen touch devices and laptops. -Make Inkeys easier and more user-friendly on various devices! + + + + + +
+ Cover + + + + Introduction video of 25H2 version on YouTube (Chinese) + + +
## Download -[Github Release Assets](https://github.com/Alan-CRL/IDT/releases) | [Official Website Download(Chinese)](https://www.inkeys.top/col.jsp?id=106) +[Github Release Assets](https://github.com/Alan-CRL/Inkeys/releases) | [Official Website Download(Chinese)](https://www.inkeys.top/download) -#### System Requirements -The minimum support is `Windows 7 (sp0)` and supports `x86 / x64 / arm64` windows. +#### Requirements +The minimum support is `Windows 7 (RTM, sp0)` and supports `x86 / x64 / arm64` windows. + +## Terms of Service +Using Inkeys requires agreeing to our [Inkeys Terms of Service](https://www.inkeys.top/tos/zh-cn) | [Backup Link](./TOS/zh-CN.md) ## Feedback -Author email: `alan-crl@foxmail.com` +Author email: `alan-crl@foxmail.com` `alancrl1007@gmail.com` Author QQ: `2685549821` ## License @@ -71,21 +79,65 @@ This project is licensed under the [GNU General Public License v3.0](LICENSE). --- ## Compile Instructions - -### Compile Preparation -[CompilationProcess](GithubRes/CompilationProcess_en-US.md) +[Compilation Process](GithubRes/CompilationProcess_en-US.md) ### Branch Description -- `main`: Store recently stable and buildable program source code. -- `dev`: Daily timely updates, storing automatically saved source code, may not be able to build. +- `main`: Main repository, stores stable and buildable program source code from the previous release. +- `insider`: Branch repository, stores source code from the previous preview version, may be unstable with many bugs. +- `dev`: Branch repository, daily timely updates, storing automatically saved source code, may not be able to build. ## Project Reference -[Dear Imgui](https://github.com/ocornut/imgui) -[DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker) -[Hashlib++](https://github.com/aksalj/hashlibpp) -[HiEasyX](https://github.com/zouhuidong/HiEasyX) -[JsonCpp](https://github.com/open-source-parsers/jsoncpp) -[libcuckoo](https://github.com/efficient/libcuckoo) -[Stb](https://github.com/nothings/stb) -[WinToast](https://github.com/mohabouje/WinToast) -[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win) \ No newline at end of file + +### Third-party Open Source Components + +We would like to express our gratitude to these open source projects and their contributors. The following is a summary of some major third-party components and their licensing information (for summary purposes only; specific constraints are subject to the license text accompanying each component): + +| Component Name | License | Copyright Info / Note | +| :--- | :--- | :--- | +| **[abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)** | Apache License 2.0 | | +| **[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp)** | See LICENSE / Readme within the library | Copyright (c) 2007–2011 Benjamin Gr¸delbach | +| **[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)** | GNU General Public License v3.0 | | +| **[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue)** | See LICENSE / Readme within the library | Copyright (c) 2013–2016, Cameron Desrochers. All rights reserved. | +| **[efficient/libcuckoo](https://github.com/efficient/libcuckoo)** | See LICENSE / Readme within the library | Copyright (C) 2013, Carnegie Mellon University and Intel Corporation | +| **[gabime/spdlog](https://github.com/gabime/spdlog)** | MIT License | Copyright (c) 2016 Gabi Melman. | +| **[google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler)** | Apache License 2.0 | | +| **[martinus/unordered_dense](https://github.com/martinus/unordered_dense)** | MIT License | Copyright (c) 2022 Martin Leitner-Ankerl | +| **[mohabouje/WinToast](https://github.com/mohabouje/WinToast)** | MIT License | Copyright (C) 2016–2023 WinToast v1.3.0 - Mohammed Boujemaoui | +| **[nothings/stb](https://github.com/nothings/stb)** | MIT License | Copyright (c) 2017 Sean Barrett | +| **[ocornut/imgui](https://github.com/ocornut/imgui)** | MIT License | Copyright (c) 2014–2025 Omar Cornut | +| **[openssl/openssl](https://github.com/openssl/openssl)** | Apache License 2.0 | Copyright (c) 1998–2025 The OpenSSL Project Authors. Copyright (c) 1995–1998 Eric A. Young, Tim J. Hudson. All rights reserved. | +| **[open-source-parsers/jsoncpp](https://github.com/open-source-parsers/jsoncpp)** | MIT License | Copyright (c) 2007–2010 Baptiste Lepilleur and The JsonCpp Authors | +| **[sammycage/lunasvg](https://github.com/sammycage/lunasvg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[sammycage/plutovg](https://github.com/sammycage/plutovg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[yhirose/cpp-httplib](https://github.com/yhirose/cpp-httplib)** | MIT License | Copyright (c) 2017 yhirose | +| **[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win)** | See project page and accompanying license | | +| **[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX)** | MIT License | Copyright (c) 2022 zouhuidong | + +For a **complete list** of all third-party libraries used in this software and their corresponding license texts, please consult: + +- **Major Component Declarations (NOTICE)**: + +- **Summary of Third-party License Files**: + + +> In the event of any conflict or inconsistency between these Terms of Service and the licenses of the third-party components mentioned above, the specific terms of the respective third-party license regarding the use, reproduction, modification, and distribution of the relevant third-party component shall prevail. + +### Third-party Components + +In addition to the open source components mentioned above, this software may also use some third-party libraries or tools authorized under other licenses during development and operation. We also express our gratitude to the authors and maintainers of these projects. + +The following is a summary of some non-(or not fully) open source components currently used and their licensing information (if any): + +| Component Name | Licensing / Usage Instructions | +| :--- | :--- | +| **[EasyX](https://easyx.cn/)** | Used according to the license terms published on the official EasyX website. Specific rights and obligations are subject to the latest license terms from EasyX. | + +> Our use of third-party components does not constitute any claim of ownership over their projects. +> When you independently use the above third-party software, libraries, or services, you should still carefully read their respective terms of use and licenses and comply with them on your own. + +## Other Content + +Visit [Old Readme Page](https://github.com/Alan-CRL/IDT/blob/1d63b4ba18e01f7ac45abb0e470d2748380b4407/README.md) + +[Introduction video of 24H2 version on Bilibili (Older)](https://www.bilibili.com/video/BV1Tz421z72e/) +[Introduction video of 24H1 version on Bilibili (Older)](https://www.bilibili.com/video/BV1vJ4m147rN/) \ No newline at end of file diff --git a/TOS/en-US.md b/TOS/en-US.md new file mode 100644 index 00000000..4839cb8c --- /dev/null +++ b/TOS/en-US.md @@ -0,0 +1,417 @@ +# Inkeys Terms of Use + +[简体中文](./zh-cn.md) | **English** + +--- + +**Version:** 1.0 +**Release Date:** November 28, 2025 +**Effective Date:** November 28, 2025 + +**These *Inkeys Terms of Use* (hereinafter referred to as "these Terms") constitute a legally binding agreement between you ("User" or "You") and the Inkeys Development Team (hereinafter referred to as "We" or "Us") regarding your downloading, installation, copying, or other use of the "Inkeys" software (including its updates, upgrades, related documentation, and auxiliary components, collectively referred to as "the Software").** + +**Before you download, install, or use the Software, please be sure to read these Terms carefully and fully understand them, especially the clauses marked in bold, which may concern your rights, obligations, or limitations of liability.** + +**If you do not agree to any part of these Terms, please do not download, install, or use the Software.** + +**By downloading, installing, copying, running, or otherwise using the Software, you acknowledge that you have read and agree to be bound by these Terms and any updated versions thereof.** + +Specifically, when you launch the Software for the first time, the Software will guide you to read this Agreement. By checking the checkbox representing "Agree" and proceeding to the next step, you confirm that you fully understand and accept all terms of this Agreement. + +If you are using the Software on behalf of a company, organization, or other entity, you represent and warrant that you have full authority to bind that entity to these Terms, and in such case, "You" refers to that company, organization, or other entity. + +--- + +### I. Service Description + +1. The Software is developed and provided by Us, aiming to provide users with screen annotation, marking, drawing, and related auxiliary tools. Specific functions and interfaces may change with version updates. + +2. The Software is primarily intended for general users worldwide, including educators, students, creators, and individuals or organizations requiring screen annotation functionality. The Software is **not specifically designed or certified for any specific industry, region, or regulatory field**. If you use the Software in high-risk scenarios, you shall evaluate its suitability independently and assume all associated risks. + +3. We may add, remove, or modify the functions of the Software, or suspend, pause, or terminate the maintenance and updates of the Software within a reasonable scope, based on product planning, technical conditions, or legal and regulatory requirements. We will endeavor to notify you of significant changes via the official website, project homepage, or in-software notifications (to the extent technically and legally permissible). + +4. The Software is **provided free of charge**. + +### II. User Eligibility, Rights, and Obligations + +1. **User Eligibility** + + 1.1 You confirm that when using the Software, you possess full civil capacity and are capable of independently assuming legal liability. + + 1.2 If you are a minor under the age of 18, or a minor as defined by the laws of your jurisdiction, you should use the Software only after your guardian has read and agreed to these Terms and the relevant Privacy Policy. Use of the Software by a minor shall be deemed to be with the consent and guidance of their guardian. + + 1.3 If you use the Software on behalf of a company, organization, or other entity, you represent and warrant that you have the authority to bind such entity to these Terms. + +2. **User Rights** + + Subject to your compliance with these Terms and applicable laws and regulations, you enjoy the following rights: + + 2.1 You have the right to download, install, and use the Software within the scope permitted by these Terms and applicable open-source licenses. + + 2.2 You have the right to use the functions of the Software in personal, educational, research, and commercial scenarios, provided that you comply with applicable laws, regulations, and these Terms. For parts subject to open-source licenses such as GPLv3, you must also comply with the requirements of the respective open-source licenses. + + 2.3 To the extent permitted by applicable law, you have the right to exercise rights regarding your personal information, such as access, correction, deletion, restriction of processing, and withdrawal of consent. Please refer to Part III, "Privacy Policy," of these Terms for details. + + 2.4 If you do not agree to these Terms (including subsequent revisions), you have the right to stop using and uninstall the Software. Uninstalling the Software does not automatically extinguish any rights, obligations, or liabilities incurred by you during your prior use of the Software. + +3. **User Obligations** + + When using the Software, you undertake and agree to: + + 3.1 Comply with applicable laws, regulations, these Terms, and other rules related to the Software. You shall not use the Software to engage in any illegal, non-compliant, or infringing acts, including but not limited to: + + - Infringing upon the intellectual property rights, privacy rights, reputation rights, or other legitimate rights and interests of others; + - Transmitting, storing, or publishing illegal, harmful, or prohibited content; + - Engaging in fraud, cyberattacks, malicious intrusion, disruption of computer information systems, etc. + + 3.2 Not use the Software in any manner that could damage, disable, overburden, or impair the normal operation of the Software, or interfere with any other party's use of the Software. + + 3.3 Not intentionally transmit viruses, trojans, or other malicious programs through the Software. + + 3.4 Not forge, tamper with, or delete copyright notices, trademarks, or other proprietary notices in the Software. + + 3.5 Carefully check any error reports, logs, memory dump files, or other technical information before proactively submitting them to Us to ensure they do not contain sensitive content you do not wish to disclose (e.g., trade secrets, personal privacy). + + 3.6 Properly safeguard your device and system environment configuration and not intentionally provide a device installed with the Software to others for use in illegal activities. + +4. **Breach of Terms** + + 4.1 If We have reasonable grounds to believe that you have violated these Terms or applicable laws and regulations, We have the right to take reasonable measures to the extent permitted by law, including but not limited to: + + - Requiring you to immediately cease the relevant violation; + - Restricting or terminating the provision of relevant services to you (e.g., online updates, technical support) and prohibiting you from using official releases bearing Our trademarks and digital signatures to the extent permitted by law; + - Reporting to relevant competent authorities and cooperating with investigations where necessary. + + 4.2 Provided there is no conflict with applicable open-source licenses (such as GPLv3), the above measures do not prejudice any other rights or remedies We may have under the law or other agreements. + +### III. Privacy Policy + +We respect and protect the personal privacy rights of all users of the Software. We are committed to minimizing data collection and processing information only to the extent necessary to achieve essential functions and improve services, in compliance with applicable data protection laws. + +**Unless otherwise stated in these Terms or a separately published Privacy Policy, the core functions of the Software are performed primarily on your local device. We do not proactively read, upload, or store personal sensitive information related to the content of your creations.** + +> In the event of any inconsistency between these Terms and the Privacy Policy updated and published separately by Us from time to time, the latest published Privacy Policy shall prevail. + +#### 1. Information We Collect and How We Use It + +To improve product functionality, perform software updates, and provide technical support, We may collect and process the following information within the **minimum necessary scope**. We will not use it for purposes unrelated to the Software. + +| Information Name | Purpose of Collection | Collection and Processing Method | +| :--- | :--- | :--- | +| **IP Information** and **Desktop OS Information (e.g., OS version, locale)** | 1. To determine compatibility and software update strategies for different regions and system versions;
2. To understand the system distribution of the user base for planning future development and optimization. | Automatically captured and recorded via network requests when you check for updates or access Our update servers. We focus on statistical analysis and will not attempt to directly link this to personally identifiable information. | +| **Unique User ID (User ID)** | 1. To accurately count active users and avoid duplicate counting;
2. To correlate configuration statistics, crash statistics, etc., where necessary, to improve the product. | We generate a **GUID locally on your device** by obtaining hardware information such as motherboard UUID and CPUID, and use the **SHA256 hash** of it as the User ID. This identifier is designed not to contain direct identification information such as your name or account, and We will not attempt to reverse engineer your real identity based on this identifier. | +| **Software Settings Status (e.g., feature toggles, preferences)** | 1. To evaluate the overall usage of various functions to guide future functional adjustments and optimizations;
2. To provide more reasonable default settings and experiences for different user groups. | We may statistically analyze the status of certain settings in the Software (e.g., whether a function is enabled) and associate it with the User ID anonymously for overall analysis. The default values of some settings may refer to your system settings (e.g., system language, theme color); this reading operation is performed locally only, and We will not upload the raw details of your system settings. | + +**We will not use the above information for automated decision-making or user profiling, nor will we use it for cross-site or cross-service tracking.** + +#### 2. Telemetry and Error Reporting (User Initiated) + +When the Software crashes or encounters a serious error, the Software may generate an error report on your local device to assist in analyzing the problem. We handle such data based on the principle of **completely voluntary and explicit consent**: + +- **Error reports are not uploaded automatically.** + We will only receive relevant information when you are explicitly aware of it and **choose to send it proactively**. +- Before clicking "Send," you can usually view or locate the error report file; We recommend that you check it yourself before sending to ensure it does not contain sensitive content you do not wish to share. +- Depending on the specific error type, the error report may contain the following information (content may vary): + - **System Configuration Information**: e.g., CPU info, graphics card info, memory info, screen parameters (e.g., resolution, refresh rate, EDID, etc.); + - **Software Logs and Memory Dump Files**: Used to analyze the internal state of the program when the crash occurred; + - **Relevant Process Information (e.g., process name, basic process info)**: Used to troubleshoot compatibility issues with other processes. + +**Our sole purpose in receiving such error reports is to locate and fix issues and improve the stability and performance of the Software.** +Unless required to meet mandatory legal or regulatory requirements, or necessary to protect the significant legitimate rights and interests of you or others, We will not use error reports for purposes unrelated to failure analysis. + +#### 3. Localization and Content Data + +We specifically declare and promise: + +1. The main functions of the Software (including screen annotation, drawing, screenshot processing, etc.) run on your local device. + +2. Any content created or processed by you during use, including but not limited to: + + - Screenshots; + - Annotation content (text, graphics, handwriting, etc.); + - Canvas files or project files; + - Materials displayed by you during teaching, presentation, or creative activities, + are stored by default **only on your local device or storage locations of your choice (e.g., local disks, third-party cloud drives configured by you)**. + +3. We will not access, collect, or upload the above content through backdoors or hidden functions of the Software, nor will We transfer content stored locally by you to Our servers without your explicit consent. + +4. If future versions add functions such as online synchronization, cloud backup, or online collaboration, We will inform you of the types of data to be collected and processed in a conspicuous manner **before you enable the relevant functions** and obtain your separate consent. + +#### 4. Data Storage, Cross-Border Transfer, and Security + +1. **Data Storage Location** + + 1.1 Under the current product design, the Software and related online services are primarily provided to users located **within the territory of the People's Republic of China (PRC)**. We primarily store limited telemetry statistical data and error reports proactively sent by you on servers located **within the PRC**. + + 1.2 We may use third-party service providers (e.g., code hosting platforms, log or error tracking services) to assist Us in processing the above data. We select service providers that operate servers **within the PRC** and comply with legal and regulatory requirements to process data related to the Software; under the current business architecture, this data will not be transferred to or stored on overseas servers. + + 1.3 If cross-border data transfer becomes necessary due to business development or compliance requirements in the future, We will fully inform you of the relevant purpose, data type, recipient, and protection measures in advance within the scope required by applicable law, and proceed only after obtaining necessary authorization or fulfilling statutory procedures. + +2. **Data Encryption and Security Measures** + + 2.1 During transmission, We will use industry-standard security measures (e.g., TLS/HTTPS) to the extent possible to protect data from unauthorized access or tampering. + + 2.2 During storage, We will take technical and management measures within a reasonable scope to prevent data from unauthorized access, use, disclosure, modification, or loss. + + 2.3 Although We have made reasonable efforts to improve security, data transmitted over the Internet and stored electronically **cannot be guaranteed to be absolutely secure**. You understand and accept the inherent risks associated with the Internet. + +3. **Data Retention Period** + + 3.1 We generally retain your relevant data only for the shortest period necessary to achieve the purposes stated in these Terms. + + 3.2 When the data is no longer necessary for the stated purposes, or when the law no longer requires Us to retain it, We will delete, anonymize, or otherwise handle such data in accordance with applicable laws. + +4. **Data Sharing and Disclosure** + + We may share or disclose data in the following circumstances, provided it complies with legal and regulatory requirements and is within a necessary and reasonable scope: + + - Sharing relevant data with third-party service providers who provide technical services to Us for limited purposes such as error analysis, log hosting, or statistical analysis; We will require them to comply with confidentiality obligations and data protection requirements; + - Providing relevant data to competent authorities when necessary to comply with applicable laws, regulations, court orders, or requirements of competent authorities, or to enforce applicable government regulatory measures; + - In connection with a merger, division, asset transfer, or similar corporate transaction, if the transfer of personal data is involved, We will require the new recipient to continue to be bound by these Terms and applicable laws, or re-obtain your consent if necessary. + +5. **What We Will Not Do** + + - We will **not** sell or rent your personal data to third parties for their independent commercial purposes; + - We will **not** create cross-service user profiles for the purpose of pushing third-party targeted advertising to you. + +#### 5. Protection of Minors + +1. We do not proactively provide the Software to minors as a specific target audience, nor do We proactively collect personal information from minors. + +2. If you are a minor user, please use the Software with the consent and under the guidance of your guardian. + +3. If We discover that We have collected personal information from a minor without guardian consent, We will take reasonable measures to delete or anonymize such information as soon as possible after becoming aware of it (unless applicable law requires Us to retain it). + +#### 6. Updates to These Terms + +We may update these Terms as the Software's functions expand, applicable laws and policies change, or data processing methods are adjusted. We will notify you of material changes by updating the "Modification Date" on this page, via in-software pop-ups, or through other reasonable means (to the extent technically and legally permissible). + +**Your continued use of the Software after the update takes effect constitutes your reading, understanding, and agreement to be bound by the updated Terms.** +If you do not agree to the updated content, you have the right to stop using the Software and uninstall it. + +### IV. Open Source License and Third-Party Components + +#### 1. Open Source License of the Software + +The core source code of the Software is released under the **GNU General Public License v3.0 (GPLv3)**. + +Subject to compliance with the terms of the GPLv3: + +- You are free to view, use, copy, modify, and distribute the source code of the Software; +- If you modify, redistribute, or integrate the source code of the Software into other software (constituting a "derivative work"), you generally need to: + - License the entire derivative work in a manner compatible with GPLv3; and + - Provide the recipient with the complete source code or a way to obtain the source code when distributing it to others; +- Any content in these Terms of Use that conflicts with the terms of the GPLv3 shall be superseded by the provisions of the GPLv3 within the scope of the conflict. + +The complete source code and build process are located in the GitHub repository (hereinafter referred to as the "Official Repository"): + +- Official Repository: + +> A mirror repository is located at GitCode: . +> There may be a time delay in synchronization between the mirror repository and the official repository. In case of inconsistency in terms or license information, the official repository shall prevail. + +For the complete text of the GPLv3 license, please refer to: + +- + +> These Terms of Use primarily apply to: +> - The use of the **compiled executable programs**, installation packages, documentation, websites, and related services you obtain; +> +> While the copying, modification, and redistribution of the source code itself are primarily governed by the GPLv3 agreement. + +#### 2. Code Signing and Integrity + +To improve the security and credibility of released versions, the Software digitally signs official executable files. + +- We may use the trusted third-party certificate authority **SignPath Foundation** to provide code signing support, and We collaborate with SignPath on release process security audits; +- The code signing, build, and release processes are integrated into the CI/CD workflow of GitHub Actions; +- You can verify the digital signature to confirm whether the downloaded installation package or executable file comes from an official release channel and whether it has been tampered with during transmission. + +For relevant information, please see: + +- SignPath Project Page: + +> Even though the Software is open-source software, We strongly recommend that you **obtain executable files only from officially announced release pages or trusted channels** and verify signatures where possible to reduce the risk of malicious tampered versions. + +#### 3. Third-Party Open Source Components + +The development of the Software uses and integrates multiple third-party open-source libraries. +These third-party components are subject to their respective independent open-source licenses, which are usually included in the Software's source code repository or distribution package. + +We hereby express our gratitude to these open-source projects and their contributors. The following are some major third-party components and their licensing information (summary only; specific constraints are subject to the license text accompanying each component): + +| Component Name | License | Copyright / Notes | +| :--- | :--- | :--- | +| **[abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)** | Apache License 2.0 | | +| **[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp)** | See LICENSE / Readme in lib | Copyright (c) 2007–2011 Benjamin Gr¸delbach | +| **[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)** | GNU General Public License v3.0 | | +| **[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue)** | See LICENSE / Readme in lib | Copyright (c) 2013–2016, Cameron Desrochers. All rights reserved. | +| **[efficient/libcuckoo](https://github.com/efficient/libcuckoo)** | See LICENSE / Readme in lib | Copyright (C) 2013, Carnegie Mellon University and Intel Corporation | +| **[gabime/spdlog](https://github.com/gabime/spdlog)** | MIT License | Copyright (c) 2016 Gabi Melman. | +| **[google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler)** | Apache License 2.0 | | +| **[martinus/unordered_dense](https://github.com/martinus/unordered_dense)** | MIT License | Copyright (c) 2022 Martin Leitner-Ankerl | +| **[mohabouje/WinToast](https://github.com/mohabouje/WinToast)** | MIT License | Copyright (C) 2016–2023 WinToast v1.3.0 - Mohammed Boujemaoui | +| **[nothings/stb](https://github.com/nothings/stb)** | MIT License | Copyright (c) 2017 Sean Barrett | +| **[ocornut/imgui](https://github.com/ocornut/imgui)** | MIT License | Copyright (c) 2014–2025 Omar Cornut | +| **[openssl/openssl](https://github.com/openssl/openssl)** | Apache License 2.0 | Copyright (c) 1998–2025 The OpenSSL Project Authors. Copyright (c) 1995–1998 Eric A. Young, Tim J. Hudson. All rights reserved. | +| **[sammycage/lunasvg](https://github.com/sammycage/lunasvg)** | MIT License | Copyright (c) 2007–2010 Baptiste Lepilleur and The JsonCpp Authors | +| **[sammycage/plutovg](https://github.com/sammycage/plutovg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[yhirose/cpp-httplib](https://github.com/yhirose/cpp-httplib)** | MIT License | Copyright (c) 2017 yhirose | +| **[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win)** | See project page and license | | +| **[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX)** | MIT License | Copyright (c) 2022 zouhuidong | + +For the **complete list** of all third-party libraries used by the Software and their corresponding license texts, please consult: + +- **Notice of Major Components (NOTICE)**: + +- **Summary of Third-Party License Files**: + + +> In the event of any conflict or inconsistency between these Terms of Use and the licenses of the aforementioned third-party components, the specific terms of the respective third-party licenses shall prevail regarding the use, copying, modification, and distribution of such third-party components. + +#### 4. Other Third-Party Components and Proprietary Licensing + +In addition to the open-source components mentioned above, the Software may also use certain third-party libraries or tools licensed under other terms during development and operation. We likewise express our gratitude to the authors and maintainers of these projects. + +The following is a summary of some non-open-source (or partially open-source) components currently used and their licensing information (if any): + +| Component Name | Licensing / Usage Note | +| :--- | :--- | +| **[EasyX](https://easyx.cn/)** | Used in accordance with the licensing terms published on the official EasyX website. Specific rights and obligations are subject to the latest official EasyX licensing terms. | + +> Our use of third-party components does not constitute any claim of ownership over their projects. +> When you independently use the above third-party software, libraries, or services, you should still carefully read and comply with their respective terms of use and licenses. + +### V. Intellectual Property + +#### 1. Proprietary Intellectual Property + +Except for the source code and third-party open-source components authorized under open-source licenses as described in Article IV of this Agreement, other intellectual property rights related to the Software, including but not limited to: + +- The Software name, logo, icons (excluding parts of third-party icon fonts belonging to their copyright owners), interface design, and visual elements; +- The overall interaction design, layout, and organizational structure of the Software; +- Official documentation, manuals, help content, tutorials, sample data, demo videos, promotional materials, etc., released by the Software; +- Images, graphic materials, icons, and other design resources independently created by Us and provided with the Software, which are not subject to third-party proprietary license restrictions; + +The copyright, trademark rights, patent rights, and other relevant rights of the above content belong to **Us (Inkeys Development Team)** or have been legally authorized by their rights holders. + +Provided there is no conflict with applicable open-source licenses (e.g., GPLv3), without Our prior written permission, you may not in any way: + +- Remove, hide, or alter copyright notices, trademarks, logos, or other rights notices displayed in the Software; +- Use names, logos, or branding elements identical or confusingly similar to "Inkeys" in a manner that may cause confusion; +- Use the Software's name, logo, or interface to promote products or services not associated with Us or not authorized by Us; +- Sell or distribute the Software and various intellectual property rights related to the Software for profit. + +> If any statement regarding intellectual property rights in this Agreement conflicts with open-source licenses (e.g., GPLv3) within the scope of source code usage, the terms of the respective open-source license shall prevail regarding the source code part. + +#### 2. Third-Party Fonts and Design Resources + +The Software uses certain third-party fonts and icon fonts as design resources in its interface display, such as (including but not limited to): + +- **HarmonyOS Sans** series fonts; +- **Douyu Font**, etc.; +- **Segoe Fluent Icons / Segoe Fluent UI** and other icon fonts or related icon sets. + +Regarding the above third-party fonts and design resources, please understand and agree that: + +1. **Ownership of Rights** + - The copyright, trademark rights, and other rights of the above fonts, icon fonts, and related design resources belong to their respective owners; + - We use them for interface display and other purposes of the Software only within the scope permitted by their respective licensing terms; + - Nothing in these Terms of Use should be construed as transferring or licensing any rights or interests in these third-party fonts or design resources to you. + +2. **Restrictions on Your Use** + - You must not extract these third-party fonts or design resources from the Software independently for separate distribution, sale, rental, or download; + - If you wish to use the above fonts or icon fonts independently outside of the Software (including but not limited to commercial scenarios), you should consult and comply with the relevant licensing terms from their rights holders or official channels and obtain separate authorization if necessary; + - For third-party design resources that We do not have the right to sublicense, We do not provide any form of sublicense commitment or legal guarantee to you. + +3. **Relation to the Open Source Nature of the Software** + - The source code of the Software (within the scope of GPLv3) is open source; + - However, certain fonts, icon fonts, or other design resources contained in the Software may **not be licensed under an open-source license along with the source code**; + - If you redistribute or perform secondary development based on the Software, **it is your responsibility to verify and comply with the licensing terms of these third-party resources** and replace them with resources you are legally entitled to use if necessary. + +#### 3. Copyright Notice + +Copyright © 2023–2025 **AlanCRL (Chen Runlin) Studio** +All Rights Reserved (within the scope permitted by applicable open-source licenses). + +Third-party fonts, icon fonts, and other resources subject to third-party licenses are the property of their respective rights holders. + +### VI. DISCLAIMER + +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, YOU UNDERSTAND AND AGREE TO THE FOLLOWING TERMS: + +1. **PROVIDED "AS IS"** + + THE SOFTWARE AND RELATED SERVICES ARE PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS. + WE MAKE NO REPRESENTATIONS OR WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, REGARDING THE SOFTWARE, INCLUDING BUT NOT LIMITED TO: + + - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE; + - CONTINUOUS AVAILABILITY, ABSENCE OF ERRORS OR DEFECTS; + - FULL COMPATIBILITY WITH SPECIFIC HARDWARE, SYSTEMS, OR SOFTWARE ENVIRONMENTS; + - ANY WARRANTIES NOT EXPRESSLY STATED IN THIS AGREEMENT OR APPLICABLE OPEN-SOURCE LICENSES. + +2. **LIMITATION OF LIABILITY** + + TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WE, OUR CONTRIBUTORS, AND PARTNERS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES (INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERRUPTION, LOSS OF DATA, DAMAGE TO GOODWILL, OR OTHER INTANGIBLE LOSSES) ARISING OUT OF OR RELATED TO ANY OF THE FOLLOWING: + + - YOUR ACCESS TO, DOWNLOAD, INSTALLATION, OR USE OF THE SOFTWARE; + - YOUR FAILURE TO USE THE SOFTWARE CORRECTLY IN ACCORDANCE WITH THE DOCUMENTATION OR INSTRUCTIONS; + - MODIFICATION, REPACKAGING, REDISTRIBUTION, OR INTEGRATION OF THE SOFTWARE BY THIRD PARTIES; + - ANY SECURITY INCIDENTS, VULNERABILITIES, OR ERRORS RELATED TO THE SOFTWARE. + +3. **THIRD-PARTY CONTENT AND SERVICES** + + THE SOFTWARE MAY INTERACT OR INTEGRATE WITH THIRD-PARTY SERVICES, WEBSITES, LIBRARIES, OR CONTENT (E.G., THIRD-PARTY OPEN-SOURCE LIBRARIES, THIRD-PARTY APIS, WEBSITE LINKS). + WE ASSUME NO RESPONSIBILITY FOR THE AVAILABILITY, SECURITY, ACCURACY, OR LEGALITY OF SUCH THIRD-PARTY CONTENT OR SERVICES, NOR ARE WE LIABLE FOR ANY LOSS INCURRED BY YOUR USE OF SUCH THIRD-PARTY CONTENT OR SERVICES. + YOU SHOULD REVIEW AND COMPLY WITH THE RELEVANT THIRD-PARTY TERMS OF USE AND PRIVACY POLICIES BEFORE USE. + +4. **NON-EXCLUSION OF MANDATORY LEGAL RIGHTS** + + SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OF LIABILITY FOR CERTAIN DAMAGES. THE ABOVE DISCLAIMERS AND LIMITATIONS OF LIABILITY MAY NOT APPLY TO YOU IN WHOLE OR IN PART IN SUCH JURISDICTIONS. + IN SUCH CASES, THESE TERMS APPLY ONLY TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW AND DO NOT AFFECT ANY MANDATORY RIGHTS YOU MAY HAVE UNDER LOCAL LAW. + +### VII. Update and Effectiveness of the Agreement + +1. **Updates** + + We reserve the right to modify, update, or supplement this Agreement when necessary. These modifications may be due to reasons including but not limited to: + + - Adapting to changes in applicable laws, regulations, and regulatory policies; + - Reflecting adjustments to the Software's functions, service models, or technical architecture; + - Improving user experience, security, or compliance levels. + +2. **Method of Notification** + + When material changes occur to this Agreement, We will notify you of the updated content through one or more reasonable means: + + - Displaying via pop-up windows or prompts upon Software launch; + - Posting announcements on the official website or project homepage (e.g., GitHub repository). + +3. **Acceptance and Rejection** + + - From the effective date stated in the updated version or notification, **your continued downloading, installation, or use of the Software constitutes your full reading, understanding, and acceptance of the updated Agreement content**; + - If you do not agree to the updated Agreement content, you must immediately stop using the Software and uninstall the installed version. + +> To stay informed of the latest terms, We recommend that you check the page where this Agreement is located or relevant files in the official repository from time to time. + +### VIII. Governing Law and Dispute Resolution + +1. **Governing Law** + The conclusion, execution, interpretation, and dispute resolution of these Terms shall be governed by the laws of the People's Republic of China (for the purpose of these Terms, excluding the Hong Kong Special Administrative Region, the Macau Special Administrative Region, and the Taiwan region). If the laws of the People's Republic of China do not provide for a matter or the provisions are unclear, reference shall be made to commercial practices or general industry rules. + +2. **Exceptions for Local Law (Consumer Protection)** + Notwithstanding the foregoing, if you are a consumer in the European Union, the United Kingdom, the United States, or other jurisdictions, and local laws explicitly provide for mandatory consumer protection rights that cannot be excluded by contract (e.g., the right to sue in the consumer's place of residence), the jurisdiction clause in these Terms shall not deprive you of such rights under mandatory local laws. In such cases, other parts of these Terms shall remain in full force and effect. + +3. **Language** + These Terms may be published in multiple languages. In the event of any conflict or ambiguity between the Chinese version and other language versions, the Simplified Chinese version shall prevail to the extent permitted by applicable law. + +### IX. Contact Us + +If you have any questions, comments, or suggestions regarding this Agreement, the use of the Software, open-source licensing, privacy protection, or other related matters, please contact Us via: + +- **Project Homepage (GitHub):** +- **Contact Email:** + - alan-crl@foxmail.com + - alancrl1007@gmail.com + +We will make reasonable efforts to respond to your reasonable inquiries or feedback as soon as possible within the limits of our capacity and resources, but We do not guarantee a response to every correspondence. \ No newline at end of file diff --git a/TOS/zh-CN.md b/TOS/zh-CN.md new file mode 100644 index 00000000..2b44a0b7 --- /dev/null +++ b/TOS/zh-CN.md @@ -0,0 +1,416 @@ +# 智绘教Inkeys 使用条款 + +**简体中文** | [English](./en-us.md) + +--- + +**版本** 1.0 +**发布日期:** 2025年11月28日 +**生效日期:** 2025年11月28日 + +**本《智绘教Inkeys 使用条款》(以下简称“本条款”)是您(“用户”或“您”)与智绘教Inkeys 开发团队(下称“我们”)之间关于您下载、安装、复制或以其他方式使用“智绘教Inkeys”软件(包括其更新、升级、相关文档及附属组件,统称为“本软件”)所订立的具有法律约束力的协议。** + +**在您下载、安装或使用本软件之前,请务必仔细阅读并充分理解本条款,尤其是其中以加粗形式标注的条款,这些条款可能与您的权利义务或责任限制有关。** + +**若您不同意本条款的任何内容,请不要下载、安装或使用本软件。** + +**一旦您下载、安装、复制、运行或以其他方式使用本软件,即视为您已阅读并同意受本条款及其不时更新版本的约束。** + +特别地,在您首次启动本软件时,本软件将引导您阅读本协议。通过勾选代表“同意”的复选框并继续下一步操作的行为,即表示您确认已完全理解并接受本协议的全部条款。 + +如您代表某一公司、组织或其他实体使用本软件,则您声明并保证,您已获得充分授权可以使该实体受本条款约束,并且在本条款中,“您”也包括该公司、组织或其他实体。 + +--- + +### 一、服务说明 + +1. 本软件由我们开发和提供,旨在为用户提供屏幕批注、标记、绘制及相关辅助工具功能。具体功能和界面可能会随版本更新而变化。 +2. 本软件主要面向全球范围内的一般用户,包括教育工作者、学生、创作者及其他需要屏幕批注功能的个人或组织使用。本软件**不针对任何特定行业、特定地区或特定监管领域进行专门设计或认证**,如您在高风险领域场景中使用本软件,应自行评估其适用性并自行承担风险。 +3. 我们可能根据产品规划、技术条件或法律法规的要求,对本软件的功能进行增加、删除或调整,或在合理范围内中止、暂停或终止对本软件的维护和更新。我们将尽量通过官方网站、项目主页或软件内通知等方式提示重要变更(在技术和法律允许的范围内)。 +4. 本软件为**免费提供**。 + +### 二、用户资格、权利与义务 + +1. **用户资格** + + 1.1 您确认,在使用本软件时,您具有完全民事行为能力,能够独立承担法律责任。 + + 1.2 如您为未满 18 周岁的未成年人,或您所在司法辖区规定的未成年人,您应在监护人阅读并同意本条款及相关隐私条款后使用本软件。未成年人使用本软件的行为,即视为已获得监护人的同意和指导。 + + 1.3 如您代表公司、组织或其他实体使用本软件,您声明并保证您有权使该实体受本条款约束。 + +2. **用户权利** + + 在您遵守本条款及适用法律法规的前提下,您享有以下权利: + + 2.1 您有权在本条款及适用开源许可证允许的范围内下载、安装和使用本软件。 + + 2.2 您有权在个人、教育、科研和商业场景中使用本软件的功能,但应同时遵守适用法律法规以及本条款的各项约定,且对受 GPLv3 等开源许可证约束的部分,须遵守相应开源许可证的要求。 + + 2.3 您有权在适用法律允许的范围内,就您的个人信息向我们行使访问、更正、删除、限制处理、撤回同意等权利,具体方式请参阅本条款第三部分“隐私政策”。 + + 2.4 当您不同意本条款(包括后续的修订版本)时,您有权停止使用并卸载本软件。卸载本软件并不能自动消除您在此前使用本软件期间已经发生的权利义务或责任。 + + +3. **用户义务** + + 在使用本软件时,您承诺并同意: + + 3.1 遵守适用的法律法规、本条款及其他与本软件相关的规则,不得利用本软件从事任何违法、违规或侵权行为,包括但不限于: + + - 侵犯他人的知识产权、隐私权、名誉权或其他合法权益; + - 传播、存储或发布违法、有害或受禁止的内容; + - 参与欺诈、网络攻击、恶意入侵、破坏计算机信息系统等活动。 + + 3.2 不以可能损害、瘫痪、过载或削弱本软件正常运行的方式使用本软件,或干扰他人对本软件的正常使用。 + + 3.3 不通过本软件故意传播病毒、木马或其他恶意程序。 + + 3.4 不伪造、篡改或删除本软件中的版权声明、商标或其他所有权声明。 + + 3.5 在向我们主动提交错误报告、日志、内存转储文件或其他技术信息前,审慎检查其中是否包含您不希望我们接触的敏感内容(例如商业机密、个人隐私等)。 + + 3.6 妥善保管您的设备和系统环境配置,不得故意将装有本软件的设备提供给他人用于违法活动。 + +4. **违约处理** + + 4.1 如果我们有合理理由认为您违反本条款或适用法律法规,我们有权在法律允许的范围内采取合理措施,包括但不限于: + + - 要求您立即停止相关违规行为; + - 限制或终止向您提供相关服务(如在线更新、技术支持),并在法律允许的范围内禁止您使用带有我们商标和数字签名的官方发行版; + - 在必要时向相关主管机关报告,并配合其调查。 + + 4.2 在不与适用的开源许可证(如 GPLv3)相冲突的前提下,上述措施不影响我们根据法律或其他协议享有的任何其他权利救济。 + +### 三、隐私政策 + +我们尊重并保护所有使用本软件用户的个人隐私权。我们致力于在遵守适用的数据保护法律的前提下,尽量减少数据收集,仅在实现必要功能和改进服务所需的范围内处理信息。 + +**除非本条款或单独公布的隐私政策另有说明,本软件的核心功能主要在您的本地设备上完成,我们不会主动读取、上传或存储与您创作内容本身相关的个人敏感信息。** + +> 如本条款与我们不时更新并单独发布的隐私政策存在不一致之处,以最新发布的隐私政策为准。 + +#### 1. 我们收集的信息及用途 + +为了改进产品功能、进行软件更新和提供技术支持,我们可能在**最小必要范围内**收集和处理以下信息。我们不会用于与本软件无关的目的。 + +| 信息名称 | 收集目的 | 收集与处理方式 | +| :--- | :--- | :--- | +| **IP 信息** 和 **桌面操作系统信息(如操作系统版本、语言环境)** | 1. 用于判断不同地区、不同系统版本的兼容性和软件更新策略;
2. 了解用户群体使用的系统分布,以规划未来的开发方向和功能优化。 | 在您检查更新或访问我们的更新服务器时,通过网络请求自动获取并记录。我们以统计分析为主,不会尝试将其与可识别个人身份的信息直接关联。 | +| **唯一用户标识符 (User ID)** | 1. 准确统计活跃用户数量,避免重复计数;
2. 在必要时关联配置统计、崩溃统计等,以便改进产品。 | 我们通过获取您设备的主板 UUID、CPUID 等硬件信息,在**本地设备上生成一个 GUID**,并对其进行 **SHA256 哈希计算**后作为 User ID 使用。该标识符在设计上不包含您的姓名、账号等直接识别信息,我们不会尝试基于该标识符反向推导您的真实身份。 | +| **软件设置项状态(如功能开关、偏好设置等)** | 1. 评估各项功能的整体使用情况,以指导未来的功能调整与优化;
2. 为不同用户群体提供更合理的默认设置和体验。 | 我们可能会统计您在软件中的部分设置项状态(例如某个功能是否开启),并与 User ID 进行匿名化关联用于整体分析。部分设置的默认值可能参考您的系统设置(如系统语言、主题颜色);该读取操作仅在本地进行,我们不会上传您系统设置的原始详细信息。 | + +**我们不会基于上述信息进行自动化决策或用户画像构建,亦不会用作跨站点或跨服务的跟踪。** + +#### 2. 遥测与错误报告(用户主动发送) + +当本软件发生崩溃或严重错误时,软件可能会在您的本地设备上生成一份错误报告,用于协助分析问题。我们对该类数据采取**完全自愿、显式同意**的原则处理: + +- **错误报告不会自动上传。** + 只有在您明确知情并**主动选择发送**给我们时,我们才会接收到相关信息。 +- 在您点击“发送”之前,您通常可以查看或定位到错误报告文件;我们建议您在发送前自行检查其中是否包含您不希望分享的敏感内容。 +- 根据具体错误类型,错误报告中可能包含以下信息(不同情况内容可能不同): + - **系统配置信息**:如 中央处理器(CPU)信息、显卡信息、内存信息、屏幕参数(如分辨率、刷新率、EDID 等); + - **软件日志和内存转储文件(Dump File)**:用于分析崩溃发生时的程序内部状态; + - **相关进程信息(如进程名称、基础进程信息)**:用于排查与其他进程的兼容性问题。 + +**我们收到此类错误报告的唯一目的是:定位并修复问题,提升软件的稳定性和性能。** +除非为满足法律法规的强制要求,或为保护您或他人的重大合法权益所必需,我们不会将错误报告用于与故障分析无关的目的。 + +#### 3. 本地化处理与内容数据 + +我们特别声明并承诺: + +1. 本软件的主要功能(包括屏幕批注、绘制、截图处理等)均在您的本地设备上运行。 + +2. 您在使用过程中创建或处理的任何内容,包括但不限于: + + - 屏幕截图; + - 批注内容(文字、图形、手写笔迹等); + - 画布文件或项目文件; + - 您在教学、演示或创作活动中展示的资料, + 在默认情况下**仅存储在您的本地设备或您自主选择的存储位置(如本地磁盘、您自行配置的第三方云盘等)**。 + +3. 我们不会通过本软件后门或隐藏功能访问、收集或上传上述内容,也不会在未取得您明确同意的情况下将您本地存储的内容传输到我们的服务器。 + +4. 若未来某些版本新增在线同步、云备份或在线协同等功能,我们会在**您启用相关功能前**另行以显著方式向您说明所需收集和处理的数据类型,并征得您的单独同意。 + +#### 4. 数据存储、跨境传输与安全 + +1. **数据存储地点** + + 1.1 在目前的产品设计下,本软件及相关在线服务主要面向位于**中华人民共和国境内**的用户提供使用。我们主要在位于**中华人民共和国境内**的服务器上存储有限的遥测统计数据和您主动发送的错误报告。 + + 1.2 我们可能使用第三方服务提供商(例如代码托管平台、日志或错误跟踪服务)协助我们处理上述数据。我们仅选择在**中华人民共和国境内**运营服务器、并符合法律法规要求的服务提供商处理与本软件相关的数据;在当前业务架构下,这些数据不会被传输至境外服务器或存储在境外。 + + 1.3 如未来因业务发展或合规要求确有必要进行跨境数据传输,我们将在适用法律要求的范围内,事先向您充分告知相关目的、数据类型、接收方及保护措施,并在取得必要授权或履行法定程序后方可进行。 + +2. **数据加密与安全措施** + + 2.1 在传输过程中,我们将尽可能采用行业通用的安全措施(例如 TLS/HTTPS)保护数据不被未授权访问或篡改。 + + 2.2 在存储过程中,我们会在合理范围内采取技术和管理措施,防止数据遭受未经授权的访问、使用、披露、修改或丢失。 + + 2.3 尽管我们已尽合理努力提升安全性,但通过网络传输和电子存储的数据**无法保证绝对安全**。您理解并接受与互联网相关的固有风险。 + +3. **数据保留期限** + + 3.1 我们通常仅在实现本条款所述目的所必需的最短期间内保留您的相关数据。 + + 3.2 当数据不再为实现既定目的所必需,或法律不再要求我们继续保留时,我们将根据适用法律的要求删除、匿名化或以其他合理方式处理该等数据。 + +4. **数据共享与披露** + + 在符合法律法规要求,并在必要且合理的范围内,我们可能在以下情形中共享或披露数据: + + - 为实现错误分析、日志托管或统计分析等有限目的,向为我们提供技术服务的第三方服务提供商共享相关数据;我们将要求其遵守保密义务和数据保护要求; + - 为遵守适用法律法规、法院判决或有权机关的要求,或为执行适用的政府监管措施,有必要时向相关主管部门提供相关数据; + - 在与合并、分立、资产转让或类似的公司交易相关的情况下,如涉及到个人数据的转移,我们将要求新的接收方继续受本条款及适用法律的约束,或在必要时重新征得您的同意。 + +5. **我们不会的行为** + + - 我们**不会**将您的个人数据出售或出租给第三方用于其独立的商业目的; + - 我们**不会**以向您推送第三方定向广告为目的创建跨服务的用户画像。 + +#### 5. 未成年人保护 + +1. 我们不会以未成年人为特定对象主动提供本软件,也不会主动收集未成年人的个人信息。 + +2. 如您为未成年人用户,请在监护人的同意和指导下使用本软件。 + +3. 如我们发现未经监护人同意而收集到未成年人的个人信息,我们将在获知后采取合理措施尽快删除或匿名化处理该等信息(除非适用法律要求我们保留)。 + +#### 6. 本条款的更新 + +随着本软件功能的扩展、适用法律政策的变化或数据处理方式的调整,我们可能会对本条款进行更新。我们将通过在本页面更新“修改日期”、在软件内弹窗提示或通过其他合理方式通知您重大变更(在技术与法律允许的范围内)。 + +**在更新生效后,您继续使用本软件,即表示您已阅读、理解并同意受更新后的本条款约束。** +如您不同意更新内容,您有权停止使用本软件并卸载本软件。 + +### 四、开源协议与第三方组件 + +#### 1. 本软件的开源许可 + +本软件的核心源代码以 **GNU General Public License v3.0(GPLv3)** 许可发布。 + +在遵守 GPLv3 协议条款的前提下: + +- 您可以自由查看、使用、复制、修改和分发本软件的源代码; +- 若您基于本软件源代码进行修改、再发布、或将其整合进其他软件中(构成“派生作品”),则您通常需要: + - 以 GPLv3 兼容的方式对整个派生作品进行许可;以及 + - 在向他人分发时,向接收方提供完整的源代码或获取源代码的方式; +- 任何与 GPLv3 条款相冲突的本使用条款内容,在冲突范围内以 GPLv3 的规定为准。 + +完整的源代码和构建流程位于 GitHub 仓库(以下简称“官方仓库”): + +- 官方仓库: + +> 备用仓库位于 GitCode:。 +> 备用仓库与官方仓库之间的同步可能存在一定时间延迟,在条款或许可证信息不一致时,以官方仓库为准。 + +完整的 GPLv3 许可证文本,请参阅: + +- + +> 本使用条款主要适用于: +> - 您获取的**已编译可执行程序**、安装包、文档、网站和相关服务的使用; +> +> 而源代码本身的复制、修改和再分发,则主要受 GPLv3 协议约束。 + +#### 2. 代码签名与完整性 + +为提高发布版本的安全性和可信度,本软件对官方发布的可执行文件进行数字签名。 + +- 我们可能使用受信任的第三方证书颁发机构 **SignPath Foundation** 提供代码签名支持,我们与 SignPath 协作进行发布流程安全审核; +- 代码签名、构建和发布流程集成在 GitHub Actions 的 CI/CD 流程中; +- 您可以通过验证数字签名,确认下载的安装包或可执行文件是否来自官方发布渠道,是否在传输途中被篡改。 + +相关信息请参见: + +- SignPath 项目页: + +> 即使本软件为开源软件,我们仍强烈建议您**仅从官方公布的发布页面或可信渠道获取可执行文件**,并在可能的情况下验证签名,以降低恶意篡改版本的风险。 + +#### 3. 第三方开源组件 + +本软件的开发过程中使用并集成了多项第三方开源库。 +这些第三方组件各自适用其独立的开源许可证,通常包含在本软件的源代码仓库或发行包中。 + +我们在此对这些开源项目及其贡献者表示感谢。以下为部分主要第三方组件及其许可信息(仅为摘要,具体约束以各组件随附许可证文本为准): + +| 组件名称 | 许可证 | 版权信息 / 说明 | +| :--- | :--- | :--- | +| **[abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)** | Apache License 2.0 | | +| **[aksalj/hashlibpp](https://github.com/aksalj/hashlibpp)** | 参见库内 LICENSE / 说明文件 | Copyright (c) 2007–2011 Benjamin Gr¸delbach | +| **[Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker)** | GNU General Public License v3.0 | | +| **[cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue)** | 参见库内 LICENSE / 说明文件 | Copyright (c) 2013–2016, Cameron Desrochers. All rights reserved. | +| **[efficient/libcuckoo](https://github.com/efficient/libcuckoo)** | 参见库内 LICENSE / 说明文件 | Copyright (C) 2013, Carnegie Mellon University and Intel Corporation | +| **[gabime/spdlog](https://github.com/gabime/spdlog)** | MIT License | Copyright (c) 2016 Gabi Melman. | +| **[google/ink-stroke-modeler](https://github.com/google/ink-stroke-modeler)** | Apache License 2.0 | | +| **[martinus/unordered_dense](https://github.com/martinus/unordered_dense)** | MIT License | Copyright (c) 2022 Martin Leitner-Ankerl | +| **[mohabouje/WinToast](https://github.com/mohabouje/WinToast)** | MIT License | Copyright (C) 2016–2023 WinToast v1.3.0 - Mohammed Boujemaoui | +| **[nothings/stb](https://github.com/nothings/stb)** | MIT License | Copyright (c) 2017 Sean Barrett | +| **[ocornut/imgui](https://github.com/ocornut/imgui)** | MIT License | Copyright (c) 2014–2025 Omar Cornut | +| **[openssl/openssl](https://github.com/openssl/openssl)** | Apache License 2.0 | Copyright (c) 1998–2025 The OpenSSL Project Authors. Copyright (c) 1995–1998 Eric A. Young, Tim J. Hudson. All rights reserved. | +| **[sammycage/lunasvg](https://github.com/sammycage/lunasvg)** | MIT License | Copyright (c) 2007–2010 Baptiste Lepilleur and The JsonCpp Authors | +| **[sammycage/plutovg](https://github.com/sammycage/plutovg)** | MIT License | Copyright (c) 2020–2025 Samuel Ugochukwu | +| **[yhirose/cpp-httplib](https://github.com/yhirose/cpp-httplib)** | MIT License | Copyright (c) 2017 yhirose | +| **[Zip Utils](https://www.codeproject.com/Articles/7530/Zip-Utils-Clean-Elegant-Simple-Cplusplus-Win)** | 参见项目页面及随附许可证 | | +| **[zouhuidong/HiEasyX](https://github.com/zouhuidong/HiEasyX)** | MIT License | Copyright (c) 2022 zouhuidong | + +关于本软件使用的所有第三方库的**完整列表**及其对应的许可证文本,请查阅: + +- **主要组件声明(NOTICE)**: + +- **第三方许可证文件汇总**: + + +> 当本使用条款与上述第三方组件的许可证存在冲突或不一致时, +> 就相关第三方组件的使用、复制、修改和分发事宜,以相应第三方许可证的具体条款为准。 + +#### 4. 其他第三方组件与专有授权 + +除上述开源组件外,本软件在开发和运行过程中还可能使用部分以其他许可方式授权的第三方库或工具。我们同样对这些项目的作者和维护者表示感谢。 + +以下为目前使用的部分非(或不完全)开源组件及其授权信息摘要(如有): + +| 组件名称 | 许可授权 / 使用说明 | +| :--- | :--- | +| **[EasyX](https://easyx.cn/)** | 根据 EasyX 官方网站公布的授权条款使用。具体权利义务以 EasyX 官方的最新授权条款为准。 | + +> 我们对第三方组件的使用不构成对其项目的任何所有权声明。 +> 您在独立使用上述第三方软件、库或服务时,仍应仔细阅读其各自的使用条款和许可证,并自行遵守。 + +### 五、知识产权 + +#### 1. 自有知识产权 + +除本协议第四条所述依据开源许可证授权的源代码和第三方开源组件外,本软件相关的其他各项知识产权,包括但不限于: + +- 本软件名称、标识(Logo)、图标(不含第三方图标字体中属于其版权所有人的部分)、界面设计与视觉元素; +- 本软件的整体交互设计、版式布局、组织结构; +- 本软件官方发布的文档、说明书、帮助内容、教程、示例数据、演示视频、宣传材料等; +- 由我们独立创作并随软件一同提供,且不受第三方专有许可限制的图片、图形素材、图标等设计资源; + +上述内容的著作权、商标权、专利权及其他相关权利,均归 **我们(智绘教Inkeys 开发团队)** 所有或已合法取得其权利人的授权。 + +在不与适用的开源许可证(例如 GPLv3)相冲突的前提下,未经我们事先书面许可,您不得以任何方式: + +- 去除、隐藏或更改本软件中显示的版权声明、商标、标识或其他权利声明; +- 以可能造成混淆的方式使用与“智绘教Inkeys”相同或近似的名称、标识或品牌元素; +- 将本软件的名称、标识或界面等用于推广与我们不存在关联或未获我们授权的产品或服务; +- 出售或以盈利目的分发本软件及其与本软件相关的各项知识产权。 + +> 若本协议中关于知识产权的表述与开源许可证(如 GPLv3)在源代码使用范围内存在冲突,则就源代码部分优先适用相应开源许可证的条款。 + +#### 2. 第三方字体与设计资源 + +本软件在界面展示中使用了部分第三方字体和图标字体等设计资源,例如(包括但不限于): + +- **HarmonyOS Sans** 系列字体; +- **Douyu Font** 等字体; +- **Segoe Fluent Icons / Segoe Fluent UI** 等图标字体或相关图标集。 + +关于上述第三方字体和设计资源,请您理解并同意: + +1. **权利归属** + - 上述字体、图标字体及相关设计资源的著作权、商标权及其他权利,均归各自权利人所有; + - 我们仅在其各自授权条款允许的范围内,将其用于本软件的界面展示等用途; + - 本使用条款中的任何内容,均不应被理解为将这些第三方字体或设计资源的任何权益转让或授权给您。 + +2. **您的使用限制** + - 您不得将这些第三方字体或设计资源从本软件中单独抽取出来,作为独立资源另行分发、出售、出租或提供下载; + - 若您希望在本软件之外自行使用上述字体或图标字体(包括但不限于商用场景),应自行向其权利人或官方渠道查阅并遵守相应授权条款,并在必要时单独取得授权; + - 对于我们无权再授权的第三方设计资源,我们不向您提供任何形式的再授权承诺或法律保证。 + +3. **与本软件开源属性的关系** + - 本软件的源代码(在 GPLv3 范围内)为开源; + - 但本软件中包含的某些字体、图标字体或其他设计资源可能 **不随源代码一起以开源许可证授权**; + - 若您基于本软件进行再发行或二次开发,**有责任自行确认和遵守这些第三方资源的授权条款**,必要时应替换为您有权合法使用的资源。 + +#### 3. 版权声明 + +Copyright © 2023–2025 **AlanCRL(陈润林)工作室** +保留所有权利(在适用的开源许可证允许范围内)。 + +第三方字体、图标字体以及其他受第三方许可约束的资源,其版权和相关权利归各自权利人所有。 + +### 六、免责声明 + +在适用法律允许的最大范围内,您理解并同意如下条款: + +1. **按“现状”提供** + + 本软件及其相关服务基于“现状”和“可用性”提供。 + 我们不对本软件的以下事项作出任何形式的明示或默示保证或陈述,包括但不限于: + + - 适销性、特定用途适用性; + - 持续可用性、无错误或缺陷; + - 与特定硬件、系统、软件环境的完全兼容性; + - 任何未在本协议或适用开源许可证中明确说明的保证。 + +2. **责任限制** + + 在适用法律允许的最大范围内,即使我们已被事先告知发生此类损害的可能性,对于因下列任一原因而产生或与之相关的任何直接、间接、附带、特殊、惩罚性或后果性损害(包括但不限于利润损失、营业中断、数据丢失、商誉受损或其他无形损失),我们和我们的贡献者、合作方均不承担任何责任: + + - 您对本软件的访问、下载、安装或使用; + - 您未能按照文档或说明正确使用本软件; + - 第三方对本软件的修改、重新打包、二次分发或整合; + - 任何与本软件相关的安全事件、漏洞或错误。 + +3. **第三方内容与服务** + + 本软件可能与第三方服务、网站、库或内容进行交互或集成(例如:第三方开源库、第三方 API、网站链接等)。 + 对于此类第三方内容或服务的可用性、安全性、准确性或合法性,我们不承担责任,亦不对您使用此类第三方内容或服务产生的任何损失负责。 + 您在使用前应自行审阅并遵守相关第三方的使用条款和隐私政策。 + +4. **强制性法律权利不受影响** + + 某些司法管辖区不允许排除特定担保或限制对某些损害的责任,上述免责声明和责任限制条款在此类司法管辖区内可能部分或全部不适用。 + 在该等情况下,本条款仅在适用法律允许的最大范围内适用,并且不影响您根据当地强制性法律享有的任何权利。 + +### 七、协议的更新与生效 + +1. **更新** + + 我们保留在必要时对本协议进行修改、更新或补充的权利。这些修改可能出于以下原因(包括但不限于): + + - 适应适用法律法规、监管政策的变化; + - 反映本软件功能、服务模式或技术架构的调整; + - 提升用户体验、安全性或合规水平。 + +2. **通知方式** + + 当本协议发生实质性变更时,我们将通过以下一种或多种合理方式向您提示更新内容: + + - 在软件启动时通过弹窗或提示信息展示; + - 在官方网站或项目主页(例如 GitHub 仓库)发布公告。 + +3. **接受与拒绝** + + - 自更新版本发布或通知中载明的生效日起,**您继续下载、安装或使用本软件,即视为您已充分阅读、理解并接受更新后的协议内容**; + - 如您不同意更新后的协议内容,您应当立即停止使用本软件并卸载已安装的版本。 + +> 为便于您了解最新条款,建议您不时查阅本协议所在页面或官方仓库中的相关文件。 + +### 八、适用法律与争议解决 + +1. **适用法律** + 本条款的订立、执行、解释及争议的解决,均适用中华人民共和国(为本条款之目的,不包括香港、澳门特别行政区及台湾地区)法律。若中华人民共和国法律未作规定或规定不明,则参照商业惯例或行业通用规则。 + +2. **当地法律的例外(消费者保护)** + 尽管有上述约定,如果您是欧盟、英国、美国或其他司法管辖区的消费者,且当地法律明确规定了不可通过合同排除的强制性消费者保护权利(例如必须在消费者居住地起诉),则本条款中的管辖约定不应剥夺您根据当地强制性法律享有的权利。在此情况下,本条款的其他部分仍具有完全的法律效力。 + +3. **语言** + 本条款可能以多种语言发布。若中文版本与其他语言版本存在冲突或歧义,在适用法律允许的范围内,以简体中文版本为准。 + +### 九、联系我们 + +如您对本协议、本软件的使用、开源许可、隐私保护或其他相关事宜有任何疑问、意见或建议,欢迎通过以下方式与我们联系: + +- **项目主页(GitHub):** +- **联系邮箱:** + - alan-crl@foxmail.com + - alancrl1007@gmail.com + +我们将尽合理努力在能力和资源允许的范围内,尽快回复您的合理咨询或反馈,但不保证对所有来信逐一答复。 \ No newline at end of file diff --git a/ThirdpartyLicenses/Apache License 2.0 b/ThirdpartyLicenses/Apache License 2.0 new file mode 100644 index 00000000..62589edd --- /dev/null +++ b/ThirdpartyLicenses/Apache License 2.0 @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ThirdpartyLicenses/MIT License b/ThirdpartyLicenses/MIT License new file mode 100644 index 00000000..6802bc4b --- /dev/null +++ b/ThirdpartyLicenses/MIT License @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/ThirdpartyLicenses/concurrentqueue License b/ThirdpartyLicenses/concurrentqueue License new file mode 100644 index 00000000..454c9656 --- /dev/null +++ b/ThirdpartyLicenses/concurrentqueue License @@ -0,0 +1,19 @@ +Simplified BSD License: + +Copyright (c) 2013-2016, Cameron Desrochers. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +----- + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/ThirdpartyLicenses/hashlibpp License b/ThirdpartyLicenses/hashlibpp License new file mode 100644 index 00000000..d30a8781 --- /dev/null +++ b/ThirdpartyLicenses/hashlibpp License @@ -0,0 +1,25 @@ +hashlib++ - a simple hash library for C++ + +Copyright (c) 2007-2011 Benjamin Gr¸delbach + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1) Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2) Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/ThirdpartyLicenses/libcuckoo License b/ThirdpartyLicenses/libcuckoo License new file mode 100644 index 00000000..1f90543f --- /dev/null +++ b/ThirdpartyLicenses/libcuckoo License @@ -0,0 +1,13 @@ +Copyright (C) 2013, Carnegie Mellon University and Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231.sln" "b/\346\231\272\347\273\230\346\225\231.sln" index 1abd94e8..40909a87 100644 --- "a/\346\231\272\347\273\230\346\225\231.sln" +++ "b/\346\231\272\347\273\230\346\225\231.sln" @@ -12,17 +12,32 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PptCOM", "PptCOM\PptCOM.csp EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 Release|ARM64 = Release|ARM64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|ARM64.Build.0 = Debug|ARM64 + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|Win32.ActiveCfg = Debug|Win32 + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|Win32.Build.0 = Debug|Win32 + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|x64.ActiveCfg = Debug|x64 + {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Debug|x64.Build.0 = Debug|x64 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|ARM64.ActiveCfg = Release|ARM64 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|ARM64.Build.0 = Release|ARM64 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|Win32.ActiveCfg = Release|Win32 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|Win32.Build.0 = Release|Win32 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|x64.ActiveCfg = Release|x64 {C74D4D04-F81C-42FE-8D90-7042BA3BB8A0}.Release|x64.Build.0 = Release|x64 + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|ARM64.ActiveCfg = Release|Any CPU + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|ARM64.Build.0 = Release|Any CPU + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|Win32.ActiveCfg = Release|Any CPU + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|Win32.Build.0 = Release|Any CPU + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|x64.ActiveCfg = Release|Any CPU + {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Debug|x64.Build.0 = Release|Any CPU {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Release|ARM64.ActiveCfg = Release|Any CPU {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Release|ARM64.Build.0 = Release|Any CPU {A7B02228-179F-4B8E-BA8F-82D50066FB66}.Release|Win32.ActiveCfg = Release|Any CPU diff --git "a/\346\231\272\347\273\230\346\225\231/CrashHandler/CrashHandler.cpp" "b/\346\231\272\347\273\230\346\225\231/CrashHandler/CrashHandler.cpp" index fdc51f2a..ee47d8cb 100644 --- "a/\346\231\272\347\273\230\346\225\231/CrashHandler/CrashHandler.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/CrashHandler/CrashHandler.cpp" @@ -146,8 +146,8 @@ LONG WINAPI CrashHandler::UnhandledExceptionHandler(EXCEPTION_POINTERS* pExcepti if (currentUserStateFlag == 0) { - MessageBoxW(NULL, L"There is a problem with 智绘教Inkeys, click OK to restart 智绘教Inkeys to try to resolve the problem.\n智绘教Inkeys 出现问题,点击确定重启 智绘教Inkeys 以尝试解决问题。", L"Inkeys Error | 智绘教错误", MB_OK | MB_ICONERROR); - ShellExecuteW(NULL, NULL, exeDir.wstring().c_str(), L"-CrashTry", NULL, SW_SHOWNORMAL); + auto id = MessageBoxW(NULL, L"There is a problem with 智绘教Inkeys, click OK to restart 智绘教Inkeys to try to resolve the problem.\n智绘教Inkeys 出现问题,点击确定重启 智绘教Inkeys 以尝试解决问题。", L"Inkeys Error | 智绘教错误", MB_OK | MB_ICONERROR); + if (id == IDOK) ShellExecuteW(NULL, NULL, exeDir.wstring().c_str(), L"-CrashTry", NULL, SW_SHOWNORMAL); } else if (currentUserStateFlag == 1) ShellExecuteW(NULL, NULL, exeDir.wstring().c_str(), L"-CrashTry", NULL, SW_SHOWNORMAL); diff --git "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.cpp" "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.cpp" index 742730e7..66c038bf 100644 --- "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.cpp" @@ -1284,6 +1284,15 @@ namespace HiEasyX break; } + // 调用用户消息处理函数 + if (g_vecWindows[indexWnd].funcWndProc) + { + resultUserProc = g_vecWindows[indexWnd].funcWndProc(hWnd, msg, wParam, lParam); + + // Inkeys 修改:如果返回值为 0,则证明需要立即停止传递。(例如是触控 WM_TOUCH 消息)用户需要阻止消息传递 + if (resultUserProc == 0) return 0; + } + // 活窗口的一般事件处理 if (IsAliveWindow(indexWnd)) { @@ -1297,12 +1306,6 @@ namespace HiEasyX return lrSysCtrl; } - // 调用用户消息处理函数 - if (g_vecWindows[indexWnd].funcWndProc) - { - resultUserProc = g_vecWindows[indexWnd].funcWndProc(hWnd, msg, wParam, lParam); - } - // 善后工作 switch (msg) { @@ -1492,7 +1495,7 @@ namespace HiEasyX } // 真正创建窗口的函数(阻塞) - void InitWindow(int w, int h, int flag, LPCTSTR lpszWndTitle, LPCTSTR lpszClassName, WNDPROC WindowProcess, HWND hParent, int* nDoneFlag, bool* nStartAnimation, HWND* hWnd) + void InitWindow(int w, int h, int flag, LPCTSTR lpszWndTitle, LPCTSTR lpszClassName, WNDPROC WindowProcess, HWND hParent, int* nDoneFlag, bool* nStartAnimation, HWND* hWnd, std::function fuc = nullptr) { static int nWndCount = 0; // 已创建窗口计数(用于生成窗口标题) @@ -1573,17 +1576,11 @@ namespace HiEasyX // 获取系统任务栏自定义的消息代码 g_uWM_TASKBARCREATED = RegisterWindowMessage(TEXT("TaskbarCreated")); -#ifndef _DEBUG -#ifndef __DEBUG__ -#ifndef DEBUG #ifndef _NO_START_ANIMATION_ if (!(isPreShowState && nPreCmdShow == SW_HIDE) && w >= 640 && h >= 480) start_animation = true; -#endif -#endif -#endif #endif } @@ -1748,6 +1745,10 @@ namespace HiEasyX }).detach(); } + // Inkeys + // 窗口创建完成后可能需要执行自定义操作 + if (fuc) fuc(wnd.hWnd); + // 消息派发,阻塞 // 窗口销毁后会自动退出 MSG Msg; @@ -1758,7 +1759,7 @@ namespace HiEasyX } } - HWND initgraph_win32(int w, int h, int flag, LPCTSTR lpszWndTitle, LPCTSTR lpszClassName, WNDPROC WindowProcess, HWND hParent) + HWND initgraph_win32(int w, int h, int flag, LPCTSTR lpszWndTitle, LPCTSTR lpszClassName, WNDPROC WindowProcess, HWND hParent, std::function fuc) { // 标记是否已经完成窗口创建任务 int nDoneFlag = 0; @@ -1773,7 +1774,7 @@ namespace HiEasyX //EnableWindow(hParent, false); } - std::thread(InitWindow, w, h, flag, lpszWndTitle, lpszClassName, WindowProcess, hParent, &nDoneFlag, &nStartAnimation, &hWnd).detach(); + std::thread(InitWindow, w, h, flag, lpszWndTitle, lpszClassName, WindowProcess, hParent, &nDoneFlag, &nStartAnimation, &hWnd, fuc).detach(); while (nDoneFlag == 0) Sleep(50); // 等待窗口创建完成 if (nDoneFlag == -1) diff --git "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.h" "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.h" index 8b8b9d3c..e56bc923 100644 --- "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.h" +++ "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiWindow.h" @@ -16,6 +16,7 @@ #include #include +#include #ifdef _MSC_VER #pragma comment (lib, "Msimg32.lib") @@ -352,7 +353,8 @@ namespace HiEasyX LPCTSTR lpszWndTitle = _T(""), LPCTSTR lpszClassName = _T(""), WNDPROC WindowProcess = nullptr, - HWND hParent = nullptr + HWND hParent = nullptr, + std::function fuc = nullptr ); bool init_console(); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" index 5d613a75..f1e4b361 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" @@ -154,6 +154,8 @@ bool ReadSetting() if (setlistVal.isMember("PaintDevice") && setlistVal["PaintDevice"].isInt()) setlist.paintDevice = setlistVal["PaintDevice"].asInt(); + if (setlistVal.isMember("DisableRTS") && setlistVal["DisableRTS"].isBool()) + setlist.disableRTS = setlistVal["DisableRTS"].asBool(); if (setlistVal.isMember("LiftStraighten") && setlistVal["LiftStraighten"].isBool()) setlist.liftStraighten = setlistVal["LiftStraighten"].asBool(); if (setlistVal.isMember("WaitStraighten") && setlistVal["WaitStraighten"].isBool()) @@ -304,6 +306,16 @@ bool ReadSetting() if (setlistVal["Component"]["ShortcutButton"]["Keyboard"].isMember("KeyboardAltF4") && setlistVal["Component"]["ShortcutButton"]["Keyboard"]["KeyboardAltF4"].isBool()) setlist.component.shortcutButton.keyboard.keyboardAltF4 = setlistVal["Component"]["ShortcutButton"]["Keyboard"]["KeyboardAltF4"].asBool(); } + // rollCall + if (setlistVal["Component"]["ShortcutButton"].isMember("RollCall") && setlistVal["Component"]["ShortcutButton"]["RollCall"].isObject()) + { + if (setlistVal["Component"]["ShortcutButton"]["RollCall"].isMember("IslandCaller") && setlistVal["Component"]["ShortcutButton"]["RollCall"]["IslandCaller"].isBool()) + setlist.component.shortcutButton.rollCall.IslandCaller = setlistVal["Component"]["ShortcutButton"]["RollCall"]["IslandCaller"].asBool(); + if (setlistVal["Component"]["ShortcutButton"]["RollCall"].isMember("SecRandom") && setlistVal["Component"]["ShortcutButton"]["RollCall"]["SecRandom"].isBool()) + setlist.component.shortcutButton.rollCall.SecRandom = setlistVal["Component"]["ShortcutButton"]["RollCall"]["SecRandom"].asBool(); + if (setlistVal["Component"]["ShortcutButton"]["RollCall"].isMember("NamePicker") && setlistVal["Component"]["ShortcutButton"]["RollCall"]["NamePicker"].isBool()) + setlist.component.shortcutButton.rollCall.NamePicker = setlistVal["Component"]["ShortcutButton"]["RollCall"]["NamePicker"].asBool(); + } // linkage if (setlistVal["Component"]["ShortcutButton"].isMember("Linkage") && setlistVal["Component"]["ShortcutButton"]["Linkage"].isObject()) { @@ -313,8 +325,6 @@ bool ReadSetting() setlist.component.shortcutButton.linkage.classislandProfile = setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandProfile"].asBool(); if (setlistVal["Component"]["ShortcutButton"]["Linkage"].isMember("ClassislandClassswap") && setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandClassswap"].isBool()) setlist.component.shortcutButton.linkage.classislandClassswap = setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandClassswap"].asBool(); - if (setlistVal["Component"]["ShortcutButton"]["Linkage"].isMember("ClassislandIslandCaller") && setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandIslandCaller"].isBool()) - setlist.component.shortcutButton.linkage.classislandIslandCaller = setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandIslandCaller"].asBool(); } } } @@ -404,6 +414,7 @@ bool WriteSetting() setlistVal["Regular"]["TeachingSafetyMode"] = Json::Value(setlist.regularSetting.teachingSafetyMode); setlistVal["PaintDevice"] = Json::Value(setlist.paintDevice); + setlistVal["DisableRTS"] = Json::Value(setlist.disableRTS); setlistVal["LiftStraighten"] = Json::Value(setlist.liftStraighten); setlistVal["WaitStraighten"] = Json::Value(setlist.waitStraighten); setlistVal["PointAdsorption"] = Json::Value(setlist.pointAdsorption); @@ -493,12 +504,17 @@ bool WriteSetting() setlistVal["Component"]["ShortcutButton"]["Keyboard"]["Keyboardesc"] = Json::Value(setlist.component.shortcutButton.keyboard.keyboardesc); setlistVal["Component"]["ShortcutButton"]["Keyboard"]["KeyboardAltF4"] = Json::Value(setlist.component.shortcutButton.keyboard.keyboardAltF4); } + // rollCall + { + setlistVal["Component"]["ShortcutButton"]["RollCall"]["IslandCaller"] = Json::Value(setlist.component.shortcutButton.rollCall.IslandCaller); + setlistVal["Component"]["ShortcutButton"]["RollCall"]["SecRandom"] = Json::Value(setlist.component.shortcutButton.rollCall.SecRandom); + setlistVal["Component"]["ShortcutButton"]["RollCall"]["NamePicker"] = Json::Value(setlist.component.shortcutButton.rollCall.NamePicker); + } // linkage { setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandSettings"] = Json::Value(setlist.component.shortcutButton.linkage.classislandSettings); setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandProfile"] = Json::Value(setlist.component.shortcutButton.linkage.classislandProfile); setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandClassswap"] = Json::Value(setlist.component.shortcutButton.linkage.classislandClassswap); - setlistVal["Component"]["ShortcutButton"]["Linkage"]["ClassislandIslandCaller"] = Json::Value(setlist.component.shortcutButton.linkage.classislandIslandCaller); } } } @@ -613,8 +629,8 @@ bool PptComReadSetting() if (updateVal.isMember("MiddleSideBothWidgetScale") && updateVal["MiddleSideBothWidgetScale"].isDouble()) pptComSetlist.middleSideBothWidgetScale = (float)updateVal["MiddleSideBothWidgetScale"].asDouble(); - if (updateVal.isMember("AutoKillWpsProcess") && updateVal["AutoKillWpsProcess"].isBool()) - pptComSetlist.autoKillWpsProcess = updateVal["AutoKillWpsProcess"].asBool(); + // if (updateVal.isMember("AutoKillWpsProcess") && updateVal["AutoKillWpsProcess"].isBool()) + // pptComSetlist.autoKillWpsProcess = updateVal["AutoKillWpsProcess"].asBool(); } else return false; @@ -701,7 +717,7 @@ bool PptComWriteSetting() updateVal["BottomSideMiddleWidgetScale"] = Json::Value(pptComSetlist.bottomSideMiddleWidgetScale); updateVal["MiddleSideBothWidgetScale"] = Json::Value(pptComSetlist.middleSideBothWidgetScale); - updateVal["AutoKillWpsProcess"] = Json::Value(pptComSetlist.autoKillWpsProcess); + //updateVal["AutoKillWpsProcess"] = Json::Value(pptComSetlist.autoKillWpsProcess); } HANDLE fileHandle = NULL; @@ -739,7 +755,7 @@ DdbInteractionSetListStruct ddbInteractionSetList; //bool DdbReadInteraction() //{ // HANDLE fileHandle = NULL; -// if (!OccupyFileForRead(&fileHandle, dataPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json")) +// if (!OccupyFileForRead(&fileHandle, pluginPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json")) // { // UnOccupyFile(&fileHandle); // return false; @@ -874,6 +890,8 @@ bool DdbWriteInteraction(bool change, bool close) updateVal["Intercept"]["IntelligentClassFloating"] = Json::Value(ddbInteractionSetList.intercept.intelligentClassFloating); updateVal["Intercept"]["SeewoDesktopAnnotationFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating); updateVal["Intercept"]["SeewoDesktopSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopSideBarFloating); + updateVal["Intercept"]["Iclass30Floating"] = Json::Value(ddbInteractionSetList.intercept.iclass30Floating); + updateVal["Intercept"]["Iclass30SidebarFloating"] = Json::Value(ddbInteractionSetList.intercept.iclass30SidebarFloating); } updateVal["~ConfigurationChange"] = Json::Value(change); @@ -881,7 +899,7 @@ bool DdbWriteInteraction(bool change, bool close) } HANDLE fileHandle = NULL; - if (!OccupyFileForWrite(&fileHandle, dataPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json")) + if (!OccupyFileForWrite(&fileHandle, pluginPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json")) { UnOccupyFile(&fileHandle); return false; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" index 32d9cf07..06a98a0b 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" @@ -47,6 +47,7 @@ struct SetListStruct #pragma region 绘制 int paintDevice; + IdtAtomic disableRTS; bool liftStraighten, waitStraighten; bool pointAdsorption; @@ -117,12 +118,17 @@ struct SetListStruct IdtAtomic keyboardAltF4; } keyboard; struct + { + IdtAtomic IslandCaller; + IdtAtomic SecRandom; + IdtAtomic NamePicker; + }rollCall; + struct { // 联动 IdtAtomic classislandSettings; IdtAtomic classislandProfile; IdtAtomic classislandClassswap; - IdtAtomic classislandIslandCaller; } linkage; } shortcutButton; }component; @@ -172,7 +178,7 @@ struct PptComSetListStruct bottomSideMiddleWidgetScale = 1.0f; middleSideBothWidgetScale = 1.0f; - autoKillWpsProcess = true; + // autoKillWpsProcess = true; // 附加信息项 setAdmin = false; @@ -204,7 +210,7 @@ struct PptComSetListStruct float middleSideBothWidgetScale; // 自动结束未正确关闭的 WPP 进程 - bool autoKillWpsProcess; + // bool autoKillWpsProcess; // 附加信息项 bool setAdmin; @@ -221,8 +227,8 @@ struct DdbInteractionSetListStruct enable = false; runAsAdmin = false; - DdbEdition = L"20250404a"; - DdbSHA256 = "e0ce42c45c8287ae34129625cb64e9054b6abedea08dfa1dfccea564ec36da30"; + DdbEdition = L"20251128a"; + DdbSHA256 = "ffbfdb4ffb6f720ba19f7f042e7e3eb274b234764ad2e78247367ee470c65d05"; // ----- @@ -247,6 +253,8 @@ struct DdbInteractionSetListStruct intercept.intelligentClassFloating = true; intercept.seewoDesktopAnnotationFloating = true; intercept.seewoDesktopSideBarFloating = false; + intercept.iclass30Floating = true; + intercept.iclass30SidebarFloating = false; } bool enable; @@ -280,6 +288,8 @@ struct DdbInteractionSetListStruct bool intelligentClassFloating; bool seewoDesktopAnnotationFloating; bool seewoDesktopSideBarFloating; + bool iclass30Floating; + bool iclass30SidebarFloating; } intercept; }; extern DdbInteractionSetListStruct ddbInteractionSetList; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.cpp" index 6892126a..8bb79eb7 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.cpp" @@ -1,15 +1,17 @@ #include "IdtD2DPreparation.h" -ID2D1Factory* D2DFactory = nullptr; +CComPtr D2DFactory; D2D1_RENDER_TARGET_PROPERTIES D2DProperty; -IDWriteFactory* D2DTextFactory = nullptr; -IDWriteFontCollection* D2DFontCollection = nullptr; +CComPtr D2DTextFactory; +CComPtr D2DFontCollection; void D2DStarup() { // 创建 D2D 工厂 - D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &D2DFactory); + ID2D1Factory* tmpFactory = nullptr; + D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &tmpFactory); + D2DFactory.Attach(tmpFactory); // 创建 DC Render 并指定软件加速(因为比硬件加速快,不知道为啥,现在知道了qaq) D2DProperty = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE::D2D1_RENDER_TARGET_TYPE_SOFTWARE, @@ -20,7 +22,9 @@ void D2DStarup() ); // 创建 D2D 文字工厂 - DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&D2DTextFactory)); + IDWriteFactory* tmpWriteFactory = nullptr; + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&tmpWriteFactory)); + D2DTextFactory.Attach(tmpWriteFactory); } void D2DShutdown() { diff --git "a/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.h" "b/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.h" index a1cdc12c..bc2265ac 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtD2DPreparation.h" @@ -7,293 +7,11 @@ #pragma comment(lib, "d2d1.lib") #pragma comment(lib, "dwrite.lib") -extern ID2D1Factory* D2DFactory; +extern CComPtr D2DFactory; extern D2D1_RENDER_TARGET_PROPERTIES D2DProperty; -extern IDWriteFactory* D2DTextFactory; -extern IDWriteFontCollection* D2DFontCollection; - -/* -* Dwrite win7 支持版本不能从内存中加载字体,故只能从本地字体文件加载 -* -class IdtFontFileStream :public IDWriteFontFileStream -{ -public: - // IDWriteFontFileLoader methods - STDMETHOD(GetFileSize)(UINT64* fileSize) override - { - //Testi(1); - - *fileSize = m_collectionKeySize; - - return S_OK; - } - STDMETHOD(GetLastWriteTime)(UINT64* lastWriteTime) override - { - //Testi(2); - - *lastWriteTime = 0; - - return S_OK; - } - STDMETHOD(ReadFileFragment)(void const** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize, void** fragmentContext) override - { - //Testi(3); - - //Testi(fileOffset); - //Testi(fragmentSize); - - void const* offsetAddress = reinterpret_cast(reinterpret_cast(m_collectionKey) + fileOffset); - - vector* fontData = new vector(fragmentSize); - memcpy(fontData->data(), offsetAddress, fragmentSize); - - //Testw(to_wstring((*fontData)[0]) + L" " + to_wstring((*fontData)[1]) + L" " + to_wstring((*fontData)[2]) + L" " + to_wstring((*fontData)[3]) + L" " + to_wstring((*fontData)[4]) + L" " + to_wstring((*fontData)[5]) + L" " + to_wstring((*fontData)[6]) + L" " + to_wstring((*fontData)[7])); - - *fragmentStart = fontData->data(); - *fragmentContext = fontData; - - return S_OK; - } - void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext) override - { - //Testi(4); - - delete fragmentContext; - - return; - } - - // Idt methods - STDMETHOD(SetFont)(void const* collectionKey, UINT32 collectionKeySize) - { - //Testi(5); - - m_collectionKey = collectionKey; - m_collectionKeySize = collectionKeySize; - - return S_OK; - } - - // IUnknown methods - STDMETHOD_(ULONG, AddRef)() - { - return InterlockedIncrement(&m_cRefCount); - } - STDMETHOD_(ULONG, Release)() - { - ULONG cNewRefCount = InterlockedDecrement(&m_cRefCount); - if (cNewRefCount == 0) - { - delete this; - } - return cNewRefCount; - } - STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) - { - if ((riid == IID_IStylusSyncPlugin) || (riid == IID_IUnknown)) - { - *ppvObj = this; - AddRef(); - return S_OK; - } - else if ((riid == IID_IMarshal) && (m_punkFTMarshaller != NULL)) - { - return m_punkFTMarshaller->QueryInterface(riid, ppvObj); - } - - *ppvObj = NULL; - return E_NOINTERFACE; - } - -private: - void const* m_collectionKey; - UINT32 m_collectionKeySize; - - LONG m_cRefCount; - IUnknown* m_punkFTMarshaller; -}; -class IdtFontFileLoader :public IDWriteFontFileLoader -{ -public: - // IDWriteFontFileLoader methods - STDMETHOD(CreateStreamFromKey)(void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, IDWriteFontFileStream** fontFileStream) override - { - //Testi(6); - - IdtFontFileStream* D2DFontFileStream = new IdtFontFileStream; - D2DFontFileStream->SetFont(fontFileReferenceKey, fontFileReferenceKeySize); - - *fontFileStream = D2DFontFileStream; - - return S_OK; - } - - // IUnknown methods - STDMETHOD_(ULONG, AddRef)() - { - return InterlockedIncrement(&m_cRefCount); - } - STDMETHOD_(ULONG, Release)() - { - ULONG cNewRefCount = InterlockedDecrement(&m_cRefCount); - if (cNewRefCount == 0) - { - delete this; - } - return cNewRefCount; - } - STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) - { - if ((riid == IID_IStylusSyncPlugin) || (riid == IID_IUnknown)) - { - *ppvObj = this; - AddRef(); - return S_OK; - } - else if ((riid == IID_IMarshal) && (m_punkFTMarshaller != NULL)) - { - return m_punkFTMarshaller->QueryInterface(riid, ppvObj); - } - - *ppvObj = NULL; - return E_NOINTERFACE; - } - -private: - LONG m_cRefCount; - IUnknown* m_punkFTMarshaller; -}; -*/ -class IdtFontFileEnumerator : public IDWriteFontFileEnumerator -{ -public: - - // IDWriteFontFileEnumerator methods - STDMETHOD(GetCurrentFontFile)(IDWriteFontFile** fontFile) override - { - *fontFile = m_font[m_currentfontCount - 1]; - - return S_OK; - } - STDMETHOD(MoveNext)(BOOL* hasCurrentFile) override - { - m_currentfontCount++; - *hasCurrentFile = m_currentfontCount > (int)m_font.size() ? FALSE : TRUE; - - return S_OK; - } - - // Idt methods - STDMETHOD(AddFont)(IDWriteFactory* factory, wstring fontPath) - { - IDWriteFontFile* D2DFont = nullptr; - - // 文件导入方案 - factory->CreateFontFileReference(fontPath.c_str(), 0, &D2DFont); - - m_font.push_back(D2DFont); - - return S_OK; - } - - // IUnknown methods - STDMETHOD_(ULONG, AddRef)() - { - return InterlockedIncrement(&m_cRefCount); - } - STDMETHOD_(ULONG, Release)() - { - ULONG cNewRefCount = InterlockedDecrement(&m_cRefCount); - if (cNewRefCount == 0) - { - delete this; - } - return cNewRefCount; - } - STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) - { - if ((riid == IID_IStylusSyncPlugin) || (riid == IID_IUnknown)) - { - *ppvObj = this; - AddRef(); - return S_OK; - } - else if ((riid == IID_IMarshal) && (m_punkFTMarshaller != NULL)) - { - return m_punkFTMarshaller->QueryInterface(riid, ppvObj); - } - - *ppvObj = NULL; - return E_NOINTERFACE; - } - -private: - int m_currentfontCount = 0; - IDWriteFactory* m_D2DTextFactory = nullptr; - - vector m_font; - - LONG m_cRefCount; - IUnknown* m_punkFTMarshaller; -}; -class IdtFontCollectionLoader : public IDWriteFontCollectionLoader -{ -public: - - // IDWriteFontCollectionLoader methods - STDMETHOD(CreateEnumeratorFromKey)(IDWriteFactory* factory, void const* /*collectionKey*/, UINT32 /*collectionKeySize*/, IDWriteFontFileEnumerator** fontFileEnumerator) override - { - *fontFileEnumerator = D2DFontFileEnumerator; - - return S_OK; - } - - // Idt methods - STDMETHOD(AddFont)(IDWriteFactory* factory, wstring fontPath) - { - D2DFontFileEnumerator->AddFont(factory, fontPath); - - return S_OK; - } - - // IUnknown methods - STDMETHOD_(ULONG, AddRef)() - { - return InterlockedIncrement(&m_cRefCount); - } - STDMETHOD_(ULONG, Release)() - { - ULONG cNewRefCount = InterlockedDecrement(&m_cRefCount); - if (cNewRefCount == 0) - { - delete this; - } - return cNewRefCount; - } - STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObj) - { - if ((riid == IID_IStylusSyncPlugin) || (riid == IID_IUnknown)) - { - *ppvObj = this; - AddRef(); - return S_OK; - } - else if ((riid == IID_IMarshal) && (m_punkFTMarshaller != NULL)) - { - return m_punkFTMarshaller->QueryInterface(riid, ppvObj); - } - - *ppvObj = NULL; - return E_NOINTERFACE; - } - -private: - IdtFontFileEnumerator* D2DFontFileEnumerator = new IdtFontFileEnumerator; - - LONG m_cRefCount; - IUnknown* m_punkFTMarshaller; -}; +extern CComPtr D2DTextFactory; +extern CComPtr D2DFontCollection; template void DxObjectSafeRelease(T** ppT) { diff --git "a/\346\231\272\347\273\230\346\225\231/IdtDisplayManagement.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtDisplayManagement.cpp" index 86be124f..fa3d1ec0 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtDisplayManagement.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtDisplayManagement.cpp" @@ -54,22 +54,41 @@ BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMoni int displayOrientation = 0; wstring strModel, strDriver; - DISPLAY_DEVICE ddMonitorTmp; - ddMonitorTmp.cb = sizeof(DISPLAY_DEVICE); - DWORD devIndex = 0; + // 通过下面的代码获取到显示器的 PnP ID wstring deviceId; - while (EnumDisplayDevices(MonitorInfo.szDevice, devIndex, &ddMonitorTmp, 0)) { - if ((ddMonitorTmp.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE && - (ddMonitorTmp.StateFlags & DISPLAY_DEVICE_ATTACHED) == DISPLAY_DEVICE_ATTACHED) + DISPLAY_DEVICEW ddAdapter; + ddAdapter.cb = sizeof(DISPLAY_DEVICEW); + for (DWORD adapIdx = 0; EnumDisplayDevicesW(nullptr, adapIdx, &ddAdapter, 0); adapIdx++) { - deviceId = ddMonitorTmp.DeviceID; - break; + //Testw(L"cmp1 " + wstring(ddAdapter.DeviceName) + L" : " + wstring(MonitorInfo.szDevice)); + //Testw(L"cmp1t " + wstring(ddAdapter.DeviceID)); + + DISPLAY_DEVICEW ddMonitor; + ddMonitor.cb = sizeof(DISPLAY_DEVICEW); + + for (DWORD monIdx = 0; EnumDisplayDevicesW(ddAdapter.DeviceName, monIdx, &ddMonitor, 0); monIdx++) + { + //Testw(L"cmp2 " + wstring(ddMonitor.DeviceName) + L" : " + wstring(MonitorInfo.szDevice)); + //Testw(L"cmp2t " + wstring(ddMonitor.DeviceID)); + + if ((ddMonitor.StateFlags & DISPLAY_DEVICE_ACTIVE) == DISPLAY_DEVICE_ACTIVE && + (ddMonitor.StateFlags & DISPLAY_DEVICE_ATTACHED) == DISPLAY_DEVICE_ATTACHED && + _wcsnicmp(ddMonitor.DeviceName, MonitorInfo.szDevice, wcslen(MonitorInfo.szDevice)) == 0) + { + deviceId = ddMonitor.DeviceID; + } + + if (!deviceId.empty()) break; + } + if (!deviceId.empty()) break; } } if (!deviceId.empty() && IdtGetModelDriverFromDeviceID(deviceId.c_str(), strModel, strDriver)) { + //Testw(deviceId + L": " + strModel + L" " + strDriver); + // 获取 EDID 数据 BYTE EDIDBuf[32]; // 我们只需要获取 18~22 的值,后面的可以截断 DWORD dwRealGetBytes = 0; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" index 772f15e2..0adffbc8 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" @@ -14,6 +14,7 @@ #include "IdtTime.h" #include "IdtUpdate.h" #include "IdtWindow.h" +#include "Inkeys/Other/IdtInputs.h" #include @@ -39,7 +40,6 @@ shared_mutex drawWaitingSm; IMAGE drawpad(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); //主画板 IMAGE window_background(GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); -unordered_map KeyBoradDown; HHOOK DrawpadHookCall; bool IsHotkeyDown; LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) @@ -48,17 +48,17 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) { KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam; - if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) KeyBoradDown[(BYTE)pKeyInfo->vkCode] = true; - else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) KeyBoradDown[(BYTE)pKeyInfo->vkCode] = false; + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) IdtInputs::SetKeyBoardDown((BYTE)pKeyInfo->vkCode, true); + else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) IdtInputs::SetKeyBoardDown((BYTE)pKeyInfo->vkCode, false); - if (!IsHotkeyDown && (KeyBoradDown[VK_CONTROL] || KeyBoradDown[VK_LCONTROL] || KeyBoradDown[VK_RCONTROL]) && (KeyBoradDown[VK_LWIN] || KeyBoradDown[VK_RWIN]) && (KeyBoradDown[VK_MENU] || KeyBoradDown[VK_LMENU] || KeyBoradDown[VK_RMENU])) + if (!IsHotkeyDown && (IdtInputs::IsKeyBoardDown(VK_CONTROL) || IdtInputs::IsKeyBoardDown(VK_LCONTROL) || IdtInputs::IsKeyBoardDown(VK_RCONTROL)) && (IdtInputs::IsKeyBoardDown(VK_LWIN) || IdtInputs::IsKeyBoardDown(VK_RWIN)) && (IdtInputs::IsKeyBoardDown(VK_MENU) || IdtInputs::IsKeyBoardDown(VK_LMENU) || IdtInputs::IsKeyBoardDown(VK_RMENU))) { IsHotkeyDown = true; if (stateMode.StateModeSelect == StateModeSelectEnum::IdtSelection) ChangeStateModeToPen(); else ChangeStateModeToSelection(); } - else if (IsHotkeyDown && !(KeyBoradDown[VK_CONTROL] || KeyBoradDown[VK_LCONTROL] || KeyBoradDown[VK_RCONTROL]) && !(KeyBoradDown[VK_LWIN] || KeyBoradDown[VK_RWIN]) && !(KeyBoradDown[VK_MENU] || KeyBoradDown[VK_LMENU] || KeyBoradDown[VK_RMENU])) IsHotkeyDown = false; + else if (IsHotkeyDown && !(IdtInputs::IsKeyBoardDown(VK_CONTROL) || IdtInputs::IsKeyBoardDown(VK_LCONTROL) || IdtInputs::IsKeyBoardDown(VK_RCONTROL)) && !(IdtInputs::IsKeyBoardDown(VK_LWIN) || IdtInputs::IsKeyBoardDown(VK_RWIN)) && !(IdtInputs::IsKeyBoardDown(VK_MENU) || IdtInputs::IsKeyBoardDown(VK_LMENU) || IdtInputs::IsKeyBoardDown(VK_RMENU))) IsHotkeyDown = false; // 全局状态变量 bool checkEndShowIsChecking = CheckEndShow.isChecking; @@ -75,7 +75,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) case VK_DOWN: // 下箭头 case VK_RETURN: // Enter { - if (KeyBoradDown[(BYTE)pKeyInfo->vkCode]) + if (IdtInputs::IsKeyBoardDown((BYTE)pKeyInfo->vkCode)) { pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -90,7 +90,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) case VK_UP: // 上箭头 case VK_BACK: // Backsapce { - if (KeyBoradDown[(BYTE)pKeyInfo->vkCode]) + if (IdtInputs::IsKeyBoardDown((BYTE)pKeyInfo->vkCode)) { pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -114,7 +114,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) msgKey.vkcode = (BYTE)pKeyInfo->vkCode; // 借用结构:是否按下 ctrl - msgKey.prevdown = (KeyBoradDown[VK_CONTROL] || KeyBoradDown[VK_LCONTROL] || KeyBoradDown[VK_RCONTROL]); + msgKey.prevdown = (IdtInputs::IsKeyBoardDown(VK_CONTROL) || IdtInputs::IsKeyBoardDown(VK_LCONTROL) || IdtInputs::IsKeyBoardDown(VK_RCONTROL)); int index = hiex::GetWindowIndex(drawpad_window, false); unique_lock lg_vecWindows_vecMessage_sm(hiex::g_vecWindows_vecMessage_sm[index]); @@ -218,7 +218,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) } */ // 穿透所需的额外情况(穿透模式下禁用 Ctrl + E,用于关闭穿透) - else if (penetrate.select && (KeyBoradDown[VK_CONTROL] || KeyBoradDown[VK_LCONTROL] || KeyBoradDown[VK_RCONTROL]) && (BYTE)pKeyInfo->vkCode == (BYTE)0x45) + else if (penetrate.select && (IdtInputs::IsKeyBoardDown(VK_CONTROL) || IdtInputs::IsKeyBoardDown(VK_LCONTROL) || IdtInputs::IsKeyBoardDown(VK_RCONTROL)) && (BYTE)pKeyInfo->vkCode == (BYTE)0x45) { ExMessage msgKey = {}; msgKey.message = wParam; @@ -276,7 +276,8 @@ void KeyboardInteraction() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[vkcode]) break; + if (!IdtInputs::IsKeyBoardDown(vkcode)) break; + if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) PreviousPptSlides(); this_thread::sleep_for(chrono::milliseconds(15)); @@ -307,7 +308,7 @@ void KeyboardInteraction() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[vkcode]) break; + if (!IdtInputs::IsKeyBoardDown(vkcode)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { @@ -335,7 +336,7 @@ void KeyboardInteraction() { auto vkcode = m.vkcode; - while (KeyBoradDown[vkcode]) this_thread::sleep_for(chrono::milliseconds(20)); + while (IdtInputs::IsKeyBoardDown(vkcode)) this_thread::sleep_for(chrono::milliseconds(20)); if (stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && penetrate.select == false) { @@ -354,7 +355,7 @@ void KeyboardInteraction() { while (1) { - if (!KeyBoradDown[(BYTE)0x51]) + if (!IdtInputs::IsKeyBoardDown((BYTE)0x51)) { if (FreezeFrame.mode != 1) { @@ -380,7 +381,7 @@ void KeyboardInteraction() { while (1) { - if (!KeyBoradDown[(BYTE)0x45]) + if (!IdtInputs::IsKeyBoardDown((BYTE)0x45)) { if (stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection) { @@ -408,7 +409,7 @@ void KeyboardInteraction() { while (1) { - if (!KeyBoradDown[(BYTE)0x5A]) + if (!IdtInputs::IsKeyBoardDown((BYTE)0x5A)) { if (stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && (!RecallImage.empty() || (!FirstDraw && RecallImagePeak == 0))) IdtRecall(); else if (stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && RecallImage.empty() && current_record_pointer <= total_record_pointer + 1 && practical_total_record_pointer) IdtRecovery(); @@ -424,7 +425,6 @@ void KeyboardInteraction() } } -HCURSOR hArrowCursor = LoadCursor(nullptr, IDC_ARROW); LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) @@ -439,7 +439,6 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l flags |= (0x00010000); return (LRESULT)flags; } - case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) @@ -462,43 +461,6 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l return HIWINDOW_DEFAULT_PROC; } -BOOL DisableEdgeGestures(HWND hwnd, BOOL disable) -{ - typedef HRESULT(WINAPI* SHGetPropertyStoreForWindowFunc)(HWND, REFIID, void**); - - HMODULE hShcore = LoadLibrary(TEXT("Shell32.dll")); - if (!hShcore) return FALSE; - - SHGetPropertyStoreForWindowFunc pSHGetPropertyStoreForWindow = (SHGetPropertyStoreForWindowFunc)GetProcAddress(hShcore, "SHGetPropertyStoreForWindow"); - if (!pSHGetPropertyStoreForWindow) - { - FreeLibrary(hShcore); - return FALSE; - } - - IPropertyStore* pPropStore = NULL; - HRESULT hr = pSHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pPropStore)); - - if (SUCCEEDED(hr)) - { - PROPERTYKEY propKey; - propKey.fmtid = GUID{ 0x32CE38B2, 0x2C9A, 0x41B1, { 0x9B, 0xC5, 0xB3, 0x78, 0x43, 0x94, 0xAA, 0x44 } }; - propKey.pid = 2; - - PROPVARIANT propVar; - PropVariantInit(&propVar); - propVar.vt = VT_BOOL; - propVar.boolVal = (disable ? VARIANT_TRUE : VARIANT_FALSE); - - hr = pPropStore->SetValue(propKey, propVar); - - PropVariantClear(&propVar); - pPropStore->Release(); - } - - FreeLibrary(hShcore); - return SUCCEEDED(hr); -} // 落笔预备 shared_mutex prepareCanvasQueueSm; @@ -2134,11 +2096,9 @@ int drawpad_main() SetWindowPos(drawpad_window, NULL, MainMonitor.rcMonitor.left, MainMonitor.rcMonitor.top, MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, SWP_NOZORDER | SWP_NOACTIVATE); } - // 禁用手势 初始化 - { - hiex::SetWndProcFunc(drawpad_window, DrawpadMsgCallback); - DisableEdgeGestures(drawpad_window, true); - } + + // 设置自定义窗口消息回调 + hiex::SetWndProcFunc(drawpad_window, DrawpadMsgCallback); //初始化数值 { @@ -2217,6 +2177,9 @@ int drawpad_main() if (int(state) == 1 && nextPointMode == StateModeSelectEnum::IdtEraser && setlist.RubberRecover) target_status = 0; else if (int(state) == 1 && nextPointMode == StateModeSelectEnum::IdtPen && setlist.BrushRecover) target_status = 0; + // 颜色和粗细选择等需要缩回 + else if (int(state) == 1 && nextPointMode == StateModeSelectEnum::IdtPen && state != 1.0) state = 1.0; + if (current_record_pointer != reference_record_pointer) { current_record_pointer = reference_record_pointer = max(1, reference_record_pointer - 1); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.h" "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.h" index 90efde7f..3fc7b64c 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.h" @@ -42,7 +42,6 @@ extern shared_mutex drawWaitingSm; extern IMAGE drawpad; extern IMAGE window_background; -extern unordered_map KeyBoradDown; extern HHOOK DrawpadHookCall; LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam); void DrawpadInstallHook(); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" index e797872e..8bd954ef 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" @@ -18,6 +18,7 @@ #include "IdtUpdate.h" #include "IdtWindow.h" #include "SuperTop/IdtSuperTop.h" +#include "Inkeys/Other/IdtInputs.h" #include #include @@ -136,7 +137,7 @@ pair GetPointOnCircle(double x, double y, double r, double angle int SeekBar(ExMessage m) { - if (!KeyBoradDown[VK_LBUTTON]) return 0; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) return 0; POINT p; GetCursorPos(&p); @@ -148,7 +149,7 @@ int SeekBar(ExMessage m) while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; GetCursorPos(&p); if (firX == p.x && firY == p.y) continue; @@ -193,13 +194,13 @@ LRESULT CALLBACK FloatingHookCallback(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { - if (wParam == WM_LBUTTONDOWN) KeyBoradDown[VK_LBUTTON] = true; - else if (wParam == WM_LBUTTONUP) KeyBoradDown[VK_LBUTTON] = false; - else if (wParam == WM_MBUTTONDOWN) KeyBoradDown[VK_MBUTTON] = true; - else if (wParam == WM_MBUTTONUP) KeyBoradDown[VK_MBUTTON] = false; + if (wParam == WM_LBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_LBUTTON, true); + else if (wParam == WM_LBUTTONUP) IdtInputs::SetKeyBoardDown(VK_LBUTTON, false); + else if (wParam == WM_MBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_MBUTTON, true); + else if (wParam == WM_MBUTTONUP) IdtInputs::SetKeyBoardDown(VK_MBUTTON, false); - else if (wParam == WM_RBUTTONDOWN) KeyBoradDown[VK_RBUTTON] = true; - else if (wParam == WM_RBUTTONUP) KeyBoradDown[VK_RBUTTON] = false; + else if (wParam == WM_RBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_RBUTTON, true); + else if (wParam == WM_RBUTTONUP) IdtInputs::SetKeyBoardDown(VK_RBUTTON, false); if (wParam == WM_MOUSEWHEEL && stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && !penetrate.select && ppt_show != NULL) { @@ -240,6 +241,172 @@ LRESULT CALLBACK FloatingHookCallback(int nCode, WPARAM wParam, LPARAM lParam) thread(MouseClickCollapse).detach(); } } + + // TODO 关闭RTS时,靠钩子仅支持鼠标绘制:具体实验位于 inkeys2-final + /*struct DrawpadMsgCallbackInfoStruct + { + IdtAtomic isLbuttonDown = false; + IdtAtomic isRbuttonDown = false; + }; + extern DrawpadMsgCallbackInfoStruct drawpadMsgCallbackInfo;*/ + /*// 输入融合感知:鼠标 + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + { + // 这是一个按下状态 + + // 输入融合感知:鼠标 + TouchMode mode{}; + TouchInfo info{}; + + // 获取坐标 + mode.pt.x = GET_X_LPARAM(lParam); + mode.pt.y = GET_Y_LPARAM(lParam); + + // 获取设备类型 + int deviceType = 2; + LONG touchCnt = -1; + // 右键 + if (msg == WM_RBUTTONDOWN) + { + mode.type = deviceType = 3; + touchCnt = -2; + drawpadMsgCallbackInfo.isRbuttonDown = true; + } + // 左键 + else + { + mode.type = deviceType = 2; + touchCnt = -1; + drawpadMsgCallbackInfo.isLbuttonDown = true; + } + + // 设置固定 PID + info.pid = touchCnt; + + std::unique_lock lock1(touchPosSm); + TouchPos[touchCnt] = mode; + lock1.unlock(); + + std::unique_lock lock2(touchSpeedSm); + TouchSpeed[touchCnt] = 0; + PreviousPointPosition[touchCnt].first = PreviousPointPosition[touchCnt].second = -1; + lock2.unlock(); + + std::unique_lock lock3(pointListSm); + TouchList.push_back(touchCnt); + lock3.unlock(); + + info.mode = mode; + + std::unique_lock lock4(touchTempSm); + TouchTemp.push_back(info); + lock4.unlock(); + + rtsNum++, rtsDown = true; + + return 0; + } + + case WM_MOUSEMOVE: + { + if (!drawpadMsgCallbackInfo.isLbuttonDown && !drawpadMsgCallbackInfo.isRbuttonDown) break; + + // 这是一个移动状态 + + // 获取坐标 + auto xO = GET_X_LPARAM(lParam); + auto yO = GET_Y_LPARAM(lParam); + + if (drawpadMsgCallbackInfo.isLbuttonDown) + { + int pid = -1; + + shared_lock lock1(touchPosSm); + TouchMode mode = TouchPos[pid]; + lock1.unlock(); + + mode.pt.x = xO; + mode.pt.y = yO; + + unique_lock lock2(touchPosSm); + TouchPos[pid] = mode; + lock2.unlock(); + } + if (drawpadMsgCallbackInfo.isRbuttonDown) + { + int pid = -2; + + shared_lock lock1(touchPosSm); + TouchMode mode = TouchPos[pid]; + lock1.unlock(); + + mode.pt.x = xO; + mode.pt.y = yO; + + unique_lock lock2(touchPosSm); + TouchPos[pid] = mode; + lock2.unlock(); + } + + return 0; + } + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + { + // 这是一个抬起状态 + + // 先更新最后的位置 + int pid = -1; + { + if (msg == WM_RBUTTONUP) + { + pid = -2; + drawpadMsgCallbackInfo.isRbuttonDown = false; + } + // 左键 + else + { + pid = -1; + drawpadMsgCallbackInfo.isLbuttonDown = false; + } + + shared_lock lock1(touchPosSm); + TouchMode mode = TouchPos[pid]; + lock1.unlock(); + + mode.pt.x = GET_X_LPARAM(lParam); + mode.pt.y = GET_Y_LPARAM(lParam); + + unique_lock lock2(touchPosSm); + TouchPos[pid] = mode; + lock2.unlock(); + } + + rtsNum = max(0, rtsNum - 1); + if (rtsNum == 0) rtsDown = false; + + auto it = std::find(TouchList.begin(), TouchList.end(), pid); + if (it != TouchList.end()) + { + unique_lock lockPointListSm(pointListSm); + TouchList.erase(it); + lockPointListSm.unlock(); + } + + if (rtsNum == 0) + { + unique_lock lockTouchPosSm(touchPosSm); + TouchPos.clear(); + lockTouchPosSm.unlock(); + + touchNum = 0; + inkNum = 0; + } + + return 0; + }*/ } // 继续传递事件给下一个钩子或目标窗口 @@ -328,6 +495,9 @@ void DrawScreen() idtLoadImage(&floating_icon[28], L"PNG", L"CustomizeIco8", 40, 40, true); // lockS idtLoadImage(&floating_icon[29], L"PNG", L"CustomizeIco9", 40, 40, true); // taskmgr + idtLoadImage(&floating_icon[31], L"PNG", L"CustomizeIco10", 40, 40, true); // SecRandom + idtLoadImage(&floating_icon[32], L"PNG", L"CustomizeIco11", 40, 40, true); // NamePicker + idtLoadImage(&sign, L"PNG", L"sign1", 30, 30, true); idtLoadImage(&skin[1], L"PNG", L"skin1"); @@ -4664,7 +4834,10 @@ void DrawScreen() { ChangeColor(floating_icon[7], UIControlColor[L"Image/test/fill"].v); hiex::TransparentImage(&background, int(UIControl[L"Image/test/x"].v), int(UIControl[L"Image/test/y"].v), &floating_icon[7], int((UIControlColor[L"Image/test/fill"].v >> 24) & 0xff)); - if (AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateRestart) hiex::EasyX_Gdiplus_SolidEllipse(UIControl[L"Image/test/x"].v + 30, UIControl[L"Image/test/y"].v, 10, 10, RGBA(228, 55, 66, 255), false, SmoothingModeHighQuality, &background); + if (AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateRestart || + AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateLimit || + AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateInkeys3) + hiex::EasyX_Gdiplus_SolidEllipse(UIControl[L"Image/test/x"].v + 30, UIControl[L"Image/test/y"].v, 10, 10, RGBA(228, 55, 66, 255), false, SmoothingModeHighQuality, &background); Gdiplus::Font gp_font(&HarmonyOS_fontFamily, UIControl[L"Words/test/height"].v, FontStyleRegular, UnitPixel); SolidBrush WordBrush(hiex::ConvertToGdiplusColor(UIControlColor[L"Words/test/words_color"].v, true)); @@ -4690,10 +4863,13 @@ void DrawScreen() else if (setlist.component.shortcutButton.keyboard.keyboardesc) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[25], UIControl[L"Image/Customize1/transparency"].v); else if (setlist.component.shortcutButton.keyboard.keyboardAltF4) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[26], UIControl[L"Image/Customize1/transparency"].v); + else if (setlist.component.shortcutButton.rollCall.IslandCaller) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[22], UIControl[L"Image/Customize1/transparency"].v); + else if (setlist.component.shortcutButton.rollCall.SecRandom) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[31], UIControl[L"Image/Customize1/transparency"].v); + else if (setlist.component.shortcutButton.rollCall.NamePicker) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[32], UIControl[L"Image/Customize1/transparency"].v); + else if (setlist.component.shortcutButton.linkage.classislandSettings) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[21], UIControl[L"Image/Customize1/transparency"].v); else if (setlist.component.shortcutButton.linkage.classislandProfile) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[21], UIControl[L"Image/Customize1/transparency"].v); else if (setlist.component.shortcutButton.linkage.classislandClassswap) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[21], UIControl[L"Image/Customize1/transparency"].v); - else if (setlist.component.shortcutButton.linkage.classislandIslandCaller) hiex::TransparentImage(&background, int(UIControl[L"Image/Customize1/x"].v), int(UIControl[L"Image/Customize1/y"].v), &floating_icon[22], UIControl[L"Image/Customize1/transparency"].v); Gdiplus::Font gp_font(&HarmonyOS_fontFamily, UIControl[L"Words/Customize1/height"].v, FontStyleRegular, UnitPixel); SolidBrush WordBrush(hiex::ConvertToGdiplusColor(UIControlColor[L"Words/Customize1/words_color"].v, true)); @@ -4715,10 +4891,13 @@ void DrawScreen() else if (setlist.component.shortcutButton.keyboard.keyboardesc) graphics.DrawString(L"ESC键", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); else if (setlist.component.shortcutButton.keyboard.keyboardAltF4) graphics.DrawString(L"Alt+F4", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); + else if (setlist.component.shortcutButton.rollCall.IslandCaller) graphics.DrawString(L"随机点名", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); + else if (setlist.component.shortcutButton.rollCall.SecRandom) graphics.DrawString(L"随机点名", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); + else if (setlist.component.shortcutButton.rollCall.NamePicker) graphics.DrawString(L"随机点名", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); + else if (setlist.component.shortcutButton.linkage.classislandSettings) graphics.DrawString(L"CI设置", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); else if (setlist.component.shortcutButton.linkage.classislandProfile) graphics.DrawString(L"档案编辑", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); else if (setlist.component.shortcutButton.linkage.classislandClassswap) graphics.DrawString(L"快速换课", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); - else if (setlist.component.shortcutButton.linkage.classislandIslandCaller) graphics.DrawString(L"随机点名", -1, &gp_font, hiex::RECTToRectF(words_rect), &stringFormat, &WordBrush); } //插件空位1:随机点名 @@ -5654,7 +5833,7 @@ void MouseInteraction() else widthBuffer = 101 + int(double(idx - 260) / 60.0 * 399.0); SetPenWidth((float)widthBuffer, false); - if (!KeyBoradDown[VK_LBUTTON]) + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) { SetPenWidth((float)widthBuffer); break; @@ -5864,7 +6043,7 @@ void MouseInteraction() UIControlTarget[L"RoundRect/BrushColorChooseMark/x"].v = UIControl[L"RoundRect/BrushColorChooseMark/x"].v = result.x + UIControl[L"RoundRect/BrushColorChooseWheel/x"].v - 7; UIControlTarget[L"RoundRect/BrushColorChooseMark/y"].v = UIControl[L"RoundRect/BrushColorChooseMark/y"].v = result.y + UIControl[L"RoundRect/BrushColorChooseWheel/y"].v - 7; - if (!KeyBoradDown[VK_LBUTTON]) + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) { SetPenColor(RGBA(red, green, blue, (floatingInfo.brushColor >> 24) & 0xFF)); break; @@ -6274,6 +6453,7 @@ void MouseInteraction() if (m.message == WM_LBUTTONDOWN) { lx = m.x, ly = m.y; + while (1) { ExMessage m = hiex::getmessage_win32(EM_MOUSE, floating_window); @@ -6314,6 +6494,46 @@ void MouseInteraction() keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0); } + else if (setlist.component.shortcutButton.rollCall.IslandCaller) + { + /*ShellExecute(NULL, L"open", L"classisland://plugins/IslandCaller/Run", NULL, NULL, SW_SHOWNORMAL);*/ + + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_NOASYNC; + sei.hwnd = NULL; + sei.lpVerb = L"open"; + sei.lpFile = L"classisland://plugins/IslandCaller/Run"; + sei.nShow = SW_SHOWNORMAL; + + ShellExecuteEx(&sei); + } + else if (setlist.component.shortcutButton.rollCall.SecRandom) + { + /*ShellExecute(NULL, L"open", L"secrandom://direct_extraction", NULL, NULL, SW_SHOWNORMAL);*/ + + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_NOASYNC; + sei.hwnd = NULL; + sei.lpVerb = L"open"; + sei.lpFile = L"secrandom://direct_extraction"; + sei.nShow = SW_SHOWNORMAL; + + ShellExecuteEx(&sei); + } + else if (setlist.component.shortcutButton.rollCall.NamePicker) + { + /*ShellExecute(NULL, L"open", L"namepicker://", NULL, NULL, SW_SHOWNORMAL);*/ + + SHELLEXECUTEINFO sei = { sizeof(sei) }; + sei.fMask = SEE_MASK_NOASYNC; + sei.hwnd = NULL; + sei.lpVerb = L"open"; + sei.lpFile = L"namepicker://"; + sei.nShow = SW_SHOWNORMAL; + + ShellExecuteEx(&sei); + } + else if (setlist.component.shortcutButton.linkage.classislandSettings) { ShellExecute(NULL, L"open", L"classisland://app/settings/", NULL, NULL, SW_SHOWNORMAL); @@ -6326,10 +6546,6 @@ void MouseInteraction() { ShellExecute(NULL, L"open", L"classisland://app/class-swap", NULL, NULL, SW_SHOWNORMAL); } - else if (setlist.component.shortcutButton.linkage.classislandIslandCaller) - { - ShellExecute(NULL, L"open", L"classisland://plugins/IslandCaller/Run", NULL, NULL, SW_SHOWNORMAL); - } break; } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" index 5956ad7e..08c39a11 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" @@ -13,7 +13,7 @@ #include "IdtMain.h" -#include "IdtBar.h" +#include "CrashHandler/CrashHandler.h" #include "IdtConfiguration.h" #include "IdtD2DPreparation.h" #include "IdtDisplayManagement.h" @@ -35,8 +35,9 @@ #include "IdtTime.h" #include "IdtUpdate.h" #include "IdtWindow.h" +#include "Inkeys/Other/IdtGesture.h" +#include "Inkeys/Load/IdtFontLoad.h" #include "Launch/IdtLaunchState.h" -#include "CrashHandler/CrashHandler.h" #include "SuperTop/IdtSuperTop.h" #include @@ -45,12 +46,12 @@ #pragma comment(lib, "netapi32.lib") wstring buildTime = __DATE__ L" " __TIME__; // 构建时间 -wstring editionDate = L"20250721a"; // 程序发布日期 +wstring editionDate = L"20251204a"; // 程序发布日期 wstring editionChannel = L"LTS"; // 程序发布通道 wstring userId; // 用户GUID wstring globalPath; // 程序当前路径 -wstring dataPath; // 数据保存的路径 +wstring pluginPath; // 数据保存的路径 wstring programArchitecture = L"win32"; wstring targetArchitecture = L"win32"; @@ -125,12 +126,18 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR return 0; } - // 获取数据存储路径 + // 获取目录 { - wchar_t buffer[MAX_PATH]; - if (GetEnvironmentVariableW(L"ProgramData", buffer, MAX_PATH) != 0) dataPath = buffer; - else dataPath = L"C:\\ProgramData"; - dataPath += L"\\Inkeys"; + // 获取插件存储路径 + { + /* + wchar_t buffer[MAX_PATH]; + if (GetEnvironmentVariableW(L"ProgramData", buffer, MAX_PATH) != 0) pluginPath = buffer; + else pluginPath = L"C:\\ProgramData";*/ + + pluginPath = globalPath; + pluginPath += L"\\Inkeys\\Plugin"; + } } } // 防止重复启动 @@ -860,6 +867,8 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR } // 绘制 { + setlist.disableRTS = false; + setlist.liftStraighten = false, setlist.waitStraighten = true; setlist.pointAdsorption = true; @@ -923,11 +932,15 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR setlist.component.shortcutButton.keyboard.keyboardesc = false; setlist.component.shortcutButton.keyboard.keyboardAltF4 = false; } + { + setlist.component.shortcutButton.rollCall.IslandCaller = false; + setlist.component.shortcutButton.rollCall.SecRandom = false; + setlist.component.shortcutButton.rollCall.NamePicker = false; + } { setlist.component.shortcutButton.linkage.classislandSettings = false; setlist.component.shortcutButton.linkage.classislandProfile = false; setlist.component.shortcutButton.linkage.classislandClassswap = false; - setlist.component.shortcutButton.linkage.classislandIslandCaller = false; } } } @@ -975,17 +988,20 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR else setlist.paintDevice = 1; } { - //// 获取屏幕设备上下文 - //HDC screen = GetDC(NULL); + HDC screenDC = GetDC(nullptr); + double scale = 1.0; - //// 获取屏幕的 DPI 值 - //int dpiX = GetDeviceCaps(screen, LOGPIXELSX); - //int dpiY = GetDeviceCaps(screen, LOGPIXELSY); + if (screenDC) + { + int dpiX = GetDeviceCaps(screenDC, LOGPIXELSX); + ReleaseDC(nullptr, screenDC); - //// 释放设备上下文 - //ReleaseDC(NULL, screen); + // 转换为缩放倍率 + scale = static_cast(dpiX) / USER_DEFAULT_SCREEN_DPI; + } - setlist.settingGlobalScale = 1.0f; + // 限制范围 1.0 ~ 1.5 + setlist.settingGlobalScale = static_cast(clamp(scale, 1.0, 1.5)); } } @@ -1063,6 +1079,16 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR } // 自动更新初始化 { + // 检查系统版本 + { + IdtSysVersionStruct windowsVersion = GetWindowsVersion(); + + if (windowsVersion.majorVersion > 6 || (windowsVersion.majorVersion == 6 && windowsVersion.minorVersion >= 2)) isWindows8OrGreater = true; + else isWindows8OrGreater = false; + + windowsEdition = to_wstring(windowsVersion.majorVersion) + L"." + to_wstring(windowsVersion.minorVersion) + L"." + to_wstring(windowsVersion.buildNumber); + } + #ifdef IDT_RELEASE thread(AutomaticUpdate).detach(); #endif @@ -1076,38 +1102,40 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR } // 字体初始化 { - INT numFound = 0; - HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(198), L"TTF"); - HGLOBAL hMem = LoadResource(NULL, hRes); - void* pLock = LockResource(hMem); - DWORD dwSize = SizeofResource(NULL, hRes); - - fontCollection.AddMemoryFont(pLock, dwSize); - fontCollection.GetFamilies(1, &HarmonyOS_fontFamily, &numFound); + { + INT numFound = 0; + HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(198), L"TTF"); + HGLOBAL hMem = LoadResource(NULL, hRes); + void* pLock = LockResource(hMem); + DWORD dwSize = SizeofResource(NULL, hRes); + + fontCollection.AddMemoryFont(pLock, dwSize); + fontCollection.GetFamilies(1, &HarmonyOS_fontFamily, &numFound); + + stringFormat.SetAlignment(StringAlignmentCenter); + stringFormat.SetLineAlignment(StringAlignmentCenter); + stringFormat.SetFormatFlags(StringFormatFlagsNoWrap); + + stringFormat_left.SetAlignment(StringAlignmentNear); + stringFormat_left.SetLineAlignment(StringAlignmentNear); + stringFormat_left.SetFormatFlags(StringFormatFlagsNoWrap); + } { - if (_waccess((globalPath + L"ttf").c_str(), 0) == -1) - { - error_code ec; - filesystem::create_directory(globalPath + L"ttf", ec); - } - ExtractResource((globalPath + L"ttf\\hmossscr.ttf").c_str(), L"TTF", MAKEINTRESOURCE(198)); + vector fontResourceIDs; + fontResourceIDs.emplace_back(198); // HarmonyOS Sans SC - IdtFontCollectionLoader* D2DFontCollectionLoader = new IdtFontCollectionLoader; - D2DFontCollectionLoader->AddFont(D2DTextFactory, globalPath + L"ttf\\hmossscr.ttf"); + IdtFontFileLoader::IsLoaderInitialized(); + IdtFontCollectionLoader::IsLoaderInitialized(); - D2DTextFactory->RegisterFontCollectionLoader(D2DFontCollectionLoader); - D2DTextFactory->CreateCustomFontCollection(D2DFontCollectionLoader, 0, 0, &D2DFontCollection); - D2DTextFactory->UnregisterFontCollectionLoader(D2DFontCollectionLoader); - } + D2DTextFactory->RegisterFontFileLoader(IdtFontFileLoader::GetLoader()); + D2DTextFactory->RegisterFontCollectionLoader(IdtFontCollectionLoader::GetLoader()); - stringFormat.SetAlignment(StringAlignmentCenter); - stringFormat.SetLineAlignment(StringAlignmentCenter); - stringFormat.SetFormatFlags(StringFormatFlagsNoWrap); + IDWriteFontCollection* tempFontCollection = nullptr; + D2DTextFactory->CreateCustomFontCollection(IdtFontCollectionLoader::GetLoader(), fontResourceIDs.data(), static_cast(fontResourceIDs.size() * sizeof(UINT)), &tempFontCollection); - stringFormat_left.SetAlignment(StringAlignmentNear); - stringFormat_left.SetLineAlignment(StringAlignmentNear); - stringFormat_left.SetFormatFlags(StringFormatFlagsNoWrap); + D2DFontCollection.Attach(tempFontCollection); + } IDTLogger->info("[主线程][IdtMain] 字体初始化完成"); } @@ -1118,18 +1146,29 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR if (userId == L"Error") ClassName = L"HiEasyX041"; else ClassName = userId; + // 窗口创建完成后处理的 + auto disableGestureFuc = [&](HWND hWnd) -> void + { + IdtGesture::DisableEdgeGestures(hWnd, true); + }; + auto touchRegisterFuc = [&](HWND hWnd) -> void + { + RegisterTouchWindow(hWnd, 0); + disableGestureFuc(hWnd); + }; + CreateMagnifierWindow(); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); freeze_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys5 FreezeWindow", (L"Inkeys1;" + ClassName).c_str(), nullptr, magnifierWindow); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); - drawpad_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys4 DrawpadWindow", (L"Inkeys2;" + ClassName).c_str(), nullptr, freeze_window); + drawpad_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys4 DrawpadWindow", (L"Inkeys2;" + ClassName).c_str(), nullptr, freeze_window, disableGestureFuc); SettingWindowBegin(); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); - ppt_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys2 PptWindow", (L"Inkeys4;" + ClassName).c_str(), nullptr, setting_window); + ppt_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys2 PptWindow", (L"Inkeys4;" + ClassName).c_str(), nullptr, setting_window, touchRegisterFuc); //ppt_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys2 PptWindow", (L"Inkeys4;" + ClassName).c_str(), nullptr, drawpad_window); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); @@ -1238,7 +1277,35 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR IDTLogger->info("[主线程][IdtMain] 线程初始化完成"); } - CrashHandler::IsSecond(false); + + { + // 创建测试控制台 + +#ifndef IDT_RELEASE + { + AllocConsole(); + + FILE* fp; + freopen_s(&fp, "CONOUT$", "w", stdout); + freopen_s(&fp, "CONOUT$", "w", stderr); + freopen_s(&fp, "CONIN$", "r", stdin); + + // 让 C++ 流重新与 C 的 FILE* 同步 + // true = 同步;不传参数的重载在 C++11 之后是被弃用的(某些编译器行为不定) + std::ios::sync_with_stdio(true); + + // 清空原来的缓冲(保证重新绑定后生效) + std::wcout.clear(); + std::wcin.clear(); + std::wcerr.clear(); + std::cout.clear(); + std::cin.clear(); + std::cerr.clear(); + + std::wcout.imbue(std::locale("chs")); + } +#endif + } IDTLogger->info("[主线程][IdtMain] 开始等待关闭程序信号发出"); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" index 32d85a68..34e029da 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" @@ -61,6 +61,7 @@ #include #include #include +#include // 日志类 #define SPDLOG_WCHAR_FILENAMES @@ -98,7 +99,7 @@ extern wstring editionChannel; extern wstring userId; extern wstring globalPath; -extern wstring dataPath; +extern wstring pluginPath; extern wstring programArchitecture; extern wstring targetArchitecture; @@ -167,7 +168,6 @@ class IdtAtomic operator IdtAtomicT() const noexcept { return load(); } IdtAtomic& operator=(IdtAtomicT desired) noexcept { store(desired); return *this; } - // Increment/Decrement Operators added template >> T operator++() noexcept { return value.fetch_add(1, std::memory_order_seq_cst) + 1; @@ -184,6 +184,17 @@ class IdtAtomic T operator--(int) noexcept { return value.fetch_sub(1, std::memory_order_seq_cst); } + + template >> + IdtAtomic& operator+=(T arg) noexcept { + fetch_add(arg, std::memory_order_seq_cst); + return *this; + } + template >> + IdtAtomic& operator-=(T arg) noexcept { + fetch_sub(arg, std::memory_order_seq_cst); + return *this; + } }; // 调测专用 @@ -194,7 +205,8 @@ void Testi(long long t); void Testd(double t); void Testw(wstring t); void Testa(string t); -#define IdtFalse false +#define TestFalse false +#define TestCout cout // this_thread::sleep_for(chrono::milliseconds(int)) -#endif \ No newline at end of file +#endif diff --git "a/\346\231\272\347\273\230\346\225\231/IdtNet.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtNet.cpp" index e7e3ba67..fbe41ea5 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtNet.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtNet.cpp" @@ -6,10 +6,15 @@ #define CPPHTTPLIB_OPENSSL_SUPPORT #include "cpphttplib/httplib.h" -std::string GetEditionInformation() +std::string GetEditionInformation(std::string referer) { httplib::Result res; - httplib::Headers headers = { {"Cache-Control", "no-cache"}, {"Pragma", "no-cache"} }; + httplib::Headers headers = + { + { "Cache-Control", "no-cache" }, + { "Pragma", "no-cache" }, + { "Referer", referer.c_str() } + }; // 尝试主地址 { @@ -19,7 +24,7 @@ std::string GetEditionInformation() scli.set_read_timeout(10); // 尝试 Https 连接 - res = scli.Get("/1709404/version_identification/official_version.json", headers); + res = scli.Get("/1709404/Inkeys/Version/version.json", headers); if (!res || res->status != 200) { // 失败后尝试使用 Http 连接 @@ -28,7 +33,7 @@ std::string GetEditionInformation() scli.set_connection_timeout(5); scli.set_read_timeout(10); - res = cli.Get("/1709404/version_identification/official_version.json", headers); + res = cli.Get("/1709404/Inkeys/Version/version.json", headers); } if (res && res->status == 200) @@ -46,7 +51,7 @@ std::string GetEditionInformation() scli.set_read_timeout(10); // 尝试 Https 连接 - res = scli.Get("/version_identification/official_version.json", headers); + res = scli.Get("/Inkeys/Version/version.json", headers); if (!res || res->status != 200) { // 失败后尝试使用 Http 连接 @@ -55,7 +60,7 @@ std::string GetEditionInformation() scli.set_connection_timeout(5); scli.set_read_timeout(10); - res = cli.Get("/version_identification/official_version.json", headers); + res = cli.Get("/Inkeys/Version/version.json", headers); } if (res && res->status == 200) @@ -68,10 +73,15 @@ std::string GetEditionInformation() return "Error"; } -bool DownloadEdition(std::string domain, std::string path, std::wstring directory, std::wstring fileName, std::atomic_ullong& downloadedSize) +bool DownloadEdition(std::string domain, std::string path, std::wstring directory, std::wstring fileName, std::atomic_ullong& downloadedSize, std::string referer) { httplib::Result res; - httplib::Headers headers = { {"Cache-Control", "no-cache"}, {"Pragma", "no-cache"} }; + httplib::Headers headers = + { + { "Cache-Control", "no-cache" }, + { "Pragma", "no-cache" }, + { "Referer", referer.c_str() } + }; std::ofstream file; auto callback = [&](const char* data, size_t data_length) diff --git "a/\346\231\272\347\273\230\346\225\231/IdtNet.h" "b/\346\231\272\347\273\230\346\225\231/IdtNet.h" index 560339ae..a0ab2d45 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtNet.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtNet.h" @@ -3,5 +3,5 @@ #include #include -std::string GetEditionInformation(); -bool DownloadEdition(std::string domain, std::string path, std::wstring directory, std::wstring fileName, std::atomic_ullong& downloadedSize); \ No newline at end of file +std::string GetEditionInformation(std::string referer = ""); +bool DownloadEdition(std::string domain, std::string path, std::wstring directory, std::wstring fileName, std::atomic_ullong& downloadedSize, std::string referer = ""); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" index 090c88e9..4d925554 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" @@ -34,6 +34,7 @@ #include "IdtImage.h" #include "IdtState.h" #include "IdtI18n.h" +#include "Inkeys/Other/IdtInputs.h" #include #include @@ -207,6 +208,157 @@ bool CheckPptCom() return true; } +LRESULT CALLBACK PptWindowMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_TABLET_QUERYSYSTEMGESTURESTATUS: + { + DWORD flags = 0; + flags |= (0x00000001); + flags |= (0x00000008); + flags |= (0x00000100); + flags |= (0x00000200); + flags |= (0x00010000); + return (LRESULT)flags; + } + + case WM_TOUCH: + { + static DWORD activeTouchId = 0; // 0表示无活动ID + static bool isTouchActive = false; + + UINT cInputs = LOWORD(wParam); + TOUCHINPUT inputs[32]; + if (GetTouchInputInfo((HTOUCHINPUT)lParam, cInputs, inputs, sizeof(TOUCHINPUT))) + { + bool touchIdCheck = false; // 检测当前活动ID是否还存在 + short x = 0, y = 0; // 坐标 + + for (UINT i = 0; i < cInputs; i++) + { + const TOUCHINPUT& ti = inputs[i]; + + double xO = static_cast(ti.x) / 100.0; + double yO = static_cast(ti.y) / 100.0; + x = static_cast(xO + 0.5); + y = static_cast(yO + 0.5); + + if (ti.dwFlags & TOUCHEVENTF_DOWN) + { + // 如果当前无activeID,则锁定第一个DOWN点 + if (!isTouchActive) + { + activeTouchId = ti.dwID; + isTouchActive = true; + + { + ExMessage msgMouse = {}; + msgMouse.message = WM_LBUTTONDOWN; + msgMouse.x = x; + msgMouse.y = y; + msgMouse.lbutton = true; + + int index = hiex::GetWindowIndex(ppt_window, false); + unique_lock lg_vecWindows_vecMessage_sm(hiex::g_vecWindows_vecMessage_sm[index]); + hiex::g_vecWindows[index].vecMessage.push_back(msgMouse); + lg_vecWindows_vecMessage_sm.unlock(); + } + } + } + if (ti.dwFlags & TOUCHEVENTF_MOVE) + { + if (isTouchActive && ti.dwID == activeTouchId) + { + ExMessage msgMouse = {}; + msgMouse.message = WM_MOUSEMOVE; + msgMouse.x = x; + msgMouse.y = y; + msgMouse.lbutton = true; + + int index = hiex::GetWindowIndex(ppt_window, false); + unique_lock lg_vecWindows_vecMessage_sm(hiex::g_vecWindows_vecMessage_sm[index]); + hiex::g_vecWindows[index].vecMessage.push_back(msgMouse); + lg_vecWindows_vecMessage_sm.unlock(); + } + } + if (ti.dwFlags & TOUCHEVENTF_UP) + { + if (isTouchActive && ti.dwID == activeTouchId) + { + activeTouchId = 0; + isTouchActive = false; + + { + ExMessage msgMouse = {}; + msgMouse.message = WM_LBUTTONUP; + msgMouse.x = x; + msgMouse.y = y; + msgMouse.lbutton = false; + + int index = hiex::GetWindowIndex(ppt_window, false); + unique_lock lg_vecWindows_vecMessage_sm(hiex::g_vecWindows_vecMessage_sm[index]); + hiex::g_vecWindows[index].vecMessage.push_back(msgMouse); + lg_vecWindows_vecMessage_sm.unlock(); + } + } + } + + if (isTouchActive && ti.dwID == activeTouchId) touchIdCheck = true; + } + + if (isTouchActive && !touchIdCheck) + { + activeTouchId = 0; + isTouchActive = false; + + { + ExMessage msgMouse = {}; + msgMouse.message = WM_LBUTTONUP; + msgMouse.x = x; + msgMouse.y = y; + msgMouse.lbutton = false; + + int index = hiex::GetWindowIndex(ppt_window, false); + unique_lock lg_vecWindows_vecMessage_sm(hiex::g_vecWindows_vecMessage_sm[index]); + hiex::g_vecWindows[index].vecMessage.push_back(msgMouse); + lg_vecWindows_vecMessage_sm.unlock(); + } + } + + CloseTouchInputHandle((HTOUCHINPUT)lParam); + } + + return 0; + } + + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_MOUSEMOVE: + { + // 如果是触摸模拟出来的鼠标消息,就直接丢掉 + DWORD extraInfo = GetMessageExtraInfo(); + if ((extraInfo & 0xFFFFFF00) == 0xFF515700) + { + return 0; + } + + // 否则当成真正的鼠标消息处理 + // 您的鼠标处理逻辑 + break; + } + + default: + return HIWINDOW_DEFAULT_PROC; + } + + return HIWINDOW_DEFAULT_PROC; +} + wstring GetPptTitle() { wstring ret = L""; @@ -254,7 +406,7 @@ void GetPptState() try { //_com_util::CheckError(PptCOMPto.CreateInstance(_uuidof(PptCOMServer))); - rel = PptCOMPto->Initialization(&PptInfoState.TotalPage, &PptInfoState.CurrentPage, pptComSetlist.autoKillWpsProcess); + rel = PptCOMPto->Initialization(&PptInfoState.TotalPage, &PptInfoState.CurrentPage/*, pptComSetlist.autoKillWpsProcess*/); } catch (_com_error err) { @@ -346,10 +498,25 @@ bool EndPptShow() return false; } +bool ViewPptShow() +{ + try + { + SetForegroundWindow(ppt_show); + PptCOMPto->ViewSlideShow(); + + return true; + } + catch (_com_error) + { + } + + return false; +} -void PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) +double PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) { - if (!KeyBoradDown[VK_LBUTTON]) return; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) return 0.0; PptUiAllReplaceSignal = 1; MonitorInfoStruct PPTMainMonitor; @@ -357,8 +524,9 @@ void PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) PPTMainMonitor = MainMonitor; DisplaysInfoLock.unlock(); - POINT p; - GetCursorPos(&p); + double ret = 0.0; + int firX = static_cast(firstX); + int firY = static_cast(firstY); // 自身数值记录 float widthFirst = pptComSetlist.bottomBothWidth; @@ -395,7 +563,7 @@ void PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) for (;;) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; POINT p; GetCursorPos(&p); @@ -489,6 +657,9 @@ void PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) pptComSetlist.bottomBothWidth = widthTarget; pptComSetlist.bottomBothHeight = heightTarget; + ret += sqrt((p.x - firX) * (p.x - firX) + (p.y - firY) * (p.y - firY)); + firX = static_cast(p.x), firY = static_cast(p.y); + this_thread::sleep_for(chrono::milliseconds(5)); } // 写入文件 @@ -496,11 +667,11 @@ void PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) PptComWriteSetting(); PptUiAllReplaceSignal = -1; - return; + return ret; } -void PptMiddlePageWidgetSeekBar(int firstX, int firstY, bool xReverse) +double PptMiddlePageWidgetSeekBar(int firstX, int firstY, bool xReverse) { - if (!KeyBoradDown[VK_LBUTTON]) return; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) return 0.0; PptUiAllReplaceSignal = 1; MonitorInfoStruct PPTMainMonitor; @@ -508,8 +679,9 @@ void PptMiddlePageWidgetSeekBar(int firstX, int firstY, bool xReverse) PPTMainMonitor = MainMonitor; DisplaysInfoLock.unlock(); - POINT p; - GetCursorPos(&p); + double ret = 0.0; + int firX = static_cast(firstX); + int firY = static_cast(firstY); // 自身数值记录 float widthFirst = pptComSetlist.middleBothWidth; @@ -546,7 +718,7 @@ void PptMiddlePageWidgetSeekBar(int firstX, int firstY, bool xReverse) for (;;) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; POINT p; GetCursorPos(&p); @@ -640,18 +812,21 @@ void PptMiddlePageWidgetSeekBar(int firstX, int firstY, bool xReverse) pptComSetlist.middleBothWidth = widthTarget; pptComSetlist.middleBothHeight = heightTarget; + ret += sqrt((p.x - firX) * (p.x - firX) + (p.y - firY) * (p.y - firY)); + firX = static_cast(p.x), firY = static_cast(p.y); + this_thread::sleep_for(chrono::milliseconds(5)); } // 写入文件 - if (pptComSetlist.memoryWidgetPosition && (pptComSetlist.bottomBothWidth != widthFirst || pptComSetlist.bottomBothHeight != heightFirst)) + if (pptComSetlist.memoryWidgetPosition && (pptComSetlist.middleBothWidth != widthFirst || pptComSetlist.middleBothHeight != heightFirst)) PptComWriteSetting(); PptUiAllReplaceSignal = -1; - return; + return ret; } void PptBottomMiddleSeekBar(int firstX, int firstY) { - if (!KeyBoradDown[VK_LBUTTON]) return; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) return; PptUiAllReplaceSignal = 1; MonitorInfoStruct PPTMainMonitor; @@ -697,7 +872,7 @@ void PptBottomMiddleSeekBar(int firstX, int firstY) for (;;) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; POINT p; GetCursorPos(&p); @@ -2092,6 +2267,9 @@ void PptDraw() } } + // 设置窗口自定义消息回调 + hiex::SetWndProcFunc(ppt_window, PptWindowMsgCallback); + // 创建 EasyX 兼容的 DC Render Target ID2D1DCRenderTarget* DCRenderTarget = nullptr; D2DFactory->CreateDCRenderTarget(&D2DProperty, &DCRenderTarget); @@ -3297,7 +3475,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { PreviousPptSlides(); @@ -3354,7 +3532,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { @@ -3388,12 +3566,21 @@ void PptInteract() } } else if (PptInfoStateBuffer.TotalPage != -1) pptUiRoundRectWidgetTarget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget_NextPage].FillColor.v = RGBA(250, 250, 250, 160); + // 底部左侧全局拖动条 if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].X.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Y.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Width.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Height.v) })) { if (m.message == WM_LBUTTONDOWN) { - PptBottomPageWidgetSeekBar(m.x, m.y, false); + auto moveDis = PptBottomPageWidgetSeekBar(m.x, m.y, false); + if (moveDis <= 20) + { + if (IsInRect(m.x, m.y, { long(pptUiWordsWidget[PptUiWordsWidgetID::BottomSide_LeftPageNum_Above].Left.v + 5.0f * pptComSetlist.bottomSideBothWidgetScale), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Y.v), long(pptUiWordsWidget[PptUiWordsWidgetID::BottomSide_LeftPageNum_Above].Right.v - 5.0f * pptComSetlist.bottomSideBothWidgetScale), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Height.v) })) + { + ViewPptShow(); + } + } + hiex::flushmessage_win32(EM_MOUSE, ppt_window); } } @@ -3421,7 +3608,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { PreviousPptSlides(); @@ -3478,7 +3665,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { @@ -3512,12 +3699,21 @@ void PptInteract() } } else if (PptInfoStateBuffer.TotalPage != -1) pptUiRoundRectWidgetTarget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget_NextPage].FillColor.v = RGBA(250, 250, 250, 160); + // 底部右侧全局拖动条 if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].X.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Y.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Width.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Height.v) })) { if (m.message == WM_LBUTTONDOWN) { - PptBottomPageWidgetSeekBar(m.x, m.y, true); + auto moveDis = PptBottomPageWidgetSeekBar(m.x, m.y, true); + if (moveDis <= 20) + { + if (IsInRect(m.x, m.y, { long(pptUiWordsWidget[PptUiWordsWidgetID::BottomSide_RightPageNum_Above].Left.v + 5.0f * pptComSetlist.bottomSideBothWidgetScale), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Y.v), long(pptUiWordsWidget[PptUiWordsWidgetID::BottomSide_RightPageNum_Above].Right.v - 5.0f * pptComSetlist.bottomSideBothWidgetScale), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget].Height.v) })) + { + ViewPptShow(); + } + } + hiex::flushmessage_win32(EM_MOUSE, ppt_window); } } @@ -3547,7 +3743,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { PreviousPptSlides(); @@ -3604,7 +3800,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { @@ -3638,12 +3834,24 @@ void PptInteract() } } else if (PptInfoStateBuffer.TotalPage != -1) pptUiRoundRectWidgetTarget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_NextPage].FillColor.v = RGBA(250, 250, 250, 160); + // 中部左侧全局拖动条 if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].X.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].Y.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].Width.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].Height.v) })) { if (m.message == WM_LBUTTONDOWN) { - PptMiddlePageWidgetSeekBar(m.x, m.y, false); + auto moveDis = PptMiddlePageWidgetSeekBar(m.x, m.y, false); + if (moveDis <= 20) + { + if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].X.v), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_PreviousPage].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_PreviousPage].Height.v + 5.0f * pptComSetlist.middleSideBothWidgetScale), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget].Width.v), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_NextPage].Y.v - 5.0f * pptComSetlist.middleSideBothWidgetScale) })) + { + ViewPptShow(); + } + } + hiex::flushmessage_win32(EM_MOUSE, ppt_window); } } @@ -3671,7 +3879,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { PreviousPptSlides(); @@ -3728,7 +3936,7 @@ void PptInteract() std::chrono::high_resolution_clock::time_point KeyboardInteractionManipulated = std::chrono::high_resolution_clock::now(); while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - KeyboardInteractionManipulated).count() >= 400) { @@ -3762,12 +3970,24 @@ void PptInteract() } } else if (PptInfoStateBuffer.TotalPage != -1) pptUiRoundRectWidgetTarget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_NextPage].FillColor.v = RGBA(250, 250, 250, 160); + // 中部右侧全局拖动条 if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].X.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].Y.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].Width.v), long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].Height.v) })) { if (m.message == WM_LBUTTONDOWN) { - PptMiddlePageWidgetSeekBar(m.x, m.y, true); + auto moveDis = PptMiddlePageWidgetSeekBar(m.x, m.y, true); + if (moveDis <= 20) + { + if (IsInRect(m.x, m.y, { long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].X.v), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_PreviousPage].Y.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_PreviousPage].Height.v + 5.0f * pptComSetlist.middleSideBothWidgetScale), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].X.v + pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget].Width.v), + long(pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_NextPage].Y.v - 5.0f * pptComSetlist.middleSideBothWidgetScale) })) + { + ViewPptShow(); + } + } + hiex::flushmessage_win32(EM_MOUSE, ppt_window); } } @@ -3971,7 +4191,7 @@ void StartDesktopDrawpadBlocker() { // 配置 json { - // if (_waccess((dataPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json").c_str(), 0) == 0) DdbReadInteraction(); + // if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json").c_str(), 0) == 0) DdbReadInteraction(); ddbInteractionSetList.hostPath = GetCurrentExePath(); @@ -3980,54 +4200,54 @@ void StartDesktopDrawpadBlocker() } // 配置 EXE - if (_waccess((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) + if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) { - if (_waccess((dataPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) + if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) { error_code ec; - filesystem::create_directories(dataPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::create_directories(pluginPath + L"\\DesktopDrawpadBlocker", ec); } - ExtractResource((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } else { string hash_sha256; { hashwrapper* myWrapper = new sha256wrapper(); - hash_sha256 = myWrapper->getHashFromFileW(dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); + hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); delete myWrapper; } if (hash_sha256 != ddbInteractionSetList.DdbSHA256) { - if (isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭旧版 DDB 并更新版本 DdbWriteInteraction(true, true); for (int i = 1; i <= 20; i++) { - if (!isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } } - ExtractResource((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } } // 启动 DDB - if (!isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } - else if (_waccess((dataPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == 0) + else if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == 0) { error_code ec; - filesystem::remove_all(dataPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::remove_all(pluginPath + L"\\DesktopDrawpadBlocker", ec); } } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" index b6b706b7..43e9ffc0 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" @@ -3,6 +3,7 @@ #include "IdtConfiguration.h" #include "IdtDrawpad.h" #include "IdtWindow.h" +#include "Inkeys/Other/IdtInputs.h" IdtAtomic rtsDown; // 表示触摸设备是否被按下 IdtAtomic rtsNum = 0, touchNum = 0, inkNum = 0; // 点、触摸点、触控笔的点击个数 @@ -14,7 +15,7 @@ unordered_map TouchPos; deque TouchTemp; vector TouchList; -LONG TouchCnt = 0; +IdtAtomic TouchCnt = 0; unordered_map TouchPointer; shared_mutex touchPosSm, touchSpeedSm, pointListSm, touchTempSm, touchPointerSm; @@ -149,37 +150,40 @@ IStylusSyncPlugin* CSyncEventHandlerRTS::Create(IRealTimeStylus* pRealTimeStylus HRESULT CSyncEventHandlerRTS::StylusDown(IRealTimeStylus* piRtsSrc, const StylusInfo* pStylusInfo, ULONG /*cPktCount*/, LONG* pPacket, LONG** /*ppInOutPkts*/) { // 这是一个按下状态 + + // 输入融合感知:触摸点、手写笔 TouchMode mode{}; TouchInfo info{}; - ULONG ulPacketProperties; - PACKET_PROPERTY* pPacketProperties; { ULONG ulTcidCount; TABLET_CONTEXT_ID* pTcids; piRtsSrc->GetAllTabletContextIds(&ulTcidCount, &pTcids); - piRtsSrc->GetPacketDescriptionData(pTcids[0], &mode.inkToDeviceScaleX, &mode.inkToDeviceScaleY, &ulPacketProperties, &pPacketProperties); + piRtsSrc->GetPacketDescriptionData(pTcids[0], &mode.inkToDeviceScaleX, &mode.inkToDeviceScaleY, &mode.packetPropertiesCount, &mode.pPacketProperties); + CoTaskMemFree(mode.pPacketProperties); CoTaskMemFree(pTcids); } - piRtsSrc->GetPacketDescriptionData(pStylusInfo->tcid, nullptr, nullptr, &ulPacketProperties, &pPacketProperties); + { + piRtsSrc->GetPacketDescriptionData(pStylusInfo->tcid, nullptr, nullptr, &mode.packetPropertiesCount, &mode.pPacketProperties); + } // 获取数据包信息 - for (int i = 0; i < ulPacketProperties; i++) + for (int i = 0; i < mode.packetPropertiesCount; i++) { - GUID guid = pPacketProperties[i].guid; + GUID guid = mode.pPacketProperties[i].guid; if (guid == GUID_PACKETPROPERTY_GUID_X) mode.pt.x = LONG(pPacket[i] * mode.inkToDeviceScaleX + 0.5); else if (guid == GUID_PACKETPROPERTY_GUID_Y) mode.pt.y = LONG(pPacket[i] * mode.inkToDeviceScaleY + 0.5); else if (guid == GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE) { - mode.logicalMin = pPacketProperties[i].PropertyMetrics.nLogicalMin; - mode.logicalMax = pPacketProperties[i].PropertyMetrics.nLogicalMax; + mode.pressureMin = mode.pPacketProperties[i].PropertyMetrics.nLogicalMin; + mode.pressureMax = mode.pPacketProperties[i].PropertyMetrics.nLogicalMax; - mode.pressure = double(pPacket[i] - mode.logicalMin) / double(mode.logicalMax - mode.logicalMin); + mode.pressure = double(pPacket[i] - mode.pressureMin) / double(mode.pressureMax - mode.pressureMin); } else if (guid == GUID_PACKETPROPERTY_GUID_WIDTH) mode.touchWidth = LONG(pPacket[i] * mode.inkToDeviceScaleX + 0.5); else if (guid == GUID_PACKETPROPERTY_GUID_HEIGHT) mode.touchHeight = LONG(pPacket[i] * mode.inkToDeviceScaleY + 0.5); } - CoTaskMemFree(pPacketProperties); + // 获取设备类型 int deviceType = 0; { @@ -197,7 +201,7 @@ HRESULT CSyncEventHandlerRTS::StylusDown(IRealTimeStylus* piRtsSrc, const Stylus else if (temp == TabletDeviceKind::TDK_Pen) mode.type = deviceType = 1; else { - if (KeyBoradDown[VK_RBUTTON] && !KeyBoradDown[VK_LBUTTON]) mode.type = deviceType = 3; + if (IdtInputs::IsKeyBoardDown(VK_RBUTTON) && !IdtInputs::IsKeyBoardDown(VK_LBUTTON)) mode.type = deviceType = 3; else mode.type = deviceType = 2; } } @@ -207,22 +211,27 @@ HRESULT CSyncEventHandlerRTS::StylusDown(IRealTimeStylus* piRtsSrc, const Stylus } mode.isInvertedCursor = pStylusInfo->bIsInvertedCursor; + // 如果是鼠标则不管 未来选项 + // if (mode.type == 2) return S_OK; + + LONG touchCnt = static_cast(++TouchCnt); + unique_lock lockTouchPointer(touchPointerSm); - TouchPointer[pStylusInfo->cid] = ++TouchCnt; - info.pid = TouchCnt; + TouchPointer[pStylusInfo->cid] = touchCnt; + info.pid = touchCnt; lockTouchPointer.unlock(); std::unique_lock lock1(touchPosSm); - TouchPos[TouchCnt] = mode; + TouchPos[touchCnt] = mode; lock1.unlock(); std::unique_lock lock2(touchSpeedSm); - TouchSpeed[TouchCnt] = 0; - PreviousPointPosition[TouchCnt].first = PreviousPointPosition[TouchCnt].second = -1; + TouchSpeed[touchCnt] = 0; + PreviousPointPosition[touchCnt].first = PreviousPointPosition[touchCnt].second = -1; lock2.unlock(); std::unique_lock lock3(pointListSm); - TouchList.push_back(TouchCnt); + TouchList.push_back(touchCnt); lock3.unlock(); info.mode = mode; @@ -234,37 +243,39 @@ HRESULT CSyncEventHandlerRTS::StylusDown(IRealTimeStylus* piRtsSrc, const Stylus rtsNum++, rtsDown = true; if (deviceType == 0) touchNum++; if (deviceType == 1) inkNum++; - TouchCnt %= 100000; // 光标隐藏提前指令 - if (setlist.hideTouchPointer) - SendMessage(drawpad_window, WM_SETCURSOR, (WPARAM)drawpad_window, MAKELPARAM(HTCLIENT, WM_MOUSEMOVE)); + if (setlist.hideTouchPointer) SendMessage(drawpad_window, WM_SETCURSOR, (WPARAM)drawpad_window, MAKELPARAM(HTCLIENT, WM_MOUSEMOVE)); return S_OK; } -HRESULT CSyncEventHandlerRTS::StylusUp(IRealTimeStylus*, const StylusInfo* pStylusInfo, ULONG /*cPktCount*/, LONG* pPacket, LONG** /*ppInOutPkts*/) +HRESULT CSyncEventHandlerRTS::StylusUp(IRealTimeStylus*, const StylusInfo* pStylusInfo, ULONG cPktCount, LONG* pPacket, LONG** /*ppInOutPkts*/) { // 这是一个抬起状态 + shared_lock lockTouchPointer(touchPointerSm); + auto pIt = TouchPointer.find(pStylusInfo->cid); + if (pIt == TouchPointer.end()) return S_OK; + int pid = pIt->second; + lockTouchPointer.unlock(); + shared_lock lock1(touchPosSm); + const auto mode = &TouchPos[pid]; + lock1.unlock(); + rtsNum = max(0, rtsNum - 1); if (rtsNum == 0) rtsDown = false; - int pid = TouchPointer[pStylusInfo->cid]; - auto it = std::find(TouchList.begin(), TouchList.end(), pid); if (it != TouchList.end()) { - shared_lock lockPointPosSm(touchPosSm); { - if (TouchPos.find(pid) != TouchPos.end()) - { - if (TouchPos[pid].type == 0) - touchNum = max(0, touchNum - 1); - if (TouchPos[pid].type == 1) - inkNum = max(0, inkNum - 1); - } + if (mode->type == 0) + touchNum = max(0, touchNum - 1); + if (mode->type == 1) + inkNum = max(0, inkNum - 1); + + if (mode->pPacketProperties != nullptr) CoTaskMemFree(mode->pPacketProperties); } - lockPointPosSm.unlock(); unique_lock lockPointListSm(pointListSm); TouchList.erase(it); @@ -284,31 +295,34 @@ HRESULT CSyncEventHandlerRTS::StylusUp(IRealTimeStylus*, const StylusInfo* pStyl return S_OK; } -HRESULT CSyncEventHandlerRTS::Packets(IRealTimeStylus* piRtsSrc, const StylusInfo* pStylusInfo, ULONG /*cPktCount*/, ULONG /*cPktBuffLength*/, LONG* pPacket, ULONG* /*pcInOutPkts*/, LONG** /*ppInOutPkts*/) +HRESULT CSyncEventHandlerRTS::Packets(IRealTimeStylus* piRtsSrc, const StylusInfo* pStylusInfo, ULONG cPktCount, ULONG /*cPktBuffLength*/, LONG* pPacket, ULONG* /*pcInOutPkts*/, LONG** /*ppInOutPkts*/) { // 这是一个移动状态 - ULONG ulPacketProperties; - PACKET_PROPERTY* pPacketProperties; - piRtsSrc->GetPacketDescriptionData(pStylusInfo->tcid, nullptr, nullptr, &ulPacketProperties, &pPacketProperties); + if (cPktCount == 0) return S_OK; shared_lock lockTouchPointer(touchPointerSm); - int pid = TouchPointer[pStylusInfo->cid]; + auto pIt = TouchPointer.find(pStylusInfo->cid); + if (pIt == TouchPointer.end()) return S_OK; + int pid = pIt->second; lockTouchPointer.unlock(); - shared_lock lock1(touchPosSm); TouchMode mode = TouchPos[pid]; lock1.unlock(); - for (int i = 0; i < ulPacketProperties; i++) + + const ULONG propsPerPacket = mode.packetPropertiesCount; // 单个数据包的属性数量 + ULONG lastPacketIndex = cPktCount - 1; // 1. 计算最后一个数据包的索引 (cPktCount - 1) + LONG* lastPacket = pPacket + (lastPacketIndex * propsPerPacket); // 2. 定位到最后一个数据包在 pPacket 缓冲区中的起始地址 + + for (int i = 0; i < mode.packetPropertiesCount; i++) { - GUID guid = pPacketProperties[i].guid; - if (guid == GUID_PACKETPROPERTY_GUID_X) mode.pt.x = LONG(pPacket[i] * mode.inkToDeviceScaleX + 0.5); - else if (guid == GUID_PACKETPROPERTY_GUID_Y) mode.pt.y = LONG(pPacket[i] * mode.inkToDeviceScaleY + 0.5); - else if (guid == GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE) mode.pressure = double(pPacket[i] - mode.logicalMin) / double(mode.logicalMax - mode.logicalMin); - else if (guid == GUID_PACKETPROPERTY_GUID_WIDTH) mode.touchWidth = LONG(pPacket[i] * mode.inkToDeviceScaleX + 0.5); - else if (guid == GUID_PACKETPROPERTY_GUID_HEIGHT) mode.touchHeight = LONG(pPacket[i] * mode.inkToDeviceScaleY + 0.5); + GUID guid = mode.pPacketProperties[i].guid; + if (guid == GUID_PACKETPROPERTY_GUID_X) mode.pt.x = LONG(lastPacket[i] * mode.inkToDeviceScaleX + 0.5); + else if (guid == GUID_PACKETPROPERTY_GUID_Y) mode.pt.y = LONG(lastPacket[i] * mode.inkToDeviceScaleY + 0.5); + else if (guid == GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE) mode.pressure = double(lastPacket[i] - mode.pressureMin) / double(mode.pressureMax - mode.pressureMin); + else if (guid == GUID_PACKETPROPERTY_GUID_WIDTH) mode.touchWidth = LONG(lastPacket[i] * mode.inkToDeviceScaleX + 0.5); + else if (guid == GUID_PACKETPROPERTY_GUID_HEIGHT) mode.touchHeight = LONG(lastPacket[i] * mode.inkToDeviceScaleY + 0.5); } - CoTaskMemFree(pPacketProperties); unique_lock lock2(touchPosSm); TouchPos[pid] = mode; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtRts.h" "b/\346\231\272\347\273\230\346\225\231/IdtRts.h" index aa4d742c..07589665 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtRts.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtRts.h" @@ -15,18 +15,22 @@ struct TouchMode bool isInvertedCursor; // 辅助变量 - FLOAT inkToDeviceScaleX; - FLOAT inkToDeviceScaleY; - LONG logicalMin; - LONG logicalMax; + FLOAT inkToDeviceScaleX = 1.0f; + FLOAT inkToDeviceScaleY = 1.0f; + LONG pressureMin = 0; + LONG pressureMax = 1; + ULONG packetPropertiesCount = 0; + PACKET_PROPERTY* pPacketProperties = nullptr; // 信息变量 int type; // 触摸0 手写笔1 左键2 右键3 }; extern unordered_map TouchSpeed; + extern unordered_map TouchPos; extern vector TouchList; +extern IdtAtomic TouchCnt; struct TouchInfo { LONG pid; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" index e07eff99..9767a50a 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" @@ -17,6 +17,7 @@ #include "IdtWindow.h" #include "CrashHandler/CrashHandler.h" #include "SuperTop/IdtSuperTop.h" +#include "Inkeys/Other/IdtInputs.h" #include "imgui/imgui.h" #include "imgui/imgui_impl_dx9.h" @@ -75,7 +76,7 @@ struct void SettingSeekBar() { - if (!KeyBoradDown[VK_LBUTTON]) return; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) return; POINT p; GetCursorPos(&p); @@ -85,7 +86,7 @@ void SettingSeekBar() while (1) { - if (!KeyBoradDown[VK_LBUTTON]) break; + if (!IdtInputs::IsKeyBoardDown(VK_LBUTTON)) break; POINT p; GetCursorPos(&p); @@ -183,6 +184,8 @@ void SettingMain() { ::ShowWindow(setting_window, SW_SHOWNOACTIVATE); + showWindow = true; + CreateDeviceD3D(setting_window); // 初始化 @@ -560,71 +563,87 @@ void SettingMain() font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; font_cfg.FontDataOwnedByAtlas = false; + font_cfg.MergeMode = false; - HRSRC hRes; - if (I18n::identifying == L"zh-TW" && 0 /*在翻译彻底完成之前还不行*/) hRes = FindResource(NULL, MAKEINTRESOURCE(258), L"TTF"); - else hRes = FindResource(NULL, MAKEINTRESOURCE(198), L"TTF"); - HGLOBAL hMem = LoadResource(NULL, hRes); - void* pLock = LockResource(hMem); - DWORD dwSize = SizeofResource(NULL, hRes); + ImWchar exclude_ranges[] = { 0xe81e, 0xe81e, 0 }; + font_cfg.GlyphExcludeRanges = exclude_ranges; + + { + HRSRC hRes; + if (I18n::identifying == L"zh-TW") hRes = FindResource(NULL, MAKEINTRESOURCE(258), L"TTF"); + else hRes = FindResource(NULL, MAKEINTRESOURCE(198), L"TTF"); + HGLOBAL hMem = LoadResource(NULL, hRes); + void* pLock = LockResource(hMem); + DWORD dwSize = SizeofResource(NULL, hRes); + + ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 30.0f * settingGlobalScale, &font_cfg); + } + if (I18n::identifying == L"zh-TW") + { + // 补充简化字 + HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(198), L"TTF"); + HGLOBAL hMem = LoadResource(NULL, hRes); + void* pLock = LockResource(hMem); + DWORD dwSize = SizeofResource(NULL, hRes); - ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 30.0f * settingGlobalScale, &font_cfg, io.Fonts->GetGlyphRangesChineseFull()); + ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 30.0f * settingGlobalScale, &font_cfg); + } // 字体自身偏小,假设是 28px 字高 } { - ImWchar icons_ranges[] = - { - 0xe713, 0xe713, // 设置 - 0xe8bb, 0xe8bb, // 关闭 - 0xe72b, 0xe72b, // 返回 - - 0xf167, 0xf167, // 信息 - 0xec61, 0xec61, // 完成 - 0xe814, 0xe814, // 警告 - 0xeb90, 0xeb90, // 错误 - - 0xe80f, 0xe80f, // 主页 - 0xf2b7, 0xf2b7, // 语言 - 0xe7b8, 0xe7b8, // 常规 - 0xee56, 0xee56, // 绘制 - 0xe74e, 0xe74e, // 保存 - 0xec4a, 0xec4a, // 性能 - 0xf259, 0xf259, // 预设 - 0xe70b, 0xe70b, // 组件 - 0xe74c, 0xe74c, // 插件 - 0xe765, 0xe765, // 快捷键 - 0xe81e, 0xe81e, // 软件配置 - 0xe946, 0xe946, // 软件版本 - 0xe716, 0xe716, // 社区名片 - 0xe789, 0xe789, // 赞助我们 - - 0xe711,0xe711, // 关闭程序 - 0xe72c,0xe72c, // 重启程序 - - 0 - }; + //ImWchar icons_ranges[] = + //{ + // 0xe713, 0xe713, // 设置 + // 0xe8bb, 0xe8bb, // 关闭 + // 0xe72b, 0xe72b, // 返回 + + // 0xf167, 0xf167, // 信息 + // 0xec61, 0xec61, // 完成 + // 0xe814, 0xe814, // 警告 + // 0xeb90, 0xeb90, // 错误 + + // 0xe80f, 0xe80f, // 主页 + // 0xf2b7, 0xf2b7, // 语言 + // 0xe7b8, 0xe7b8, // 常规 + // 0xee56, 0xee56, // 绘制 + // 0xe74e, 0xe74e, // 保存 + // 0xec4a, 0xec4a, // 性能 + // 0xf259, 0xf259, // 预设 + // 0xe70b, 0xe70b, // 组件 + // 0xe74c, 0xe74c, // 插件 + // 0xe765, 0xe765, // 快捷键 + // 0xe81e, 0xe81e, // 软件配置 + // 0xe946, 0xe946, // 软件版本 + // 0xe716, 0xe716, // 社区名片 + // 0xe789, 0xe789, // 赞助我们 + + // 0xe711,0xe711, // 关闭程序 + // 0xe72c,0xe72c, // 重启程序 + + // 0 + //}; ImFontConfig font_cfg; font_cfg.OversampleH = 1; font_cfg.OversampleV = 1; font_cfg.FontDataOwnedByAtlas = false; - font_cfg.MergeMode = true; font_cfg.GlyphOffset.y = 10.0f * settingGlobalScale; font_cfg.PixelSnapH = true; + font_cfg.MergeMode = true; HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(257), L"TTF"); HGLOBAL hMem = LoadResource(NULL, hRes); void* pLock = LockResource(hMem); DWORD dwSize = SizeofResource(NULL, hRes); - ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 36.0f * settingGlobalScale, &font_cfg, icons_ranges); + ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 36.0f * settingGlobalScale, &font_cfg); } { - ImWchar icons_ranges[] = + /*ImWchar icons_ranges[] = { 0xf900, 0xf907, 0 - }; + };*/ ImFontConfig font_cfg; font_cfg.OversampleH = 1; @@ -639,10 +658,10 @@ void SettingMain() void* pLock = LockResource(hMem); DWORD dwSize = SizeofResource(NULL, hRes); - ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 32.0f * settingGlobalScale, &font_cfg, icons_ranges); + ImFontMain = io.Fonts->AddFontFromMemoryTTF(pLock, dwSize, 32.0f * settingGlobalScale, &font_cfg); } - io.Fonts->Build(); + // io.Fonts->Build(); ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); { @@ -769,7 +788,7 @@ void SettingMain() float BottomMiddleWidth = pptComSetlist.bottomMiddleWidth; float BottomMiddleHeight = pptComSetlist.bottomMiddleHeight; - bool AutoKillWpsProcess = pptComSetlist.autoKillWpsProcess; + //bool AutoKillWpsProcess = pptComSetlist.autoKillWpsProcess; struct { @@ -791,6 +810,8 @@ void SettingMain() bool IntelligentClassFloating = ddbInteractionSetList.intercept.intelligentClassFloating; bool SeewoDesktopAnnotationFloating = ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating; bool SeewoDesktopSideBarFloating = ddbInteractionSetList.intercept.seewoDesktopSideBarFloating; + bool Iclass30Floating = ddbInteractionSetList.intercept.iclass30Floating; + bool Iclass30SidebarFloating = ddbInteractionSetList.intercept.iclass30SidebarFloating; }intercept; } Ddb; @@ -802,10 +823,12 @@ void SettingMain() bool ComponentShortcutButtonSystemLockWorkStation = setlist.component.shortcutButton.system.lockWorkStation; bool ComponentShortcutButtonKeyboardKeyboardesc = setlist.component.shortcutButton.keyboard.keyboardesc; bool ComponentShortcutButtonKeyboardKeyboardAltF4 = setlist.component.shortcutButton.keyboard.keyboardAltF4; + bool ComponentShortcutButtonRollCallIslandCaller = setlist.component.shortcutButton.rollCall.IslandCaller; + bool ComponentShortcutButtonRollCallSecRandom = setlist.component.shortcutButton.rollCall.SecRandom; + bool ComponentShortcutButtonRollCallNamePicker = setlist.component.shortcutButton.rollCall.NamePicker; bool ComponentShortcutButtonLinkageClassislandSettings = setlist.component.shortcutButton.linkage.classislandSettings; bool ComponentShortcutButtonLinkageClassislandProfile = setlist.component.shortcutButton.linkage.classislandProfile; bool ComponentShortcutButtonLinkageClassislandClassswap = setlist.component.shortcutButton.linkage.classislandClassswap; - bool ComponentShortcutButtonLinkageClassislandIslandCaller = setlist.component.shortcutButton.linkage.classislandIslandCaller; // ========== @@ -1677,10 +1700,16 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f * settingGlobalScale, 8.0f * settingGlobalScale)); - vector vec; - vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/en-US")).c_str())); - vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/zh-CN")).c_str())); - vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/zh-TW")).c_str())); + //vector vec; + //vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/en-US")).c_str())); + //vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/zh-CN")).c_str())); + //vec.emplace_back(_strdup((IA("SettingsUI/Language/UI/Language/zh-TW")).c_str())); + // TODO + + vector vec; + vec.emplace_back(IA("SettingsUI/Language/UI/Language/en-US")); + vec.emplace_back(IA("SettingsUI/Language/UI/Language/zh-CN")); + vec.emplace_back(IA("SettingsUI/Language/UI/Language/zh-TW")); { int item_count = vec.size(); @@ -1688,14 +1717,14 @@ void SettingMain() float popup_height = item_count * item_height + ImGui::GetStyle().WindowPadding.y * 2 * settingGlobalScale + 16.0f * settingGlobalScale; ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, popup_height)); } - if (ImGui::BeginCombo("##语言", vec[SelectLanguage])) + if (ImGui::BeginCombo("##语言", vec[SelectLanguage].c_str())) { for (int i = 0; i < vec.size(); i++) { ImGui::Dummy(ImVec2(0, 8.0f * settingGlobalScale)); bool is_selected = (SelectLanguage == i); - if (ImGui::Selectable(vec[i], is_selected)) + if (ImGui::Selectable(vec[i].c_str(), is_selected)) { SelectLanguage = i; if (setlist.selectLanguage != SelectLanguage) @@ -1715,7 +1744,7 @@ void SettingMain() ImGui::Dummy(ImVec2(0, 8.0f * settingGlobalScale)); ImGui::EndCombo(); } - for (char* ptr : vec) free(ptr), ptr = nullptr; + //for (char* ptr : vec) free(ptr), ptr = nullptr; } { @@ -2039,6 +2068,120 @@ void SettingMain() ImGui::TextUnformatted(IA("SettingsUI/Version/N").c_str()); } + if (AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateLimit) + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 244, 206, 255)); + ImGui::BeginChild("软件版本#10086", { 750.0f * settingGlobalScale,120.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(157, 93, 0, 255)); + ImGui::TextUnformatted("\ue814"); + } + { + ImGui::SetCursorPos({ 60.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); + ImGui::BeginChild("软件版本-提示10086", { 560.0f * settingGlobalScale,80.0f * settingGlobalScale }, false); + + { + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextWrapped(IA("SettingsUI/Version/LimitUpdate/N").c_str()); + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPos({ 630.0f * settingGlobalScale, cursosPosY + 15.0f * settingGlobalScale }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(255, 255, 255, 179)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(249, 249, 249, 128)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(249, 249, 249, 77)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 228)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0, 0, 0, 15)); + if (ImGui::Button(IA("SettingsUI/Version/LimitUpdate/Info").c_str(), { 100.0f * settingGlobalScale,30.0f * settingGlobalScale })) + { + ShellExecuteW(0, 0, L"https://www.inkeys.top/win7", 0, 0, SW_SHOW); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + if (AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateInkeys3) + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 244, 206, 255)); + ImGui::BeginChild("软件版本#1008611", { 750.0f * settingGlobalScale,120.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(157, 93, 0, 255)); + ImGui::TextUnformatted("\ue814"); + } + { + ImGui::SetCursorPos({ 60.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); + ImGui::BeginChild("软件版本-提示1008611", { 560.0f * settingGlobalScale,80.0f * settingGlobalScale }, false); + + { + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextWrapped(IA("SettingsUI/Version/Inkeys3Update/N").c_str()); + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPos({ 630.0f * settingGlobalScale, cursosPosY + 15.0f * settingGlobalScale }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(255, 255, 255, 179)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(249, 249, 249, 128)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonActive, IM_COL32(249, 249, 249, 77)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 228)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0, 0, 0, 15)); + if (ImGui::Button(IA("SettingsUI/Version/Inkeys3Update/Download").c_str(), { 100.0f * settingGlobalScale,30.0f * settingGlobalScale })) + { + mandatoryUpdate = true; + AutomaticUpdateState = AutomaticUpdateStateEnum::UpdateObtainInformation; + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + if (AutomaticUpdateState == AutomaticUpdateStateEnum::UpdateNew) { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f * settingGlobalScale); @@ -2147,7 +2290,7 @@ void SettingMain() { ImGui::SetCursorPos({ 35.0f * settingGlobalScale,20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[1], ImVec2((float)settingSign[1].width, (float)settingSign[1].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[1], ImVec2((float)settingSign[1].width, (float)settingSign[1].height)); } { ImGui::SetCursorPosY(ImGui::GetCursorPosY()); @@ -2320,6 +2463,7 @@ void SettingMain() } ImGui::EndChild(); } + if (AutomaticUpdateState != AutomaticUpdateStateEnum::UpdateInkeys3) { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 30.0f * settingGlobalScale); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); @@ -2439,7 +2583,7 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 255, 255, 0)); - ImGui::BeginChild("软件版本#4", { 750.0f * settingGlobalScale,315.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("软件版本#4", { 750.0f * settingGlobalScale,330.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); { ImGui::SetCursorPos({ 0.0f * settingGlobalScale, 0.0f * settingGlobalScale }); @@ -2513,7 +2657,7 @@ void SettingMain() ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("更新通道-提示", { 750.0f * settingGlobalScale,80.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("更新通道-提示", { 750.0f * settingGlobalScale,95.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { @@ -2527,7 +2671,7 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); - ImGui::BeginChild("组件-提示", { 670.0f * settingGlobalScale,40.0f * settingGlobalScale }, false); + ImGui::BeginChild("组件-提示", { 670.0f * settingGlobalScale,55.0f * settingGlobalScale }, false); { ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); @@ -2588,8 +2732,10 @@ void SettingMain() vector vec; vec.emplace_back(_strdup((IA("SettingsUI/Version/Update/Channel/LTS")).c_str())); vec.emplace_back(_strdup((IA("SettingsUI/Version/Update/Channel/Insider")).c_str())); + vec.emplace_back(_strdup((IA("SettingsUI/Version/Update/Channel/Canary")).c_str())); if (setlist.UpdateChannel == "Insider") UpdateChannelMode = 1; + else if (setlist.UpdateChannel == "Canary") UpdateChannelMode = 2; else UpdateChannelMode = 0; { @@ -2609,10 +2755,13 @@ void SettingMain() { UpdateChannelMode = i; if ((UpdateChannelMode == 0 && setlist.UpdateChannel != "LTS") || - (UpdateChannelMode == 1 && setlist.UpdateChannel != "Insider")) + (UpdateChannelMode == 1 && setlist.UpdateChannel != "Insider") || + (UpdateChannelMode == 2 && setlist.UpdateChannel != "Canary")) { if (UpdateChannelMode == 1) setlist.UpdateChannel = "Insider"; + else if (UpdateChannelMode == 2) setlist.UpdateChannel = "Canary"; else setlist.UpdateChannel = "LTS"; + WriteSetting(); AutomaticUpdateState = AutomaticUpdateStateEnum::UpdateObtainInformation; @@ -3275,7 +3424,7 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); - ImGui::BeginChild("置顶间隔-介绍", { 410.0f * settingGlobalScale,50.0f * settingGlobalScale }, false); + ImGui::BeginChild("置顶间隔-介绍", { 510.0f * settingGlobalScale,50.0f * settingGlobalScale }, false); { ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); @@ -4944,7 +5093,7 @@ void SettingMain() { { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, 20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[5], ImVec2((float)settingSign[5].width, (float)settingSign[5].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[5], ImVec2((float)settingSign[5].width, (float)settingSign[5].height)); } { ImGui::SetCursorPos({ 60.0f * settingGlobalScale, 20.0f * settingGlobalScale }); @@ -5010,7 +5159,7 @@ void SettingMain() { { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, 20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[10], ImVec2((float)settingSign[10].width, (float)settingSign[10].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[10], ImVec2((float)settingSign[10].width, (float)settingSign[10].height)); } { ImGui::SetCursorPos({ 60.0f * settingGlobalScale, 20.0f * settingGlobalScale }); @@ -5073,7 +5222,7 @@ void SettingMain() { { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, 20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[8], ImVec2((float)settingSign[8].width, (float)settingSign[8].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[8], ImVec2((float)settingSign[8].width, (float)settingSign[8].height)); } { ImGui::SetCursorPos({ 60.0f * settingGlobalScale, 20.0f * settingGlobalScale }); @@ -5136,7 +5285,7 @@ void SettingMain() { { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, 20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[6], ImVec2((float)settingSign[6].width, (float)settingSign[6].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[6], ImVec2((float)settingSign[6].width, (float)settingSign[6].height)); } { ImGui::SetCursorPos({ 60.0f * settingGlobalScale, 20.0f * settingGlobalScale }); @@ -5290,7 +5439,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0, 0, 0, 15)); if (ImGui::Button(IA("SettingsUI/PlugIn/PPTHelper/Solve").c_str(), { 100.0f * settingGlobalScale,30.0f * settingGlobalScale })) { - ShellExecuteW(0, 0, L"https://blog.csdn.net/alan16356/article/details/143618256?fromshare=blogdetail&sharetype=blogdetail&sharerId=143618256&sharerefer=PC&sharesource=alan16356&sharefrom=from_link", 0, 0, SW_SHOW); + ShellExecuteW(0, 0, L"https://www.inkeys.top/tutorial/ppt-com", 0, 0, SW_SHOW); } } @@ -5346,7 +5495,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0, 0, 0, 15)); if (ImGui::Button(IA("SettingsUI/PlugIn/PPTHelper/Solve").c_str(), { 100.0f * settingGlobalScale,30.0f * settingGlobalScale })) { - ShellExecuteW(0, 0, L"https://blog.csdn.net/alan16356/article/details/143625981?fromshare=blogdetail&sharetype=blogdetail&sharerId=143625981&sharerefer=PC&sharesource=alan16356&sharefrom=from_link", 0, 0, SW_SHOW); + ShellExecuteW(0, 0, L"https://www.inkeys.top/tutorial/ppt-admin", 0, 0, SW_SHOW); } } @@ -6224,6 +6373,8 @@ void SettingMain() } ImGui::EndChild(); } + + /* { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 30.0f * settingGlobalScale); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); @@ -6315,6 +6466,7 @@ void SettingMain() } ImGui::EndChild(); } + */ { ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; @@ -6968,48 +7120,48 @@ void SettingMain() if (ddbInteractionSetList.enable) { ddbInteractionSetList.hostPath = GetCurrentExePath(); - if (_waccess((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) + if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) { - if (_waccess((dataPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) + if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) { error_code ec; - filesystem::create_directories(dataPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::create_directories(pluginPath + L"\\DesktopDrawpadBlocker", ec); } - ExtractResource((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } else { string hash_sha256; { hashwrapper* myWrapper = new sha256wrapper(); - hash_sha256 = myWrapper->getHashFromFileW(dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); + hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); delete myWrapper; } if (hash_sha256 != ddbInteractionSetList.DdbSHA256) { - if (isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭旧版 DDB 并更新版本 DdbWriteInteraction(true, true); for (int i = 1; i <= 20; i++) { - if (!isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } } - ExtractResource((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } } // 启动 DDB - if (!isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } else @@ -7019,13 +7171,13 @@ void SettingMain() // 历史遗留问题处理 { // 取消开机自动启动 - SetStartupState(false, dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe", L"$Inkeys_DesktopDrawpadBlocker"); + SetStartupState(false, pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe", L"$Inkeys_DesktopDrawpadBlocker"); // 移除开机自启标识 - if (_waccess((dataPath + L"\\DesktopDrawpadBlocker\\start_up.signal").c_str(), 0) == 0) + if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\start_up.signal").c_str(), 0) == 0) { error_code ec; - filesystem::remove(dataPath + L"\\DesktopDrawpadBlocker\\start_up.signal", ec); + filesystem::remove(pluginPath + L"\\DesktopDrawpadBlocker\\start_up.signal", ec); } } } @@ -7103,20 +7255,20 @@ void SettingMain() ddbInteractionSetList.runAsAdmin = Ddb.RunAsAdmin; WriteSetting(); - if (isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭 DDB 并重新启动 DdbWriteInteraction(true, true); for (int i = 1; i <= 20; i++) { - if (!isProcessRunning((dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (dataPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } } @@ -7237,7 +7389,7 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 255, 255, 0)); - ImGui::BeginChild("同类软件悬浮窗拦截助手#3", { 750.0f * settingGlobalScale,915.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("同类软件悬浮窗拦截助手#3", { 750.0f * settingGlobalScale,1060.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); { ImGui::SetCursorPos({ 0.0f * settingGlobalScale, 0.0f * settingGlobalScale }); @@ -7648,7 +7800,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("需要管理员权限。"); + ImGui::TextUnformatted("支持在白板时自动恢复,需要管理员权限。"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -7888,6 +8040,108 @@ void SettingMain() } ImGui::EndChild(); } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("精确控制#11", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("C30智能教学 桌面悬浮窗"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("包括PPT控件,支持在白板时自动恢复,需要管理员权限。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!Ddb.intercept.Iclass30Floating) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##C30智能教学 桌面悬浮窗", &Ddb.intercept.Iclass30Floating, config); + + if (ddbInteractionSetList.intercept.iclass30Floating != Ddb.intercept.Iclass30Floating) + { + ddbInteractionSetList.intercept.iclass30Floating = Ddb.intercept.Iclass30Floating; + WriteSetting(); + + DdbWriteInteraction(true, false); + } + } + + // Separator + cursosPosY = ImGui::GetCursorPosY(); + { + ImGui::SetCursorPosY(cursosPosY + 25.0f * settingGlobalScale); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Separator, IM_COL32(229, 229, 229, 255)); + ImGui::Separator(); + } + + cursosPosY = ImGui::GetCursorPosY(); + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("C30智能教学 侧栏悬浮窗"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("需要管理员权限。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!Ddb.intercept.Iclass30SidebarFloating) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##C30智能教学 侧栏悬浮窗", &Ddb.intercept.Iclass30SidebarFloating, config); + + if (ddbInteractionSetList.intercept.iclass30SidebarFloating != Ddb.intercept.Iclass30SidebarFloating) + { + ddbInteractionSetList.intercept.iclass30SidebarFloating = Ddb.intercept.Iclass30SidebarFloating; + WriteSetting(); + + DdbWriteInteraction(true, false); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } { if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; @@ -8371,7 +8625,185 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 255, 255, 0)); - ImGui::BeginChild("组件#4", { 750.0f * settingGlobalScale,360.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("组件#4", { 750.0f * settingGlobalScale,250.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + { + ImGui::SetCursorPos({ 0.0f * settingGlobalScale, 0.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("点名器"); + } + + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("点名器#1", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("IslandCaller 随机点名"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("需要安装 ClassIsland 插件 IslandCaller,并需要 ClassIsland 注册 Url 协议。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!ComponentShortcutButtonRollCallIslandCaller) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##IslandCaller 随机点名", &ComponentShortcutButtonRollCallIslandCaller, config); + + if (setlist.component.shortcutButton.rollCall.IslandCaller != ComponentShortcutButtonRollCallIslandCaller) + { + setlist.component.shortcutButton.rollCall.IslandCaller = ComponentShortcutButtonRollCallIslandCaller; + WriteSetting(); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("点名器#2", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("SecRandom 随机点名"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("需要 SecRandom 注册 Url 协议。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!ComponentShortcutButtonRollCallSecRandom) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##SecRandom 随机点名", &ComponentShortcutButtonRollCallSecRandom, config); + + if (setlist.component.shortcutButton.rollCall.SecRandom != ComponentShortcutButtonRollCallSecRandom) + { + setlist.component.shortcutButton.rollCall.SecRandom = ComponentShortcutButtonRollCallSecRandom; + WriteSetting(); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("点名器#3", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("NamePicker 随机点名"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("需要 NamePicker 注册 Url 协议。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!ComponentShortcutButtonRollCallNamePicker) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##NamePicker 随机点名", &ComponentShortcutButtonRollCallNamePicker, config); + + if (setlist.component.shortcutButton.rollCall.NamePicker != ComponentShortcutButtonRollCallNamePicker) + { + setlist.component.shortcutButton.rollCall.NamePicker = ComponentShortcutButtonRollCallNamePicker; + WriteSetting(); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 30.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 255, 255, 0)); + ImGui::BeginChild("组件#5", { 750.0f * settingGlobalScale,285.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); { ImGui::SetCursorPos({ 0.0f * settingGlobalScale, 0.0f * settingGlobalScale }); @@ -8555,58 +8987,6 @@ void SettingMain() } ImGui::EndChild(); } - { - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); - PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("ClassIsland 联动#2", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - - float cursosPosY = 0; - { - ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); - ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("IslandCaller 随机点名"); - } - { - ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); - ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("需要安装 ClassIsland 插件 IslandCaller。"); - } - { - ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!ComponentShortcutButtonLinkageClassislandIslandCaller) - { - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); - } - else - { - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); - } - ImGui::Toggle("##IslandCaller 随机点名", &ComponentShortcutButtonLinkageClassislandIslandCaller, config); - - if (setlist.component.shortcutButton.linkage.classislandIslandCaller != ComponentShortcutButtonLinkageClassislandIslandCaller) - { - setlist.component.shortcutButton.linkage.classislandIslandCaller = ComponentShortcutButtonLinkageClassislandIslandCaller; - WriteSetting(); - } - } - - { - if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; - if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; - while (PushFontNum) PushFontNum--, ImGui::PopFont(); - } - ImGui::EndChild(); - } { if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; @@ -8728,7 +9108,7 @@ void SettingMain() ImGui::BeginChild("赞助我们", { (750.0f + 30.0f) * settingGlobalScale,608.0f * settingGlobalScale }, true); ImGui::SetCursorPos({ 50.0f * settingGlobalScale,20.0f * settingGlobalScale }); - ImGui::Image((void*)TextureSettingSign[9], ImVec2((float)settingSign[9].width, (float)settingSign[9].height)); + ImGui::Image((ImTextureID)(intptr_t)TextureSettingSign[9], ImVec2((float)settingSign[9].width, (float)settingSign[9].height)); { ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 30.0f); @@ -9062,7 +9442,8 @@ void SettingMain() ImGui::SetCursorPosX(ImGui::GetCursorPos().x + 10.0f * settingGlobalScale); if (ImGui::TextLink(IA("SettingsUI/Update/ManualDownload").c_str())) { - ShellExecuteW(0, 0, L"https://www.inkeys.top/", 0, 0, SW_SHOW); + if (isWindows8OrGreater) ShellExecuteW(0, 0, L"https://www.inkeys.top/", 0, 0, SW_SHOW); + else ShellExecuteW(0, 0, L"https://www.inkeys.top/win7", 0, 0, SW_SHOW); } } @@ -9389,6 +9770,7 @@ void SettingMain() if (setlist.UpdateChannel == "LTS") channel = " (" + IA("SettingsUI/Update/Channel/LTS") + ")"; else if (setlist.UpdateChannel == "Insider") channel = " (" + IA("SettingsUI/Update/Channel/Insider") + ")"; else if (setlist.UpdateChannel == "Dev") channel = " (" + IA("SettingsUI/Update/Channel/Dev") + ")"; + else if (setlist.UpdateChannel == "Canary") channel = " (" + IA("SettingsUI/Update/Channel/Canary") + ")"; ImGui::TextUnformatted((IA("SettingsUI/Update/Latest") + channel).c_str()); } @@ -9423,6 +9805,7 @@ void SettingMain() if (setlist.UpdateChannel == "LTS") channel = " (" + IA("SettingsUI/Update/Channel/LTS") + ")"; else if (setlist.UpdateChannel == "Insider") channel = " (" + IA("SettingsUI/Update/Channel/Insider") + ")"; else if (setlist.UpdateChannel == "Dev") channel = " (" + IA("SettingsUI/Update/Channel/Dev") + ")"; + else if (setlist.UpdateChannel == "Canary") channel = " (" + IA("SettingsUI/Update/Channel/Canary") + ")"; ImGui::TextUnformatted((IA("SettingsUI/Update/Newer") + channel).c_str()); } @@ -9473,6 +9856,90 @@ void SettingMain() ImGui::EndChild(); } + else if (AutomaticUpdateState == UpdateLimit) + { + ImGui::SetCursorPos({ 170.0f * settingGlobalScale, 660.0f * settingGlobalScale }); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 244, 206, 255)); + ImGui::BeginChild("更新状态-提示", { 675.0f * settingGlobalScale,30.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 10.0f * settingGlobalScale, cursosPosY + 8.0f * settingGlobalScale }); + ImFontMain->Scale = 0.55f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(157, 93, 0, 255)); + ImGui::TextUnformatted("\ue814"); + } + { + ImGui::SetCursorPos({ 36.0f * settingGlobalScale, cursosPosY + 8.0f * settingGlobalScale }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted(IA("SettingsUI/Update/Limit").c_str()); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_TextLink, IM_COL32(0, 95, 183, 255)); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPos().x + 10.0f * settingGlobalScale); + if (ImGui::TextLink(IA("SettingsUI/Update/Info").c_str())) + { + ShellExecuteW(0, 0, L"https://www.inkeys.top/win7", 0, 0, SW_SHOW); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + else if (AutomaticUpdateState == UpdateInkeys3) + { + ImGui::SetCursorPos({ 170.0f * settingGlobalScale, 660.0f * settingGlobalScale }); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("更新状态-提示", { 675.0f * settingGlobalScale,30.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 10.0f * settingGlobalScale, cursosPosY + 8.0f * settingGlobalScale }); + ImFontMain->Scale = 0.55f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 95, 183, 255)); + ImGui::TextUnformatted("\uf167"); + } + { + ImGui::SetCursorPos({ 36.0f * settingGlobalScale, cursosPosY + 8.0f * settingGlobalScale }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted(IA("SettingsUI/Update/Limit2").c_str()); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_TextLink, IM_COL32(0, 95, 183, 255)); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPos().x + 10.0f * settingGlobalScale); + if (ImGui::TextLink(IA("SettingsUI/Update/Info2").c_str())) + { + mandatoryUpdate = true; + AutomaticUpdateState = AutomaticUpdateStateEnum::UpdateObtainInformation; + } + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_TextLink, IM_COL32(0, 95, 183, 255)); + ImGui::SameLine(); + ImGui::SetCursorPosX(ImGui::GetCursorPos().x + 10.0f * settingGlobalScale); + if (ImGui::TextLink(IA("SettingsUI/Update/Info3").c_str())) + { + ShellExecuteW(0, 0, L"https://www.inkeys.top/inkeys3", 0, 0, SW_SHOW); + } + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { ImGui::SetCursorPos({ 850.0f * settingGlobalScale, 660.0f * settingGlobalScale }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); @@ -9519,7 +9986,7 @@ void SettingMain() if (!test.select) break; if (!showWindow) { - ::ShowWindow(setting_window, SW_SHOW); + ::ShowWindow(setting_window, SW_SHOWNOACTIVATE); showWindow = true; } } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtStart.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtStart.cpp" index c8c92e59..50831043 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtStart.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtStart.cpp" @@ -60,7 +60,7 @@ void StartForInkeys() GlobalMemoryStatusEx(&memoryStatus); // 系统是否高于或是 Windows10 - hardwareInfo.isWindows10OrGreater = (GetWindowsVersion() >= 10); + hardwareInfo.isWindows10OrGreater = (GetWindowsVersion().majorVersion >= 10); // 是否拥有触摸设备 int digitizerStatus = GetSystemMetrics(SM_DIGITIZER); @@ -68,9 +68,9 @@ void StartForInkeys() } } -int GetWindowsVersion() +IdtSysVersionStruct GetWindowsVersion() { - int ret = 0; + IdtSysVersionStruct ret = { 0,0,0 }; // 动态加载 ntdll.dll 并获取 RtlGetVersion 函数地址 HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); @@ -84,7 +84,11 @@ int GetWindowsVersion() rovi.dwOSVersionInfoSize = sizeof(rovi); // 调用 RtlGetVersion 获取系统版本信息 if (pRtlGetVersion(&rovi) == 0) - ret = rovi.dwMajorVersion; + { + ret.majorVersion = static_cast(rovi.dwMajorVersion); + ret.minorVersion = static_cast(rovi.dwMinorVersion); + ret.buildNumber = static_cast(rovi.dwBuildNumber); + } } } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtStart.h" "b/\346\231\272\347\273\230\346\225\231/IdtStart.h" index 5030a267..35346ad2 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtStart.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtStart.h" @@ -150,6 +150,13 @@ void StartForInkeys(); // -------------------------------------------------- // 其他杂项 +struct IdtSysVersionStruct +{ + int majorVersion; + int minorVersion; + int buildNumber; +}; + typedef LONG(WINAPI* RtlGetVersionPtr)(RTL_OSVERSIONINFOW*); -int GetWindowsVersion(); +IdtSysVersionStruct GetWindowsVersion(); bool hasTouchDevice(); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtText.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtText.cpp" index 6b4ba065..096976c6 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtText.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtText.cpp" @@ -6,15 +6,31 @@ StringFormat stringFormat; StringFormat stringFormat_left; RECT words_rect, dwords_rect, pptwords_rect; +//wstring utf8ToUtf16(const string& input) +//{ +// wstring_convert, wchar_t> converter; +// return converter.from_bytes(input); +//} +//string utf16ToUtf8(const wstring& input) +//{ +// wstring_convert, wchar_t> converter; +// return converter.to_bytes(input); +//} wstring utf8ToUtf16(const string& input) { - wstring_convert, wchar_t> converter; - return converter.from_bytes(input); + if (input.empty()) return wstring(); + int len = MultiByteToWideChar(CP_UTF8, 0, input.data(), (int)input.size(), nullptr, 0); + wstring result(len, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, input.data(), (int)input.size(), &result[0], len); + return result; } string utf16ToUtf8(const wstring& input) { - wstring_convert, wchar_t> converter; - return converter.to_bytes(input); + if (input.empty()) return string(); + int len = WideCharToMultiByte(CP_UTF8, 0, input.data(), (int)input.size(), nullptr, 0, nullptr, nullptr); + string result(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, input.data(), (int)input.size(), &result[0], len, nullptr, nullptr); + return result; } wstring bstrToWstring(const _bstr_t& bstr) diff --git "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" index 163793a1..7b195678 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" @@ -30,6 +30,17 @@ wstring convertToHttp(const wstring& url) else return httpPrefix + url; } +string GetRefererInfo() +{ + string ret; + ret += utf16ToUtf8(editionDate) + ","; + ret += utf16ToUtf8(programArchitecture) + ","; + ret += setlist.UpdateChannel + ","; + ret += setlist.enableAutoUpdate ? "true," : "false,"; + ret += utf16ToUtf8(windowsEdition); + return ret; +} + EditionInfoClass GetEditionInfo(string channel, string arch) { /* @@ -41,7 +52,7 @@ EditionInfoClass GetEditionInfo(string channel, string arch) */ EditionInfoClass retEditionInfo; - string editionInformation = GetEditionInformation(); + string editionInformation = GetEditionInformation(GetRefererInfo()); if (editionInformation == "Error") { retEditionInfo.errorCode = 1; @@ -63,6 +74,7 @@ EditionInfoClass GetEditionInfo(string channel, string arch) if (editionInfoValue[channel].isMember("edition_date") && editionInfoValue[channel]["edition_date"].isString()) retEditionInfo.editionDate = utf8ToUtf16(editionInfoValue[channel]["edition_date"].asString()); else informationCompliance = false; if (editionInfoValue[channel].isMember("edition_code") && editionInfoValue[channel]["edition_code"].isString()) retEditionInfo.editionCode = utf8ToUtf16(editionInfoValue[channel]["edition_code"].asString()); + if (editionInfoValue[channel].isMember("inkeys3") && editionInfoValue[channel]["inkeys3"].isBool()) retEditionInfo.isInkeys3 = editionInfoValue[channel]["inkeys3"].asBool(); if (editionInfoValue[channel].isMember("explain") && editionInfoValue[channel]["explain"].isString()) retEditionInfo.explain = utf8ToUtf16(editionInfoValue[channel]["explain"].asString()); if (editionInfoValue[channel].isMember("hash") && editionInfoValue[channel]["hash"].isObject()) { @@ -200,7 +212,7 @@ AutomaticUpdateStateEnum DownloadNewProgram(DownloadNewProgramStateClass* state, state->fileSize.store(editionInfo.fileSize.load()); wstring timestamp = getTimestamp(); - bool reslut = DownloadEdition(domain, path, globalPath + L"installer\\", L"new_procedure_" + timestamp + L".tmp", state->downloadedSize); + bool reslut = DownloadEdition(domain, path, globalPath + L"installer\\", L"new_procedure_" + timestamp + L".tmp", state->downloadedSize, GetRefererInfo()); if (reslut) { @@ -287,6 +299,8 @@ AutomaticUpdateStateEnum DownloadNewProgram(DownloadNewProgramStateClass* state, return UpdateRestart; } +bool isWindows8OrGreater; +wstring windowsEdition; void AutomaticUpdate() { bool state = true; @@ -332,92 +346,109 @@ void AutomaticUpdate() //下载最新版本 if (state && editionInfo.editionDate != L"" && ((editionInfo.editionDate > editionDate && setlist.enableAutoUpdate) || mandatoryUpdate)) { - update = true; - if (_waccess((globalPath + L"installer\\update.json").c_str(), 4) == 0 && !mandatoryUpdate) + // 无法使用自动更新以及自动修复的情况 + if (editionInfo.isInkeys3 && !mandatoryUpdate) { - wstring tedition, tpath; - string thash_md5, thash_sha256; - string tarch; - - Json::Reader reader; - Json::Value root; + if (!isWindows8OrGreater) + { + AutomaticUpdateState = UpdateLimit; + state = false; + } + else + { + AutomaticUpdateState = UpdateInkeys3; + state = false; + } + } + else + { + update = true; + if (_waccess((globalPath + L"installer\\update.json").c_str(), 4) == 0 && !mandatoryUpdate) + { + wstring tedition, tpath; + string thash_md5, thash_sha256; + string tarch; - ifstream readjson; - readjson.imbue(locale("zh_CN.UTF8")); - readjson.open((globalPath + L"installer\\update.json").c_str()); + Json::Reader reader; + Json::Value root; - bool fileDamage = false; - if (reader.parse(readjson, root)) - { - if (root.isMember("edition")) tedition = utf8ToUtf16(root["edition"].asString()); - else fileDamage = true; - if (root.isMember("path")) tpath = utf8ToUtf16(root["path"].asString()); - else fileDamage = true; + ifstream readjson; + readjson.imbue(locale("zh_CN.UTF8")); + readjson.open((globalPath + L"installer\\update.json").c_str()); - if (root.isMember("hash")) + bool fileDamage = false; + if (reader.parse(readjson, root)) { - if (root["hash"].isMember("md5")) thash_md5 = root["hash"]["md5"].asString(); + if (root.isMember("edition")) tedition = utf8ToUtf16(root["edition"].asString()); else fileDamage = true; - if (root["hash"].isMember("sha256")) thash_sha256 = root["hash"]["sha256"].asString(); + if (root.isMember("path")) tpath = utf8ToUtf16(root["path"].asString()); else fileDamage = true; - } - else fileDamage = true; - // 架构确定 - if (root.isMember("arch")) tarch = root["arch"].asString(); - else fileDamage = true; - } - readjson.close(); + if (root.isMember("hash")) + { + if (root["hash"].isMember("md5")) thash_md5 = root["hash"]["md5"].asString(); + else fileDamage = true; + if (root["hash"].isMember("sha256")) thash_sha256 = root["hash"]["sha256"].asString(); + else fileDamage = true; + } + else fileDamage = true; - if (!fileDamage) - { - string hash_md5, hash_sha256; - { - hashwrapper* myWrapper = new md5wrapper(); - hash_md5 = myWrapper->getHashFromFileW(globalPath + tpath); - delete myWrapper; - } - { - hashwrapper* myWrapper = new sha256wrapper(); - hash_sha256 = myWrapper->getHashFromFileW(globalPath + tpath); - delete myWrapper; + // 架构确定 + if (root.isMember("arch")) tarch = root["arch"].asString(); + else fileDamage = true; } + readjson.close(); - if (tedition == editionInfo.editionDate && _waccess((globalPath + tpath).c_str(), 0) == 0 && hash_md5 == thash_md5 && hash_sha256 == thash_sha256 && updateArch == tarch) + if (!fileDamage) { - update = false; - AutomaticUpdateState = UpdateRestart; + string hash_md5, hash_sha256; + { + hashwrapper* myWrapper = new md5wrapper(); + hash_md5 = myWrapper->getHashFromFileW(globalPath + tpath); + delete myWrapper; + } + { + hashwrapper* myWrapper = new sha256wrapper(); + hash_sha256 = myWrapper->getHashFromFileW(globalPath + tpath); + delete myWrapper; + } + + if (tedition == editionInfo.editionDate && _waccess((globalPath + tpath).c_str(), 0) == 0 && hash_md5 == thash_md5 && hash_sha256 == thash_sha256 && updateArch == tarch) + { + update = false; + AutomaticUpdateState = UpdateRestart; + } } } - } - if (update) - { - AutomaticUpdateState = UpdateDownloading; - - against = true; - bool hasUpdateNew = false; - for (int i = 0; i < editionInfo.path_size; i++) + if (update) { - AutomaticUpdateState = DownloadNewProgram(&downloadNewProgramState, editionInfo, editionInfo.path[i], updateArch); - if (AutomaticUpdateState == UpdateRestart) - { - against = false; + AutomaticUpdateState = UpdateDownloading; - if (mandatoryUpdate) + against = true; + bool hasUpdateNew = false; + for (int i = 0; i < editionInfo.path_size; i++) + { + AutomaticUpdateState = DownloadNewProgram(&downloadNewProgramState, editionInfo, editionInfo.path[i], updateArch); + if (AutomaticUpdateState == UpdateRestart) { - mandatoryUpdate = false; - RestartProgram(); + against = false; + + if (mandatoryUpdate) + { + mandatoryUpdate = false; + RestartProgram(); + } + break; + } + else if (AutomaticUpdateState == UpdateNew && !mandatoryUpdate) + { + hasUpdateNew = true; + break; } - break; - } - else if (AutomaticUpdateState == UpdateNew && !mandatoryUpdate) - { - hasUpdateNew = true; - break; } - } - if (hasUpdateNew) continue; + if (hasUpdateNew) continue; + } } } else if (state && editionInfo.editionDate != L"") diff --git "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" index 416e9f23..1a7448e8 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" @@ -4,28 +4,14 @@ class EditionInfoClass { public: - EditionInfoClass() - { - errorCode = 0; - channel = ""; - - editionDate = L""; - editionCode = L""; - explain = L""; - representation = L""; - path_size = 0; - fileSize = 0; - - hash_md5 = ""; - hash_sha256 = ""; - } - + EditionInfoClass() {} EditionInfoClass(const EditionInfoClass& other) { cF(other); } EditionInfoClass& operator=(const EditionInfoClass& other) { if (this != &other) cF(other); return *this; } + private: void cF(const EditionInfoClass& other) { @@ -42,10 +28,12 @@ class EditionInfoClass hash_md5 = other.hash_md5; hash_sha256 = other.hash_sha256; + + isInkeys3 = other.isInkeys3; } public: - int errorCode; + int errorCode = 0; string channel; wstring editionDate; @@ -53,11 +41,14 @@ class EditionInfoClass wstring explain; wstring representation; string path[10]; - int path_size; - atomic_ullong fileSize; + int path_size = 0; + atomic_ullong fileSize = 0; string hash_md5; string hash_sha256; + + // extra + bool isInkeys3 = false; }; EditionInfoClass GetEditionInfo(string channel, string arch); @@ -74,7 +65,10 @@ enum class AutomaticUpdateStateEnum : int UpdateRestart = 8, // 重启软件更新到最新版本 UpdateLatest = 9, // 软件已经是最新版本 UpdateNewer = 10, // 软件相对最新版本更新 - UpdateNew = 11 // 发现软件新版本 + UpdateNew = 11, // 发现软件新版本 + + UpdateLimit = 12, // 自动更新被阻止 + UpdateInkeys3 = 13, }; extern AutomaticUpdateStateEnum AutomaticUpdateState; @@ -115,4 +109,6 @@ extern bool inconsistentArchitecture; wstring get_domain_name(wstring url); wstring convertToHttp(const wstring& url); +extern bool isWindows8OrGreater; +extern wstring windowsEdition; void AutomaticUpdate(); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" index 5f146155..0121bce9 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" @@ -9,6 +9,7 @@ #include "IdtState.h" #include "IdtText.h" #include "Launch/IdtLaunchState.h" +#include "CrashHandler/CrashHandler.h" HWND floating_window = NULL; //悬浮窗窗口 HWND drawpad_window = NULL; //画板窗口 @@ -95,6 +96,9 @@ void TopWindow() while (rtsWait) this_thread::sleep_for(chrono::milliseconds(500)); this_thread::sleep_for(chrono::milliseconds(1000)); + // 启动操作彻底完成:关闭启动时崩溃标识 + CrashHandler::IsSecond(false); + while (!offSignal) { if (stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && !penetrate.select) diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.cpp" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.cpp" new file mode 100644 index 00000000..a7380a82 --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.cpp" @@ -0,0 +1,46 @@ +#include "IdtFontLoad.h" + +IDWriteFontCollectionLoader* IdtFontCollectionLoader::instance_(new(std::nothrow) IdtFontCollectionLoader()); + +IdtFontFileEnumerator::IdtFontFileEnumerator(IDWriteFactory* factory) : + refCount_(0), + factory_(SafeAcquire(factory)), + currentFile_(), + nextIndex_(0) +{ +} + +IDWriteFontFileLoader* IdtFontFileLoader::instance_(new(std::nothrow) IdtFontFileLoader()); + +HMODULE const IdtFontFileStream::moduleHandle_(GetCurrentModule()); +HMODULE IdtFontFileStream::GetCurrentModule() +{ + HMODULE handle = NULL; + + GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + reinterpret_cast(&GetCurrentModule), + &handle + ); + + return handle; +} +IdtFontFileStream::IdtFontFileStream(UINT resourceID) : + refCount_(0), + resourcePtr_(NULL), + resourceSize_(0) +{ + HRSRC resource = FindResourceW(moduleHandle_, MAKEINTRESOURCE(resourceID), L"TTF"); + if (resource != NULL) + { + HGLOBAL memHandle = LoadResource(moduleHandle_, resource); + if (memHandle != NULL) + { + resourcePtr_ = LockResource(memHandle); + if (resourcePtr_ != NULL) + { + resourceSize_ = SizeofResource(moduleHandle_, resource); + } + } + } +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.h" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.h" new file mode 100644 index 00000000..4f4e48be --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Load/IdtFontLoad.h" @@ -0,0 +1,377 @@ +#pragma once + +#include "../../IdtMain.h" + +#include "../../IdtD2DPreparation.h" + +template +inline void SafeRelease(InterfaceType** currentObject) +{ + if (*currentObject != NULL) + { + (*currentObject)->Release(); + *currentObject = NULL; + } +} +template +inline InterfaceType* SafeAcquire(InterfaceType* newObject) +{ + if (newObject != NULL) + newObject->AddRef(); + + return newObject; +} +template +inline void SafeSet(InterfaceType** currentObject, InterfaceType* newObject) +{ + SafeAcquire(newObject); + SafeRelease(¤tObject); + currentObject = newObject; +} + +inline HRESULT ExceptionToHResult() throw() +{ + try + { + throw; // Rethrow previous exception. + } + catch (std::bad_alloc&) + { + return E_OUTOFMEMORY; + } + catch (...) + { + return E_FAIL; + } +} + +class IdtFontFileStream : public IDWriteFontFileStream +{ +public: + explicit IdtFontFileStream(UINT resourceID); + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileStream)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + } + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&refCount_); + } + virtual ULONG STDMETHODCALLTYPE Release() + { + ULONG newCount = InterlockedDecrement(&refCount_); + if (newCount == 0) + delete this; + + return newCount; + } + + // IDWriteFontFileStream methods + virtual HRESULT STDMETHODCALLTYPE ReadFileFragment(void const** fragmentStart, UINT64 fileOffset, UINT64 fragmentSize, OUT void** fragmentContext) + { + if (fileOffset <= resourceSize_ && + fragmentSize <= resourceSize_ - fileOffset) + { + *fragmentStart = static_cast(resourcePtr_) + static_cast(fileOffset); + *fragmentContext = NULL; + return S_OK; + } + else + { + *fragmentStart = NULL; + *fragmentContext = NULL; + return E_FAIL; + } + } + virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext) {} + virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize) + { + *fileSize = resourceSize_; + return S_OK; + } + virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime) + { + *lastWriteTime = 0; + return E_NOTIMPL; + } + + bool IsInitialized() + { + return resourcePtr_ != NULL; + } + +private: + ULONG refCount_; + void const* resourcePtr_; + DWORD resourceSize_; + + static HMODULE const moduleHandle_; + static HMODULE GetCurrentModule(); +}; +class IdtFontFileLoader : public IDWriteFontFileLoader +{ +public: + IdtFontFileLoader() : refCount_(0) {} + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileLoader)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + } + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&refCount_); + } + virtual ULONG STDMETHODCALLTYPE Release() + { + ULONG newCount = InterlockedDecrement(&refCount_); + if (newCount == 0) + delete this; + + return newCount; + } + + // IDWriteFontFileLoader methods + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromKey(void const* fontFileReferenceKey, UINT32 fontFileReferenceKeySize, OUT IDWriteFontFileStream** fontFileStream) + { + *fontFileStream = NULL; + + // Make sure the key is the right size. + if (fontFileReferenceKeySize != sizeof(UINT)) + return E_INVALIDARG; + + UINT resourceID = *static_cast(fontFileReferenceKey); + + // Create the stream object. + IdtFontFileStream* stream = new(std::nothrow) IdtFontFileStream(resourceID); + if (stream == NULL) + return E_OUTOFMEMORY; + + if (!stream->IsInitialized()) + { + delete stream; + return E_FAIL; + } + + *fontFileStream = SafeAcquire(stream); + + return S_OK; + } + + // Gets the singleton loader instance. + static IDWriteFontFileLoader* GetLoader() + { + return instance_; + } + + static bool IsLoaderInitialized() + { + return instance_ != NULL; + } + +private: + ULONG refCount_; + + static IDWriteFontFileLoader* instance_; +}; + +class IdtFontFileEnumerator : public IDWriteFontFileEnumerator +{ +public: + IdtFontFileEnumerator(IDWriteFactory* factory); + + HRESULT Initialize(UINT const* resourceIDs, UINT32 resourceCount) + { + try + { + resourceIDs_.assign(resourceIDs, resourceIDs + resourceCount); + } + catch (...) + { + return ExceptionToHResult(); + } + return S_OK; + } + + ~IdtFontFileEnumerator() + { + SafeRelease(¤tFile_); + SafeRelease(&factory_); + } + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileEnumerator)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + } + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&refCount_); + } + virtual ULONG STDMETHODCALLTYPE Release() + { + ULONG newCount = InterlockedDecrement(&refCount_); + if (newCount == 0) + delete this; + + return newCount; + } + + // IDWriteFontFileEnumerator methods + virtual HRESULT STDMETHODCALLTYPE MoveNext(OUT BOOL* hasCurrentFile) + { + HRESULT hr = S_OK; + + *hasCurrentFile = FALSE; + SafeRelease(¤tFile_); + + if (nextIndex_ < resourceIDs_.size()) + { + hr = factory_->CreateCustomFontFileReference( + &resourceIDs_[nextIndex_], + sizeof(UINT), + IdtFontFileLoader::GetLoader(), + ¤tFile_ + ); + + if (SUCCEEDED(hr)) + { + *hasCurrentFile = TRUE; + + ++nextIndex_; + } + } + + return hr; + } + virtual HRESULT STDMETHODCALLTYPE GetCurrentFontFile(OUT IDWriteFontFile** fontFile) + { + *fontFile = SafeAcquire(currentFile_); + + return (currentFile_ != NULL) ? S_OK : E_FAIL; + } + +private: + ULONG refCount_; + + IDWriteFactory* factory_; + IDWriteFontFile* currentFile_; + std::vector resourceIDs_; + size_t nextIndex_; +}; +class IdtFontCollectionLoader : public IDWriteFontCollectionLoader +{ +public: + IdtFontCollectionLoader() : refCount_(0) {} + + // IUnknown methods + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject) + { + if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontCollectionLoader)) + { + *ppvObject = this; + AddRef(); + return S_OK; + } + else + { + *ppvObject = NULL; + return E_NOINTERFACE; + } + } + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return InterlockedIncrement(&refCount_); + } + virtual ULONG STDMETHODCALLTYPE Release() + { + ULONG newCount = InterlockedDecrement(&refCount_); + if (newCount == 0) + delete this; + + return newCount; + } + + // IDWriteFontCollectionLoader methods + virtual HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey(IDWriteFactory* factory, void const* collectionKey, UINT32 collectionKeySize, OUT IDWriteFontFileEnumerator** fontFileEnumerator) + { + *fontFileEnumerator = NULL; + + HRESULT hr = S_OK; + + if (collectionKeySize % sizeof(UINT) != 0) + return E_INVALIDARG; + + IdtFontFileEnumerator* enumerator = new(std::nothrow) IdtFontFileEnumerator( + factory + ); + if (enumerator == NULL) + return E_OUTOFMEMORY; + + UINT const* resourceIDs = static_cast(collectionKey); + UINT32 const resourceCount = collectionKeySize / sizeof(UINT); + + hr = enumerator->Initialize( + resourceIDs, + resourceCount + ); + + if (FAILED(hr)) + { + delete enumerator; + return hr; + } + + *fontFileEnumerator = SafeAcquire(enumerator); + + return hr; + } + + // Gets the singleton loader instance. + static IDWriteFontCollectionLoader* GetLoader() + { + return instance_; + } + + static bool IsLoaderInitialized() + { + return instance_ != NULL; + } + +private: + ULONG refCount_; + + static IDWriteFontCollectionLoader* instance_; +}; \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.cpp" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.cpp" new file mode 100644 index 00000000..f45b6e8c --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.cpp" @@ -0,0 +1,44 @@ +#include "IdtGesture.h" + +#include +#include +#include +#pragma comment(lib, "propsys.lib") + +BOOL IdtGesture::DisableEdgeGestures(HWND hwnd, BOOL disable) +{ + typedef HRESULT(WINAPI* SHGetPropertyStoreForWindowFunc)(HWND, REFIID, void**); + + HMODULE hShcore = LoadLibrary(TEXT("Shell32.dll")); + if (!hShcore) return FALSE; + + SHGetPropertyStoreForWindowFunc pSHGetPropertyStoreForWindow = (SHGetPropertyStoreForWindowFunc)GetProcAddress(hShcore, "SHGetPropertyStoreForWindow"); + if (!pSHGetPropertyStoreForWindow) + { + FreeLibrary(hShcore); + return FALSE; + } + + IPropertyStore* pPropStore = NULL; + HRESULT hr = pSHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pPropStore)); + + if (SUCCEEDED(hr)) + { + PROPERTYKEY propKey; + propKey.fmtid = GUID{ 0x32CE38B2, 0x2C9A, 0x41B1, { 0x9B, 0xC5, 0xB3, 0x78, 0x43, 0x94, 0xAA, 0x44 } }; + propKey.pid = 2; + + PROPVARIANT propVar; + PropVariantInit(&propVar); + propVar.vt = VT_BOOL; + propVar.boolVal = (disable ? VARIANT_TRUE : VARIANT_FALSE); + + hr = pPropStore->SetValue(propKey, propVar); + + PropVariantClear(&propVar); + pPropStore->Release(); + } + + FreeLibrary(hShcore); + return SUCCEEDED(hr); +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.h" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.h" new file mode 100644 index 00000000..5d78d8bf --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtGesture.h" @@ -0,0 +1,12 @@ +#pragma once + +#include "../../IdtMain.h" + +class IdtGesture +{ +private: + IdtGesture() = delete; + +public: + static BOOL DisableEdgeGestures(HWND hwnd, BOOL disable); +}; \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.cpp" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.cpp" new file mode 100644 index 00000000..188e5d09 --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.cpp" @@ -0,0 +1,26 @@ +#pragma once + +#include "IdtInputs.h" + +#undef max +#undef min +#include "libcuckoo/cuckoohash_map.hh" + +using DownMapType = libcuckoo::cuckoohash_map; +static DownMapType* getDownMap() +{ + return reinterpret_cast(IdtInputs::downMap); +} + +void* IdtInputs::downMap = new DownMapType; + +void IdtInputs::SetKeyBoardDown(BYTE key, bool down) +{ + getDownMap()->upsert(key, [&](bool& v) { v = down; }, down); +} +bool IdtInputs::IsKeyBoardDown(BYTE key) +{ + bool down = false; + getDownMap()->find(key, down); + return down; +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.h" "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.h" new file mode 100644 index 00000000..9df54d37 --- /dev/null +++ "b/\346\231\272\347\273\230\346\225\231/Inkeys/Other/IdtInputs.h" @@ -0,0 +1,15 @@ +#pragma once + +#include "../../IdtMain.h" + +class IdtInputs +{ +private: + IdtInputs() = delete; + +public: + static void SetKeyBoardDown(BYTE key, bool down); + static bool IsKeyBoardDown(BYTE key); + + static void* downMap; +}; \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/PptCOM.dll" "b/\346\231\272\347\273\230\346\225\231/PptCOM.dll" index 1f1f6f2801969e18177cc812f76b268422809cc1..b0bdc2140859237739bf7b8a24a6639419346bcf 100644 GIT binary patch delta 5964 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 In5gjo54Bj`WB>pF delta 6140 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 diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imconfig.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imconfig.h" index b8d55842..c6d9b8d5 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imconfig.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imconfig.h" @@ -15,7 +15,8 @@ #pragma once //---- Define assertion handler. Defaults to calling assert(). -// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +// - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. +// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes. //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts @@ -29,7 +30,6 @@ //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS -//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS. //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. @@ -49,6 +49,7 @@ //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). +//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //---- Enable Test Engine / Automation features. @@ -59,9 +60,12 @@ //#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" -//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) +//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. //#define IMGUI_USE_BGRA_PACKED_COLOR +//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. +//#define IMGUI_USE_LEGACY_CRC32_ADLER + //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //#define IMGUI_USE_WCHAR32 @@ -80,13 +84,16 @@ //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). +// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience. // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. //#define IMGUI_ENABLE_FREETYPE -//---- Use FreeType+lunasvg library to render OpenType SVG fonts (SVGinOT) -// Requires lunasvg headers to be available in the include path + program to be linked with the lunasvg library (not provided). +//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) // Only works in combination with IMGUI_ENABLE_FREETYPE. -// (implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions. +// - Both require headers to be available in the include path + program to be linked with the library code (not provided). +// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) +//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG //#define IMGUI_ENABLE_FREETYPE_LUNASVG //---- Use stb_truetype to build and rasterize the font atlas (default) @@ -124,6 +131,10 @@ //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() +//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. +// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) +//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.cpp" index 3bbaf018..2bb3ad3d 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.cpp" @@ -1,31 +1,33 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (main code and documentation) // Help: -// - See links below. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // - Read top of imgui.cpp for more details, links and comments. +// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including imgui.h (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. // Resources: // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui -// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Releases & Changelog ....... https://github.com/ocornut/imgui/releases // - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!) // - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) // - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) -// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings + backends for various tech/engines) // - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary // - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) +// - Web version of the Demo .... https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html (w/ source code browser) -// For first-time users having issues compiling/linking/running/loading fonts: +// For FIRST-TIME users having issues compiling/linking/running: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. // Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. +// Since 1.92, we encourage font loading questions to also be posted in 'Issues'. -// Copyright (c) 2014-2024 Omar Cornut +// Copyright (c) 2014-2025 Omar Cornut // Developed by Omar Cornut and every direct or indirect contributors to the GitHub. // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. @@ -52,7 +54,7 @@ DOCUMENTATION - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE - HOW A SIMPLE APPLICATION MAY LOOK LIKE - - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE + - USING CUSTOM BACKEND / CUSTOM ENGINE - API BREAKING CHANGES (read me when you update!) - FREQUENTLY ASKED QUESTIONS (FAQ) - Read all answers online: https://www.dearimgui.com/faq, or in docs/FAQ.md (with a Markdown viewer) @@ -77,6 +79,7 @@ CODE // [SECTION] RENDER HELPERS // [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] FONTS, TEXTURES // [SECTION] ID STACK // [SECTION] INPUTS // [SECTION] ERROR CHECKING, STATE RECOVERY @@ -85,6 +88,7 @@ CODE // [SECTION] SCROLLING // [SECTION] TOOLTIPS // [SECTION] POPUPS +// [SECTION] WINDOW FOCUS // [SECTION] KEYBOARD/GAMEPAD NAVIGATION // [SECTION] DRAG AND DROP // [SECTION] LOGGING/CAPTURING @@ -119,7 +123,7 @@ CODE Designed primarily for developers and content-creators, not the typical end-user! Some of the current weaknesses (which we aim to address in the future) includes: - - Doesn't look fancy. + - Doesn't look fancy by default. - Limited layout features, intricate layouts are typically crafted in code. @@ -128,7 +132,7 @@ CODE - MOUSE CONTROLS - Mouse wheel: Scroll vertically. - - SHIFT+Mouse wheel: Scroll horizontally. + - Shift+Mouse wheel: Scroll horizontally. - Click [X]: Close a window, available when 'bool* p_open' is passed to ImGui::Begin(). - Click ^, Double-Click title: Collapse window. - Drag on corner/border: Resize window (double-click to auto fit window to its contents). @@ -136,22 +140,24 @@ CODE - Left-click outside popup: Close popup stack (right-click over underlying popup: Partially close popup stack). - TEXT EDITOR - - Hold SHIFT or Drag Mouse: Select text. - - CTRL+Left/Right: Word jump. - - CTRL+Shift+Left/Right: Select words. - - CTRL+A or Double-Click: Select All. - - CTRL+X, CTRL+C, CTRL+V: Use OS clipboard. - - CTRL+Z, CTRL+Y: Undo, Redo. + - Hold Shift or Drag Mouse: Select text. + - Ctrl+Left/Right: Word jump. + - Ctrl+Shift+Left/Right: Select words. + - Ctrl+A or Double-Click: Select All. + - Ctrl+X, Ctrl+C, Ctrl+V: Use OS clipboard. + - Ctrl+Z Undo. + - Ctrl+Y or Ctrl+Shift+Z: Redo. - ESCAPE: Revert text to its original value. - - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors. + - On macOS, controls are automatically adjusted to match standard macOS text editing and behaviors. + (for 99% of shortcuts, Ctrl is replaced by Cmd on macOS). - KEYBOARD CONTROLS - Basic: - - Tab, SHIFT+Tab Cycle through text editable fields. - - CTRL+Tab, CTRL+Shift+Tab Cycle through windows. - - CTRL+Click Input text into a Slider or Drag widget. + - Tab, Shift+Tab Cycle through text editable fields. + - Ctrl+Tab, Ctrl+Shift+Tab Cycle through windows. + - Ctrl+Click Input text into a Slider or Drag widget. - Extended features with `io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard`: - - Tab, SHIFT+Tab: Cycle through every items. + - Tab, Shift+Tab: Cycle through every items. - Arrow keys Move through items using directional navigation. Tweak value. - Arrow keys + Alt, Shift Tweak slower, tweak faster (when using arrow keys). - Enter Activate item (prefer text input when possible). @@ -160,7 +166,7 @@ CODE - Page Up, Page Down Previous page, next page. - Home, End Scroll to top, scroll to bottom. - Alt Toggle between scrolling layer and menu layer. - - CTRL+Tab then Ctrl+Arrows Move window. Hold SHIFT to resize instead of moving. + - Ctrl+Tab then Ctrl+Arrows Move window. Hold Shift to resize instead of moving. - Output when ImGuiConfigFlags_NavEnableKeyboard set, - io.WantCaptureKeyboard flag is set when keyboard is claimed. - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set. @@ -174,7 +180,6 @@ CODE - Set 'io.BackendFlags |= ImGuiBackendFlags_HasGamepad' + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys. - For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly. Backend code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.). - - BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead! - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved. @@ -183,8 +188,8 @@ CODE - Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + run examples/libs/synergy/uSynergy.c (on your console/tablet/phone app) in order to share your PC mouse/keyboard. - See https://github.com/ocornut/imgui/wiki/Useful-Extensions#remoting for other remoting solutions. - - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiConfigFlags_NavEnableSetMousePos flag. - Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation movements. + - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the io.ConfigNavMoveSetMousePos flag. + Enabling io.ConfigNavMoveSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs Dear ImGui to move your mouse cursor along with navigation movements. When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the mouse cursor to be moved. When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the backends in examples/ do that. (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, Dear ImGui will misbehave as it will see your mouse moving back & forth!) @@ -197,7 +202,7 @@ CODE READ FIRST ---------- - - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki) + - Remember to check the wonderful Wiki: https://github.com/ocornut/imgui/wiki - Your code creates the UI every frame of your application loop, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no construction or destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, fewer bugs. @@ -271,7 +276,8 @@ CODE HOW A SIMPLE APPLICATION MAY LOOK LIKE -------------------------------------- - EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). + + USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder). The sub-folders in examples/ contain examples applications following this structure. // Application init: create a dear imgui context, setup some options, load fonts @@ -296,7 +302,7 @@ CODE // Any application code here ImGui::Text("Hello, world!"); - // Render dear imgui into screen + // Render dear imgui into framebuffer ImGui::Render(); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); g_pSwapChain->Present(1, 0); @@ -307,26 +313,36 @@ CODE ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); - EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE + To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, + you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! + Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. + - // Application init: create a dear imgui context, setup some options, load fonts +USING CUSTOM BACKEND / CUSTOM ENGINE +------------------------------------ + +IMPLEMENTING YOUR PLATFORM BACKEND: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for basic instructions. + -> the Platform backends in impl_impl_XXX.cpp files contain many implementations. + +IMPLEMENTING YOUR RenderDrawData() function: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_RenderDrawData() function. + +IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: + -> see https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md + -> the Renderer Backends in impl_impl_XXX.cpp files contain many implementations of a ImGui_ImplXXXX_UpdateTexture() function. + + Basic application/backend skeleton: + + // Application init: create a Dear ImGui context, setup some options, load fonts ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls. - // TODO: Fill optional fields of the io structure later. - // TODO: Load TTF/OTF fonts if you don't want to use the default font. + // TODO: set io.ConfigXXX values, e.g. + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable keyboard controls - // Build and load the texture atlas into a texture - // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer) - int width, height; - unsigned char* pixels = nullptr; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); - - // At this point you've got the texture data and you need to upload that to your graphic system: - // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'. - // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID. - MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32) - io.Fonts->SetTexID((void*)texture); + // TODO: Load TTF/OTF fonts if you don't want to use the default font. + io.Fonts->AddFontFromFileTTF("NotoSans.ttf"); // Application main loop while (true) @@ -349,77 +365,25 @@ CODE MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well! - // Render dear imgui, swap buffers + // End the dear imgui frame // (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game rendering code) - ImGui::EndFrame(); + ImGui::EndFrame(); // this is automatically called by Render(), but available ImGui::Render(); + + // Update textures ImDrawData* draw_data = ImGui::GetDrawData(); - MyImGuiRenderFunction(draw_data); + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + MyImGuiBackend_UpdateTexture(tex); + + // Render dear imgui contents, swap buffers + MyImGuiBackend_RenderDrawData(draw_data); SwapBuffers(); } // Shutdown ImGui::DestroyContext(); - To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application, - you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags! - Please read the FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" about this. - - - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE - --------------------------------------------- - The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function. - - void MyImGuiRenderFunction(ImDrawData* draw_data) - { - // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled - // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering. - // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize - // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color. - ImVec2 clip_off = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) - { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui - const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) - { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; - if (pcmd->UserCallback) - { - pcmd->UserCallback(cmd_list, pcmd); - } - else - { - // Project scissor/clipping rectangles into framebuffer space - ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); - ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); - if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) - continue; - - // We are using scissoring to clip some objects. All low-level graphics API should support it. - // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches - // (some elements visible outside their bounds) but you can fix that once everything else works! - // - Clipping coordinates are provided in imgui coordinates space: - // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size - // - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize == io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values. - // - In the interest of supporting multi-viewport applications (see 'docking' branch on github), - // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space. - // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min) - MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y); - - // The texture for the draw call is specified by pcmd->GetTexID(). - // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization. - MyEngineBindTexture((MyTexture*)pcmd->GetTexID()); - - // Render 'pcmd->ElemCount/3' indexed triangles. - // By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your engine doesn't support 16-bit indices. - MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset); - } - } - } - } API BREAKING CHANGES @@ -430,10 +394,161 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2025/11/06 (1.92.5) - BeginChild: commented out some legacy names which were obsoleted in 1.90.0 (Nov 2023), 1.90.9 (July 2024), 1.91.1 (August 2024): + - ImGuiChildFlags_Border --> ImGuiChildFlags_Borders + - ImGuiWindowFlags_NavFlattened --> ImGuiChildFlags_NavFlattened (moved to ImGuiChildFlags). BeginChild(name, size, 0, ImGuiWindowFlags_NavFlattened) --> BeginChild(name, size, ImGuiChildFlags_NavFlattened, 0) + - ImGuiWindowFlags_AlwaysUseWindowPadding --> ImGuiChildFlags_AlwaysUseWindowPadding (moved to ImGuiChildFlags). BeginChild(name, size, 0, ImGuiWindowFlags_AlwaysUseWindowPadding) --> BeginChild(name, size, ImGuiChildFlags_AlwaysUseWindowPadding, 0) + - 2025/11/06 (1.92.5) - Keys: commented out legacy names which were obsoleted in 1.89.0 (August 2022): + - ImGuiKey_ModCtrl --> ImGuiMod_Ctrl + - ImGuiKey_ModShift --> ImGuiMod_Shift + - ImGuiKey_ModAlt --> ImGuiMod_Alt + - ImGuiKey_ModSuper --> ImGuiMod_Super + - 2025/11/06 (1.92.5) - IO: commented out legacy io.ClearInputCharacters() obsoleted in 1.89.8 (Aug 2023). Calling io.ClearInputKeys() is enough. + - 2025/11/06 (1.92.5) - Commented out legacy SetItemAllowOverlap() obsoleted in 1.89.7: this never worked right. Use SetNextItemAllowOverlap() _before_ item instead. + - 2025/10/14 (1.92.4) - TreeNode, Selectable, Clipper: commented out legacy names which were obsoleted in 1.89.7 (July 2023) and 1.89.9 (Sept 2023); + - ImGuiTreeNodeFlags_AllowItemOverlap --> ImGuiTreeNodeFlags_AllowOverlap + - ImGuiSelectableFlags_AllowItemOverlap --> ImGuiSelectableFlags_AllowOverlap + - ImGuiListClipper::IncludeRangeByIndices() --> ImGuiListClipper::IncludeItemsByIndex() + - 2025/08/08 (1.92.2) - Backends: SDL_GPU3: Changed ImTextureID type from SDL_GPUTextureSamplerBinding* to SDL_GPUTexture*, which is more natural and easier for user to manage. If you need to change the current sampler, you can access the ImGui_ImplSDLGPU3_RenderState struct. (#8866, #8163, #7998, #7988) + - 2025/07/31 (1.92.2) - Tabs: Renamed ImGuiTabBarFlags_FittingPolicyResizeDown to ImGuiTabBarFlags_FittingPolicyShrink. Kept inline redirection enum (will obsolete). + - 2025/06/25 (1.92.0) - Layout: commented out legacy ErrorCheckUsingSetCursorPosToExtendParentBoundaries() fallback obsoleted in 1.89 (August 2022) which allowed a SetCursorPos()/SetCursorScreenPos() call WITHOUT AN ITEM + to extend parent window/cell boundaries. Replaced with assert/tooltip that would already happens if previously using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#5548, #4510, #3355, #1760, #1490, #4152, #150) + - Incorrect way to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); + - Correct ways to make a window content size 200x200: + Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); + Begin(...) + Dummy(ImVec2(200,200)) + End(); + - TL;DR; if the assert triggers, you can add a Dummy({0,0}) call to validate extending parent boundaries. + - 2025/06/11 (1.92.0) - THIS VERSION CONTAINS THE LARGEST AMOUNT OF BREAKING CHANGES SINCE 2015! I TRIED REALLY HARD TO KEEP THEM TO A MINIMUM, REDUCE THE AMOUNT OF INTERFERENCES, BUT INEVITABLY SOME USERS WILL BE AFFECTED. + IN ORDER TO HELP US IMPROVE THE TRANSITION PROCESS, INCL. DOCUMENTATION AND COMMENTS, PLEASE REPORT **ANY** DOUBT, CONFUSION, QUESTIONS, FEEDBACK TO: https://github.com/ocornut/imgui/issues/ + As part of the plan to reduce impact of API breaking changes, several unfinished changes/features/refactors related to font and text systems and scaling will be part of subsequent releases (1.92.1+). + If you are updating from an old version, and expecting a massive or difficult update, consider first updating to 1.91.9 to reduce the amount of changes. + - Hard to read? Refer to 'docs/Changelog.txt' for a less compact and more complete version of this! + - Fonts: **IMPORTANT**: if your app was solving the OSX/iOS Retina screen specific logical vs display scale problem by setting io.DisplayFramebufferScale (e.g. to 2.0f) + setting io.FontGlobalScale (e.g. to 1.0f/2.0f) + loading fonts at scaled sizes (e.g. size X * 2.0f): + This WILL NOT map correctly to the new system! Because font will rasterize as requested size. + - With a legacy backend (< 1.92): Instead of setting io.FontGlobalScale = 1.0f/N -> set ImFontCfg::RasterizerDensity = N. This already worked before, but is now pretty much required. + - With a new backend (1.92+): This should be all automatic. FramebufferScale is automatically used to set current font RasterizerDensity. FramebufferScale is a per-viewport property provided by backend through the Platform_GetWindowFramebufferScale() handler in 'docking' branch. + - Fonts: **IMPORTANT** on Font Sizing: Before 1.92, fonts were of a single size. They can now be dynamically sized. + - PushFont() API now has a REQUIRED size parameter. + - Before 1.92: PushFont() always used font "default" size specified in AddFont() call. It is equivalent to calling PushFont(font, font->LegacySize). + - Since 1.92: PushFont(font, 0.0f) preserve the current font size which is a shared value. + - To use old behavior: use 'ImGui::PushFont(font, font->LegacySize)' at call site. + - Kept inline single parameter function. Will obsolete. + - Fonts: **IMPORTANT** on Font Merging: + - When searching for a glyph in multiple merged fonts: we search for the FIRST font source which contains the desired glyph. + Because the user doesn't need to provide glyph ranges any more, it is possible that a glyph that you expected to fetch from a secondary/merged icon font may be erroneously fetched from the primary font. + - When searching for a glyph in multiple merged fonts: we now search for the FIRST font source which contains the desired glyph. This is technically a different behavior than before! + - e.g. If you are merging fonts you may have glyphs that you expected to load from Font Source 2 which exists in Font Source 1. + After the update and when using a new backend, those glyphs may now loaded from Font Source 1! + - We added `ImFontConfig::GlyphExcludeRanges[]` to specify ranges to exclude from a given font source: + // Add Font Source 1 but ignore ICON_MIN_FA..ICON_MAX_FA range + static ImWchar exclude_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + ImFontConfig cfg1; + cfg1.GlyphExcludeRanges = exclude_ranges; + io.Fonts->AddFontFromFileTTF("segoeui.ttf", 0.0f, &cfg1); + // Add Font Source 2, which expects to use the range above + ImFontConfig cfg2; + cfg2.MergeMode = true; + io.Fonts->AddFontFromFileTTF("FontAwesome4.ttf", 0.0f, &cfg2); + - You can use `Metrics/Debugger->Fonts->Font->Input Glyphs Overlap Detection Tool` to see list of glyphs available in multiple font sources. This can facilitate understanding which font input is providing which glyph. + - Fonts: **IMPORTANT** on Thread Safety: + - A few functions such as font->CalcTextSizeA() were, by sheer luck (== accidentally) thread-safe even thou we had never provided that guarantee. They are definitively not thread-safe anymore as new glyphs may be loaded. + - Fonts: ImFont::FontSize was removed and does not make sense anymore. ImFont::LegacySize is the size passed to AddFont(). + - Fonts: Removed support for PushFont(NULL) which was a shortcut for "default font". + - Fonts: Renamed/moved 'io.FontGlobalScale' to 'style.FontScaleMain'. + - Textures: all API functions taking a 'ImTextureID' parameter are now taking a 'ImTextureRef'. Affected functions are: ImGui::Image(), ImGui::ImageWithBg(), ImGui::ImageButton(), ImDrawList::AddImage(), ImDrawList::AddImageQuad(), ImDrawList::AddImageRounded(). + - Fonts: obsoleted ImFontAtlas::GetTexDataAsRGBA32(), GetTexDataAsAlpha8(), Build(), SetTexID(), IsBuilt() functions. The new protocol for backends to handle textures doesn't need them. Kept redirection functions (will obsolete). + - Fonts: ImFontConfig::OversampleH/OversampleV default to automatic (== 0) since v1.91.8. It is quite important you keep it automatic until we decide if we want to provide a way to express finer policy, otherwise you will likely waste texture space when using large glyphs. Note that the imgui_freetype backend doesn't use and does not need oversampling. + - Fonts: specifying glyph ranges is now unnecessary. The value of ImFontConfig::GlyphRanges[] is only useful for legacy backends. All GetGlyphRangesXXXX() functions are now marked obsolete: GetGlyphRangesDefault(), GetGlyphRangesGreek(), GetGlyphRangesKorean(), GetGlyphRangesJapanese(), GetGlyphRangesChineseSimplifiedCommon(), GetGlyphRangesChineseFull(), GetGlyphRangesCyrillic(), GetGlyphRangesThai(), GetGlyphRangesVietnamese(). + - Fonts: removed ImFontAtlas::TexDesiredWidth to enforce a texture width. (#327) + - Fonts: if you create and manage ImFontAtlas instances yourself (instead of relying on ImGuiContext to create one), you'll need to call ImFontAtlasUpdateNewFrame() yourself. An assert will trigger if you don't. + - Fonts: obsolete ImGui::SetWindowFontScale() which is not useful anymore. Prefer using 'PushFont(NULL, style.FontSizeBase * factor)' or to manipulate other scaling factors. + - Fonts: obsoleted ImFont::Scale which is not useful anymore. + - Fonts: generally reworked Internals of ImFontAtlas and ImFont. While in theory a vast majority of users shouldn't be affected, some use cases or extensions might be. Among other things: + - ImDrawCmd::TextureId has been changed to ImDrawCmd::TexRef. + - ImFontAtlas::TexID has been changed to ImFontAtlas::TexRef. + - ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[] + - ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourceCount. + - Each ImFont has a number of ImFontBaked instances corresponding to actively used sizes. ImFont::GetFontBaked(size) retrieves the one for a given size. + - Fields moved from ImFont to ImFontBaked: IndexAdvanceX[], Glyphs[], Ascent, Descent, FindGlyph(), FindGlyphNoFallback(), GetCharAdvance(). + - Fields moved from ImFontAtlas to ImFontAtlas->Tex: ImFontAtlas::TexWidth => TexData->Width, ImFontAtlas::TexHeight => TexData->Height, ImFontAtlas::TexPixelsAlpha8/TexPixelsRGBA32 => TexData->GetPixels(). + - Widget code may use ImGui::GetFontBaked() instead of ImGui::GetFont() to access font data for current font at current font size (and you may use font->GetFontBaked(size) to access it for any other size.) + - Fonts: (users of imgui_freetype): renamed ImFontAtlas::FontBuilderFlags to ImFontAtlas::FontLoaderFlags. Renamed ImFontConfig::FontBuilderFlags to ImFontConfig::FontLoaderFlags. Renamed ImGuiFreeTypeBuilderFlags to ImGuiFreeTypeLoaderFlags. + If you used runtime imgui_freetype selection rather than the default IMGUI_ENABLE_FREETYPE compile-time option: Renamed/reworked ImFontBuilderIO into ImFontLoader. Renamed ImGuiFreeType::GetBuilderForFreeType() to ImGuiFreeType::GetFontLoader(). + - old: io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType() + - new: io.Fonts->FontLoader = ImGuiFreeType::GetFontLoader() + - new: io.Fonts->SetFontLoader(ImGuiFreeType::GetFontLoader()) to change dynamically at runtime [from 1.92.1] + - Fonts: (users of custom rectangles, see #8466): Renamed AddCustomRectRegular() to AddCustomRect(). Added GetCustomRect() as a replacement for GetCustomRectByIndex() + CalcCustomRectUV(). + - The output type of GetCustomRect() is now ImFontAtlasRect, which include UV coordinates. X->x, Y->y, Width->w, Height->h. + - old: + const ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(custom_rect_id); + ImVec2 uv0, uv1; + atlas->GetCustomRectUV(r, &uv0, &uv1); + ImGui::Image(atlas->TexRef, ImVec2(r->w, r->h), uv0, uv1); + - new; + ImFontAtlasRect r; + atlas->GetCustomRect(custom_rect_id, &r); + ImGui::Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + - We added a redirecting typedef but haven't attempted to magically redirect the field names, as this API is rarely used and the fix is simple. + - Obsoleted AddCustomRectFontGlyph() as the API does not make sense for scalable fonts. Kept existing function which uses the font "default size" (Sources[0]->LegacySize). Added a helper AddCustomRectFontGlyphForSize() which is immediately marked obsolete, but can facilitate transitioning old code. + - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. + - DrawList: Renamed ImDrawList::PushTextureID()/PopTextureID() to PushTexture()/PopTexture(). + - Backends: removed ImGui_ImplXXXX_CreateFontsTexture()/ImGui_ImplXXXX_DestroyFontsTexture() for all backends that had them. They should not be necessary any more. + - 2025/05/23 (1.92.0) - Fonts: changed ImFont::CalcWordWrapPositionA() to ImFont::CalcWordWrapPosition() + - old: const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, ....); + - new: const char* ImFont::CalcWordWrapPosition (float size, const char* text, ....); + The leading 'float scale' parameters was changed to 'float size'. This was necessary as 'scale' is assuming standard font size which is a concept we aim to eliminate in an upcoming update. Kept inline redirection function. + - 2025/05/15 (1.92.0) - TreeNode: renamed ImGuiTreeNodeFlags_NavLeftJumpsBackHere to ImGuiTreeNodeFlags_NavLeftJumpsToParent for clarity. Kept inline redirection enum (will obsolete). + - 2025/05/15 (1.92.0) - Commented out PushAllowKeyboardFocus()/PopAllowKeyboardFocus() which was obsoleted in 1.89.4. Use PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop)/PopItemFlag() instead. (#3092) + - 2025/05/15 (1.92.0) - Commented out ImGuiListClipper::ForceDisplayRangeByIndices() which was obsoleted in 1.89.6. Use ImGuiListClipper::IncludeItemsByIndex() instead. + - 2025/03/05 (1.91.9) - BeginMenu(): Internals: reworked mangling of menu windows to use "###Menu_00" etc. instead of "##Menu_00", allowing them to also store the menu name before it. This shouldn't affect code unless directly accessing menu window from their mangled name. + - 2025/04/16 (1.91.9) - Internals: RenderTextEllipsis() function removed the 'float clip_max_x' parameter directly preceding 'float ellipsis_max_x'. Values were identical for a vast majority of users. (#8387) + - 2025/02/27 (1.91.9) - Image(): removed 'tint_col' and 'border_col' parameter from Image() function. Added ImageWithBg() replacement. (#8131, #8238) + - old: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 tint_col = (1,1,1,1), ImVec4 border_col = (0,0,0,0)); + - new: void Image (ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1)); + - new: void ImageWithBg(ImTextureID tex_id, ImVec2 image_size, ImVec2 uv0 = (0,0), ImVec2 uv1 = (1,1), ImVec4 bg_col = (0,0,0,0), ImVec4 tint_col = (1,1,1,1)); + - TL;DR: 'border_col' had misleading side-effect on layout, 'bg_col' was missing, parameter order couldn't be consistent with ImageButton(). + - new behavior always use ImGuiCol_Border color + style.ImageBorderSize / ImGuiStyleVar_ImageBorderSize. + - old behavior altered border size (and therefore layout) based on border color's alpha, which caused variety of problems + old behavior a fixed 1.0f for border size which was not tweakable. + - kept legacy signature (will obsolete), which mimics the old behavior, but uses Max(1.0f, style.ImageBorderSize) when border_col is specified. + - added ImageWithBg() function which has both 'bg_col' (which was missing) and 'tint_col'. It was impossible to add 'bg_col' to Image() with a parameter order consistent with other functions, so we decided to remove 'tint_col' and introduce ImageWithBg(). + - 2025/02/25 (1.91.9) - internals: fonts: ImFontAtlas::ConfigData[] has been renamed to ImFontAtlas::Sources[]. ImFont::ConfigData[], ConfigDataCount has been renamed to Sources[], SourcesCount. + - 2025/02/06 (1.91.9) - renamed ImFontConfig::GlyphExtraSpacing.x to ImFontConfig::GlyphExtraAdvanceX. + - 2025/01/22 (1.91.8) - removed ImGuiColorEditFlags_AlphaPreview (made value 0): it is now the default behavior. + prior to 1.91.8: alpha was made opaque in the preview by default _unless_ using ImGuiColorEditFlags_AlphaPreview. We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. + the new flags (ImGuiColorEditFlags_AlphaOpaque, ImGuiColorEditFlags_AlphaNoBg + existing ImGuiColorEditFlags_AlphaPreviewHalf) may be combined better and allow finer controls: + - 2025/01/14 (1.91.7) - renamed ImGuiTreeNodeFlags_SpanTextWidth to ImGuiTreeNodeFlags_SpanLabelWidth for consistency with other names. Kept redirection enum (will obsolete). (#6937) + - 2024/11/27 (1.91.6) - changed CRC32 table from CRC32-adler to CRC32c polynomial in order to be compatible with the result of SSE 4.2 instructions. + As a result, old .ini data may be partially lost (docking and tables information particularly). + Because some users have crafted and storing .ini data as a way to workaround limitations of the docking API, we are providing a '#define IMGUI_USE_LEGACY_CRC32_ADLER' compile-time option to keep using old CRC32 tables if you cannot afford invalidating old .ini data. + - 2024/11/06 (1.91.5) - commented/obsoleted out pre-1.87 IO system (equivalent to using IMGUI_DISABLE_OBSOLETE_KEYIO or IMGUI_DISABLE_OBSOLETE_FUNCTIONS before) + - io.KeyMap[] and io.KeysDown[] are removed (obsoleted February 2022). + - io.NavInputs[] and ImGuiNavInput are removed (obsoleted July 2022). + - pre-1.87 backends are not supported: + - backends need to call io.AddKeyEvent(), io.AddMouseEvent() instead of writing to io.KeysDown[], io.MouseDown[] fields. + - backends need to call io.AddKeyAnalogEvent() for gamepad values instead of writing to io.NavInputs[] fields. + - for more reference: + - read 1.87 and 1.88 part of this section or read Changelog for 1.87 and 1.88. + - read https://github.com/ocornut/imgui/issues/4921 + - if you have trouble updating a very old codebase using legacy backend-specific key codes: consider updating to 1.91.4 first, then #define IMGUI_DISABLE_OBSOLETE_KEYIO, then update to latest. + - obsoleted ImGuiKey_COUNT (it is unusually error-prone/misleading since valid keys don't start at 0). probably use ImGuiKey_NamedKey_BEGIN/ImGuiKey_NamedKey_END? + - fonts: removed const qualifiers from most font functions in prevision for upcoming font improvements. + - 2024/10/18 (1.91.4) - renamed ImGuiCol_NavHighlight to ImGuiCol_NavCursor (for consistency with newly exposed and reworked features). Kept inline redirection enum (will obsolete). + - 2024/10/14 (1.91.4) - moved ImGuiConfigFlags_NavEnableSetMousePos to standalone io.ConfigNavMoveSetMousePos bool. + moved ImGuiConfigFlags_NavNoCaptureKeyboard to standalone io.ConfigNavCaptureKeyboard bool (note the inverted value!). + kept legacy names (will obsolete) + code that copies settings once the first time. Dynamically changing the old value won't work. Switch to using the new value! + - 2024/10/10 (1.91.4) - the typedef for ImTextureID now defaults to ImU64 instead of void*. (#1641) + this removes the requirement to redefine it for backends which are e.g. storing descriptor sets or other 64-bits structures when building on 32-bits archs. It therefore simplify various building scripts/helpers. + you may have compile-time issues if you were casting to 'void*' instead of 'ImTextureID' when passing your types to functions taking ImTextureID values, e.g. ImGui::Image(). + in doubt it is almost always better to do an intermediate intptr_t cast, since it allows casting any pointer/integer type without warning: + - May warn: ImGui::Image((void*)MyTextureData, ...); + - May warn: ImGui::Image((void*)(intptr_t)MyTextureData, ...); + - Won't warn: ImGui::Image((ImTextureID)(intptr_t)MyTextureData, ...); + - note that you can always define ImTextureID to be your own high-level structures (with dedicated constructors) if you like. - 2024/10/03 (1.91.3) - drags: treat v_min==v_max as a valid clamping range when != 0.0f. Zero is a still special value due to legacy reasons, unless using ImGuiSliderFlags_ClampZeroRange. (#7968, #3361, #76) - drags: extended behavior of ImGuiSliderFlags_AlwaysClamp to include _ClampZeroRange. It considers v_min==v_max==0.0f as a valid clamping range (aka edits not allowed). although unlikely, it you wish to only clamp on text input but want v_min==v_max==0.0f to mean unclamped drags, you can use _ClampOnInput instead of _AlwaysClamp. (#7968, #3361, #76) - - 2024/09/10 (1.91.2) - internals: using multiple overlayed ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) + - 2024/09/10 (1.91.2) - internals: using multiple overlaid ButtonBehavior() with same ID will now have io.ConfigDebugHighlightIdConflicts=true feature emit a warning. (#8030) it was one of the rare case where using same ID is legal. workarounds: (1) use single ButtonBehavior() call with multiple _MouseButton flags, or (2) surround the calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() - 2024/08/23 (1.91.1) - renamed ImGuiChildFlags_Border to ImGuiChildFlags_Borders for consistency. kept inline redirection flag. - 2024/08/22 (1.91.1) - moved some functions from ImGuiIO to ImGuiPlatformIO structure: @@ -612,7 +727,7 @@ CODE - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API functions but technically present in imgui.h and ImGuiIO. - 2022/01/20 (1.87) - inputs: reworded gamepad IO. - Backend writing to io.NavInputs[] -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values. - - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputing text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used). + - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputting text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used). - 2022/01/17 (1.87) - inputs: reworked mouse IO. - Backend writing to io.MousePos -> backend should call io.AddMousePosEvent() - Backend writing to io.MouseDown[] -> backend should call io.AddMouseButtonEvent() @@ -620,10 +735,10 @@ CODE - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only] note: for all calls to IO new functions, the Dear ImGui context should be bound/current. read https://github.com/ocornut/imgui/issues/4921 for details. - - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. + - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(), ImGui::IsKeyDown(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details. - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX) - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes). + - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to still function with legacy key codes). - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.* - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[] array. Will assert. - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper. @@ -715,7 +830,7 @@ CODE - ShowTestWindow() -> use ShowDemoWindow() - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow) - IsRootWindowOrAnyChildFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) - - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f) + - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f)) - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing() - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding @@ -832,7 +947,7 @@ CODE - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. - - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix. + - 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call" compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicitly to fix. - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type. - 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters completely. - 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete). @@ -957,6 +1072,7 @@ CODE associated with it. Q: What is this library called? + Q: What is the difference between Dear ImGui and traditional UI toolkits? Q: Which version should I get? >> This library is called "Dear ImGui", please don't call it "ImGui" :) >> See https://www.dearimgui.com/faq for details. @@ -1069,7 +1185,7 @@ CODE #else #include #endif -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES) +#if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) // The UWP and GDK Win32 API subsets don't support clipboard nor IME functions #define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS @@ -1102,41 +1218,45 @@ CODE #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat" // warning: format specifies type 'int' but the argument has type 'unsigned int' #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. +#pragma clang diagnostic ignored "-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. #pragma clang diagnostic ignored "-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time destruction order is undefined. if MemFree() leads to users code that has been disabled before exit it might cause problems. ImGui coding style welcomes static/globals. #pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is. #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness -#pragma clang diagnostic ignored "-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' // unreasonable, would lead to casting every %p arg to void*. probably enabled by -pedantic. #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int' #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label #elif defined(__GNUC__) // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size -#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*' -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif // Debug options -#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold CTRL to display for all candidates. CTRL+Arrow to change last direction. +#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Hold Ctrl to display for all candidates. Ctrl+Arrow to change last direction. #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window -// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. +// Default font size if unspecified in both style.FontSizeBase and AddFontXXX() calls. +static const float FONT_DEFAULT_SIZE = 20.0f; + +// When using Ctrl+Tab (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast switch. static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear - static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. - -// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) -static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. @@ -1173,9 +1293,14 @@ namespace ImGui // Item static void ItemHandleShortcut(ImGuiID id); +// Window Focus +static int FindWindowFocusIndex(ImGuiWindow* window); +static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags); + // Navigation static void NavUpdate(); static void NavUpdateWindowing(); +static void NavUpdateWindowingApplyFocus(ImGuiWindow* window); static void NavUpdateWindowingOverlay(); static void NavUpdateCancelRequest(); static void NavUpdateCreateMoveRequest(); @@ -1184,22 +1309,24 @@ static float NavUpdatePageUpPageDown(); static inline void NavUpdateAnyRequestFlag(); static void NavUpdateCreateWrappingRequest(); static void NavEndFrame(); -static bool NavScoreItem(ImGuiNavItemData* result); +static bool NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb); static void NavApplyItemToResult(ImGuiNavItemData* result); static void NavProcessItem(); static void NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flags, ImGuiNavMoveFlags move_flags); +static ImGuiInputSource NavCalcPreferredRefPosSource(); static ImVec2 NavCalcPreferredRefPos(); static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window); static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void NavRestoreLayer(ImGuiNavLayer layer); -static int FindWindowFocusIndex(ImGuiWindow* window); // Error Checking and Debug Tools static void ErrorCheckNewFrameSanityChecks(); static void ErrorCheckEndFrameSanityChecks(); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS static void UpdateDebugToolItemPicker(); -static void UpdateDebugToolStackQueries(); +static void UpdateDebugToolItemPathQuery(); static void UpdateDebugToolFlashStyleColor(); +#endif // Inputs static void UpdateKeyboardInputs(); @@ -1208,14 +1335,19 @@ static void UpdateMouseWheel(); static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt); // Misc +static void UpdateFontsNewFrame(); +static void UpdateFontsEndFrame(); +static void UpdateTexturesNewFrame(); +static void UpdateTexturesEndFrame(); static void UpdateSettings(); -static int UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); +static int UpdateWindowManualResize(ImGuiWindow* window, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect); static void RenderWindowOuterBorders(ImGuiWindow* window); static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size); static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open); static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col); static void RenderDimmedBackgrounds(); static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect); +static void SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect); // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. @@ -1271,11 +1403,16 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { + FontSizeBase = 0.0f; // Will default to io.Fonts->Fonts[0] on first frame. + FontScaleMain = 1.0f; // Main scale factor. May be set by application once, or exposed to end-user. + FontScaleDpi = 1.0f; // Additional scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. WindowPadding = ImVec2(8,8); // Padding within a window WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowBorderHoverPadding = 4.0f; // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders. WindowMinSize = ImVec2(32,32); // Minimum window size WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. @@ -1294,16 +1431,27 @@ ImGuiStyle::ImGuiStyle() ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + ScrollbarPadding = 2.0f; // Padding of scrollbar grab within its frame (same for both axises) GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. - TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + ImageBorderSize = 0.0f; // Thickness of border around tabs. + TabRounding = 5.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. TabBorderSize = 0.0f; // Thickness of border around tabs. - TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + TabMinWidthBase = 1.0f; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. + TabMinWidthShrink = 80.0f; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. + TabCloseButtonMinWidthSelected = -1.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. + TabCloseButtonMinWidthUnselected = 0.0f; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. - TabBarOverlineSize = 2.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. + TabBarOverlineSize = 1.0f; // Thickness of tab-bar overline, which highlights the selected tab-bar. TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell + TreeLinesFlags = ImGuiTreeNodeFlags_DrawLinesNone; + TreeLinesSize = 1.0f; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. + TreeLinesRounding = 0.0f; // Radius of lines connecting child nodes to the vertical line. + DragDropTargetRounding = 0.0f; // Radius of the drag and drop target frame. + DragDropTargetBorderSize = 2.0f; // Thickness of the drag and drop target border. + DragDropTargetPadding = 3.0f; // Size to expand the drag and drop target from actual target item size. ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -1326,17 +1474,24 @@ ImGuiStyle::ImGuiStyle() HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + // [Internal] + _MainScale = 1.0f; + _NextFrameFontSizeBase = 0.0f; + // Default theme ImGui::StyleColorsDark(this); } -// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper function. Scaling the fonts is done separately and is up to you. + +// Scale all spacing/padding/thickness values. Do not scale fonts. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { + _MainScale *= scale_factor; WindowPadding = ImTrunc(WindowPadding * scale_factor); WindowRounding = ImTrunc(WindowRounding * scale_factor); WindowMinSize = ImTrunc(WindowMinSize * scale_factor); + WindowBorderHoverPadding = ImTrunc(WindowBorderHoverPadding * scale_factor); ChildRounding = ImTrunc(ChildRounding * scale_factor); PopupRounding = ImTrunc(PopupRounding * scale_factor); FramePadding = ImTrunc(FramePadding * scale_factor); @@ -1349,12 +1504,21 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) ColumnsMinSpacing = ImTrunc(ColumnsMinSpacing * scale_factor); ScrollbarSize = ImTrunc(ScrollbarSize * scale_factor); ScrollbarRounding = ImTrunc(ScrollbarRounding * scale_factor); + ScrollbarPadding = ImTrunc(ScrollbarPadding * scale_factor); GrabMinSize = ImTrunc(GrabMinSize * scale_factor); GrabRounding = ImTrunc(GrabRounding * scale_factor); LogSliderDeadzone = ImTrunc(LogSliderDeadzone * scale_factor); + ImageBorderSize = ImTrunc(ImageBorderSize * scale_factor); TabRounding = ImTrunc(TabRounding * scale_factor); - TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX; + TabMinWidthBase = ImTrunc(TabMinWidthBase * scale_factor); + TabMinWidthShrink = ImTrunc(TabMinWidthShrink * scale_factor); + TabCloseButtonMinWidthSelected = (TabCloseButtonMinWidthSelected > 0.0f && TabCloseButtonMinWidthSelected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthSelected * scale_factor) : TabCloseButtonMinWidthSelected; + TabCloseButtonMinWidthUnselected = (TabCloseButtonMinWidthUnselected > 0.0f && TabCloseButtonMinWidthUnselected != FLT_MAX) ? ImTrunc(TabCloseButtonMinWidthUnselected * scale_factor) : TabCloseButtonMinWidthUnselected; TabBarOverlineSize = ImTrunc(TabBarOverlineSize * scale_factor); + TreeLinesRounding = ImTrunc(TreeLinesRounding * scale_factor); + DragDropTargetRounding = ImTrunc(DragDropTargetRounding * scale_factor); + DragDropTargetBorderSize = ImTrunc(DragDropTargetBorderSize * scale_factor); + DragDropTargetPadding = ImTrunc(DragDropTargetPadding * scale_factor); SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor); DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor); DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor); @@ -1375,18 +1539,25 @@ ImGuiIO::ImGuiIO() IniSavingRate = 5.0f; IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to lock this to an absolute path (e.g. same path as executables). LogFilename = "imgui_log.txt"; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int i = 0; i < ImGuiKey_COUNT; i++) - KeyMap[i] = -1; -#endif UserData = NULL; Fonts = NULL; - FontGlobalScale = 1.0f; FontDefault = NULL; FontAllowUserScaling = false; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + FontGlobalScale = 1.0f; // Use style.FontScaleMain instead! +#endif DisplayFramebufferScale = ImVec2(1.0f, 1.0f); + // Keyboard/Gamepad Navigation options + ConfigNavSwapGamepadButtons = false; + ConfigNavMoveSetMousePos = false; + ConfigNavCaptureKeyboard = true; + ConfigNavEscapeClearFocusItem = true; + ConfigNavEscapeClearFocusWindow = false; + ConfigNavCursorVisibleAuto = true; + ConfigNavCursorVisibleAlways = false; + // Miscellaneous options MouseDrawCursor = false; #ifdef __APPLE__ @@ -1394,17 +1565,18 @@ ImGuiIO::ImGuiIO() #else ConfigMacOSXBehaviors = false; #endif - ConfigNavSwapGamepadButtons = false; ConfigInputTrickleEventQueue = true; ConfigInputTextCursorBlink = true; ConfigInputTextEnterKeepActive = false; ConfigDragClickToInputText = false; ConfigWindowsResizeFromEdges = true; ConfigWindowsMoveFromTitleBarOnly = false; + ConfigWindowsCopyContentsWithCtrlC = false; ConfigScrollbarScrollByPage = true; ConfigMemoryCompactTimer = 60.0f; ConfigDebugIsDebuggerPresent = false; ConfigDebugHighlightIdConflicts = true; + ConfigDebugHighlightIdConflictsShowItemPicker = true; ConfigDebugBeginReturnValueOnce = false; ConfigDebugBeginReturnValueLoop = false; @@ -1432,8 +1604,6 @@ ImGuiIO::ImGuiIO() for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } AppAcceptingEvents = true; - BackendUsingLegacyKeyArrays = (ImS8)-1; - BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong } // Pass in translated ASCII characters for text input. @@ -1491,14 +1661,15 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) AddInputCharacter((unsigned)cp); } -void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) +void ImGuiIO::AddInputCharactersUTF8(const char* str) { if (!AppAcceptingEvents) return; - while (*utf8_chars != 0) + const char* str_end = str + strlen(str); + while (*str != 0) { unsigned int c = 0; - utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL); + str += ImTextCharFromUtf8(&c, str, str_end); AddInputCharacter(c); } } @@ -1514,16 +1685,15 @@ void ImGuiIO::ClearEventsQueue() // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. void ImGuiIO::ClearInputKeys() { -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - memset(KeysDown, 0, sizeof(KeysDown)); -#endif - for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++) + ImGuiContext& g = *Ctx; + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - if (ImGui::IsMouseKey((ImGuiKey)(n + ImGuiKey_KeysData_OFFSET))) + if (ImGui::IsMouseKey((ImGuiKey)key)) continue; - KeysData[n].Down = false; - KeysData[n].DownDuration = -1.0f; - KeysData[n].DownDurationPrev = -1.0f; + ImGuiKeyData* key_data = &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; + key_data->Down = false; + key_data->DownDuration = -1.0f; + key_data->DownDurationPrev = -1.0f; } KeyCtrl = KeyShift = KeyAlt = KeySuper = false; KeyMods = ImGuiMod_None; @@ -1534,7 +1704,7 @@ void ImGuiIO::ClearInputMouse() { for (ImGuiKey key = ImGuiKey_Mouse_BEGIN; key < ImGuiKey_Mouse_END; key = (ImGuiKey)(key + 1)) { - ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyData* key_data = &KeysData[key - ImGuiKey_NamedKey_BEGIN]; key_data->Down = false; key_data->DownDuration = -1.0f; key_data->DownDurationPrev = -1.0f; @@ -1548,15 +1718,6 @@ void ImGuiIO::ClearInputMouse() MouseWheel = MouseWheelH = 0.0f; } -// Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue. -// Current frame character buffer is now also cleared by ClearInputKeys(). -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -void ImGuiIO::ClearInputCharacters() -{ - InputQueueCharacters.resize(0); -} -#endif - static ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventType type, int arg = -1) { ImGuiContext& g = *ctx; @@ -1601,17 +1762,6 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; } } - // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - if (BackendUsingLegacyKeyArrays == -1) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - BackendUsingLegacyKeyArrays = 0; -#endif - if (ImGui::IsGamepadKey(key)) - BackendUsingLegacyNavInputArray = false; - // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)key); const ImGuiKeyData* key_data = ImGui::GetKeyData(&g, key); @@ -1647,20 +1797,10 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native return; IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512 IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511 - IM_UNUSED(native_keycode); // Yet unused - IM_UNUSED(native_scancode); // Yet unused - - // Build native->imgui map so old user code can still call key functions with native 0..511 values. -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode; - if (!ImGui::IsLegacyKey((ImGuiKey)legacy_key)) - return; - KeyMap[legacy_key] = key; - KeyMap[key] = legacy_key; -#else - IM_UNUSED(key); - IM_UNUSED(native_legacy_index); -#endif + IM_UNUSED(key); // Yet unused + IM_UNUSED(native_keycode); // Yet unused + IM_UNUSED(native_scancode); // Yet unused + IM_UNUSED(native_legacy_index); // Yet unused } // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. @@ -1707,7 +1847,7 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button. if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick) { - // Order of both statements matterns: this event will still release mouse button 1 + // Order of both statements matters: this event will still release mouse button 1 mouse_button = 1; if (!down) MouseCtrlLeftAsRightClick = false; @@ -1942,21 +2082,27 @@ void ImStrncpy(char* dst, const char* src, size_t count) if (count < 1) return; if (count > 1) - strncpy(dst, src, count - 1); + strncpy(dst, src, count - 1); // FIXME-OPT: strncpy not only doesn't guarantee 0-termination, it also always writes the whole array dst[count - 1] = 0; } char* ImStrdup(const char* str) { - size_t len = strlen(str); + size_t len = ImStrlen(str); void* buf = IM_ALLOC(len + 1); return (char*)memcpy(buf, (const void*)str, len + 1); } +void* ImMemdup(const void* src, size_t size) +{ + void* dst = IM_ALLOC(size); + return memcpy(dst, src, size); +} + char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) { - size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1; - size_t src_size = strlen(src) + 1; + size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1; + size_t src_size = ImStrlen(src) + 1; if (dst_buf_size < src_size) { IM_FREE(dst); @@ -1969,7 +2115,7 @@ char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src) const char* ImStrchrRange(const char* str, const char* str_end, char c) { - const char* p = (const char*)memchr(str, (int)c, str_end - str); + const char* p = (const char*)ImMemchr(str, (int)c, str_end - str); return p; } @@ -1984,12 +2130,13 @@ int ImStrlenW(const ImWchar* str) // Find end-of-line. Return pointer will point to either first \n, either str_end. const char* ImStreolRange(const char* str, const char* str_end) { - const char* p = (const char*)memchr(str, '\n', str_end - str); + const char* p = (const char*)ImMemchr(str, '\n', str_end - str); return p ? p : str_end; } const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line { + IM_ASSERT_PARANOID(buf_mid_line >= buf_begin && buf_mid_line <= buf_begin + ImStrlen(buf_begin)); while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') buf_mid_line--; return buf_mid_line; @@ -1998,7 +2145,7 @@ const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find be const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end) { if (!needle_end) - needle_end = needle + strlen(needle); + needle_end = needle + ImStrlen(needle); const char un0 = (char)ImToUpper(*needle); while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end)) @@ -2119,7 +2266,7 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, if (buf == NULL) buf = "(null)"; *out_buf = buf; - if (out_buf_end) { *out_buf_end = buf + strlen(buf); } + if (out_buf_end) { *out_buf_end = buf + ImStrlen(buf); } } else if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 's' && fmt[4] == 0) { @@ -2141,11 +2288,14 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, } } +#ifndef IMGUI_ENABLE_SSE4_2_CRC // CRC32 needs a 1KB lookup table (not cache friendly) // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. static const ImU32 GCrc32LookupTable[256] = { +#ifdef IMGUI_USE_LEGACY_CRC32_ADLER + // Legacy CRC32-adler table used pre 1.91.6 (before 2024/11/27). Only use if you cannot afford invalidating old .ini data. 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91, 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5, 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, @@ -2162,7 +2312,27 @@ static const ImU32 GCrc32LookupTable[256] = 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45, 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D, +#else + // CRC32c table compatible with SSE 4.2 instructions + 0x00000000,0xF26B8303,0xE13B70F7,0x1350F3F4,0xC79A971F,0x35F1141C,0x26A1E7E8,0xD4CA64EB,0x8AD958CF,0x78B2DBCC,0x6BE22838,0x9989AB3B,0x4D43CFD0,0xBF284CD3,0xAC78BF27,0x5E133C24, + 0x105EC76F,0xE235446C,0xF165B798,0x030E349B,0xD7C45070,0x25AFD373,0x36FF2087,0xC494A384,0x9A879FA0,0x68EC1CA3,0x7BBCEF57,0x89D76C54,0x5D1D08BF,0xAF768BBC,0xBC267848,0x4E4DFB4B, + 0x20BD8EDE,0xD2D60DDD,0xC186FE29,0x33ED7D2A,0xE72719C1,0x154C9AC2,0x061C6936,0xF477EA35,0xAA64D611,0x580F5512,0x4B5FA6E6,0xB93425E5,0x6DFE410E,0x9F95C20D,0x8CC531F9,0x7EAEB2FA, + 0x30E349B1,0xC288CAB2,0xD1D83946,0x23B3BA45,0xF779DEAE,0x05125DAD,0x1642AE59,0xE4292D5A,0xBA3A117E,0x4851927D,0x5B016189,0xA96AE28A,0x7DA08661,0x8FCB0562,0x9C9BF696,0x6EF07595, + 0x417B1DBC,0xB3109EBF,0xA0406D4B,0x522BEE48,0x86E18AA3,0x748A09A0,0x67DAFA54,0x95B17957,0xCBA24573,0x39C9C670,0x2A993584,0xD8F2B687,0x0C38D26C,0xFE53516F,0xED03A29B,0x1F682198, + 0x5125DAD3,0xA34E59D0,0xB01EAA24,0x42752927,0x96BF4DCC,0x64D4CECF,0x77843D3B,0x85EFBE38,0xDBFC821C,0x2997011F,0x3AC7F2EB,0xC8AC71E8,0x1C661503,0xEE0D9600,0xFD5D65F4,0x0F36E6F7, + 0x61C69362,0x93AD1061,0x80FDE395,0x72966096,0xA65C047D,0x5437877E,0x4767748A,0xB50CF789,0xEB1FCBAD,0x197448AE,0x0A24BB5A,0xF84F3859,0x2C855CB2,0xDEEEDFB1,0xCDBE2C45,0x3FD5AF46, + 0x7198540D,0x83F3D70E,0x90A324FA,0x62C8A7F9,0xB602C312,0x44694011,0x5739B3E5,0xA55230E6,0xFB410CC2,0x092A8FC1,0x1A7A7C35,0xE811FF36,0x3CDB9BDD,0xCEB018DE,0xDDE0EB2A,0x2F8B6829, + 0x82F63B78,0x709DB87B,0x63CD4B8F,0x91A6C88C,0x456CAC67,0xB7072F64,0xA457DC90,0x563C5F93,0x082F63B7,0xFA44E0B4,0xE9141340,0x1B7F9043,0xCFB5F4A8,0x3DDE77AB,0x2E8E845F,0xDCE5075C, + 0x92A8FC17,0x60C37F14,0x73938CE0,0x81F80FE3,0x55326B08,0xA759E80B,0xB4091BFF,0x466298FC,0x1871A4D8,0xEA1A27DB,0xF94AD42F,0x0B21572C,0xDFEB33C7,0x2D80B0C4,0x3ED04330,0xCCBBC033, + 0xA24BB5A6,0x502036A5,0x4370C551,0xB11B4652,0x65D122B9,0x97BAA1BA,0x84EA524E,0x7681D14D,0x2892ED69,0xDAF96E6A,0xC9A99D9E,0x3BC21E9D,0xEF087A76,0x1D63F975,0x0E330A81,0xFC588982, + 0xB21572C9,0x407EF1CA,0x532E023E,0xA145813D,0x758FE5D6,0x87E466D5,0x94B49521,0x66DF1622,0x38CC2A06,0xCAA7A905,0xD9F75AF1,0x2B9CD9F2,0xFF56BD19,0x0D3D3E1A,0x1E6DCDEE,0xEC064EED, + 0xC38D26C4,0x31E6A5C7,0x22B65633,0xD0DDD530,0x0417B1DB,0xF67C32D8,0xE52CC12C,0x1747422F,0x49547E0B,0xBB3FFD08,0xA86F0EFC,0x5A048DFF,0x8ECEE914,0x7CA56A17,0x6FF599E3,0x9D9E1AE0, + 0xD3D3E1AB,0x21B862A8,0x32E8915C,0xC083125F,0x144976B4,0xE622F5B7,0xF5720643,0x07198540,0x590AB964,0xAB613A67,0xB831C993,0x4A5A4A90,0x9E902E7B,0x6CFBAD78,0x7FAB5E8C,0x8DC0DD8F, + 0xE330A81A,0x115B2B19,0x020BD8ED,0xF0605BEE,0x24AA3F05,0xD6C1BC06,0xC5914FF2,0x37FACCF1,0x69E9F0D5,0x9B8273D6,0x88D28022,0x7AB90321,0xAE7367CA,0x5C18E4C9,0x4F48173D,0xBD23943E, + 0xF36E6F75,0x0105EC76,0x12551F82,0xE03E9C81,0x34F4F86A,0xC69F7B69,0xD5CF889D,0x27A40B9E,0x79B737BA,0x8BDCB4B9,0x988C474D,0x6AE7C44E,0xBE2DA0A5,0x4C4623A6,0x5F16D052,0xAD7D5351 +#endif }; +#endif // Known size hash // It is ok to call ImHashData on a string with known length but the ### operator won't be supported. @@ -2171,10 +2341,22 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed) { ImU32 crc = ~seed; const unsigned char* data = (const unsigned char*)data_p; + const unsigned char *data_end = (const unsigned char*)data_p + data_size; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; - while (data_size-- != 0) + while (data < data_end) crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++]; return ~crc; +#else + while (data + 4 <= data_end) + { + crc = _mm_crc32_u32(crc, *(ImU32*)data); + data += 4; + } + while (data < data_end) + crc = _mm_crc32_u8(crc, *data++); + return ~crc; +#endif } // Zero-terminated string hash, with support for ### to reset back to seed value @@ -2188,7 +2370,9 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) seed = ~seed; ImU32 crc = seed; const unsigned char* data = (const unsigned char*)data_p; +#ifndef IMGUI_ENABLE_SSE4_2_CRC const ImU32* crc32_lut = GCrc32LookupTable; +#endif if (data_size != 0) { while (data_size-- != 0) @@ -2196,7 +2380,11 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) unsigned char c = *data++; if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } else @@ -2205,12 +2393,27 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) { if (c == '#' && data[0] == '#' && data[1] == '#') crc = seed; +#ifndef IMGUI_ENABLE_SSE4_2_CRC crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c]; +#else + crc = _mm_crc32_u8(crc, c); +#endif } } return ~crc; } +// Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() +// FIXME-OPT: This is not designed to be optimal. Use with care. +const char* ImHashSkipUncontributingPrefix(const char* label) +{ + const char* result = label; + while (unsigned char c = *label++) + if (c == '#' && label[0] == '#' && label[1] == '#') + result = label - 1; + return result; +} + //----------------------------------------------------------------------------- // [SECTION] MISC HELPERS/UTILITIES (File functions) //----------------------------------------------------------------------------- @@ -2220,7 +2423,7 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed) ImFileHandle ImFileOpen(const char* filename, const char* mode) { -#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__) +#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__))) // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); @@ -2310,6 +2513,7 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* int len = lengths[*(const unsigned char*)in_text >> 3]; int wanted = len + (len ? 0 : 1); + // IMPORTANT: if in_text_end == NULL it assume we have enough space! if (in_text_end == NULL) in_text_end = in_text + wanted; // Max length, nulls will be taken into account. @@ -2332,7 +2536,7 @@ int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* int e = 0; e = (*out_char < mins[len]) << 6; // non-canonical encoding e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half? - e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range? + e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range we can store in ImWchar (FIXME: May evolve) e |= (s[1] & 0xc0) >> 2; e |= (s[2] & 0xc0) >> 4; e |= (s[3] ) >> 6; @@ -2416,11 +2620,11 @@ static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int return 0; } -const char* ImTextCharToUtf8(char out_buf[5], unsigned int c) +int ImTextCharToUtf8(char out_buf[5], unsigned int c) { int count = ImTextCharToUtf8_inline(out_buf, 5, c); out_buf[count] = 0; - return out_buf; + return count; } // Not optimal but we very rarely use this function. @@ -2469,25 +2673,37 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e return bytes_count; } -const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr) +const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_p) { - while (in_text_curr > in_text_start) + while (in_p > in_text_start) { - in_text_curr--; - if ((*in_text_curr & 0xC0) != 0x80) - return in_text_curr; + in_p--; + if ((*in_p & 0xC0) != 0x80) + return in_p; } return in_text_start; } +const char* ImTextFindValidUtf8CodepointEnd(const char* in_text_start, const char* in_text_end, const char* in_p) +{ + if (in_text_start == in_p) + return in_text_start; + const char* prev = ImTextFindPreviousUtf8Codepoint(in_text_start, in_p); + unsigned int prev_c; + int prev_c_len = ImTextCharFromUtf8(&prev_c, prev, in_text_end); + if (prev_c != IM_UNICODE_CODEPOINT_INVALID && prev_c_len <= (int)(in_p - prev)) + return in_p; + return prev; +} + int ImTextCountLines(const char* in_text, const char* in_text_end) { if (in_text_end == NULL) - in_text_end = in_text + strlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now. + in_text_end = in_text + ImStrlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now. int count = 0; while (in_text < in_text_end) { - const char* line_end = (const char*)memchr(in_text, '\n', in_text_end - in_text); + const char* line_end = (const char*)ImMemchr(in_text, '\n', in_text_end - in_text); in_text = line_end ? line_end + 1 : in_text_end; count++; } @@ -2768,7 +2984,7 @@ void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector= 0 && new_size >= old_size && new_size >= EndOffset); if (old_size == new_size) return; if (EndOffset == 0 || base[EndOffset - 1] == '\n') - LineOffsets.push_back(EndOffset); + Offsets.push_back(EndOffset); const char* base_end = base + new_size; - for (const char* p = base + old_size; (p = (const char*)memchr(p, '\n', base_end - p)) != 0; ) + for (const char* p = base + old_size; (p = (const char*)ImMemchr(p, '\n', base_end - p)) != 0; ) if (++p < base_end) // Don't push a trailing offset on last \n - LineOffsets.push_back((int)(intptr_t)(p - base)); + Offsets.push_back((int)(intptr_t)(p - base)); EndOffset = ImMax(EndOffset, new_size); } +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] ImGuiListClipper @@ -2937,7 +3155,7 @@ static void ImGuiListClipper_SortAndFuseRanges(ImVector& } } -static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height) +static void ImGuiListClipper_SeekCursorAndSetupPrevLine(ImGuiListClipper* clipper, float pos_y, float line_height) { // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. @@ -2955,10 +3173,13 @@ static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_ { if (table->IsInsideRow) ImGui::TableEndRow(table); - table->RowPosY2 = window->DC.CursorPos.y; const int row_increase = (int)((off_y / line_height) + 0.5f); - //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow() - table->RowBgColorCounter += row_increase; + if (row_increase > 0 && (clipper->Flags & ImGuiListClipperFlags_NoSetTableRowCounters) == 0) // If your clipper item height is != from actual table row height, consider using ImGuiListClipperFlags_NoSetTableRowCounters. See #8886. + { + table->CurrentRow += row_increase; + table->RowBgColorCounter += row_increase; + } + table->RowPosY2 = window->DC.CursorPos.y; } } @@ -3041,7 +3262,7 @@ void ImGuiListClipper::SeekCursorForItem(int item_n) // - StartPosY starts from ItemsFrozen, by adding SeekOffsetY we generally cancel that out (SeekOffsetY == LossynessOffset - ItemsFrozen * ItemsHeight). // - The reason we store SeekOffsetY instead of inferring it, is because we want to allow user to perform Seek after the last step, where ImGuiListClipperData is already done. float pos_y = (float)((double)StartPosY + StartSeekOffsetY + (double)item_n * ItemsHeight); - ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, ItemsHeight); + ImGuiListClipper_SeekCursorAndSetupPrevLine(this, pos_y, ItemsHeight); } static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) @@ -3094,10 +3315,17 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (table) IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y); - clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); - bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); + bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision((float)clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(window->DC.CursorPos.y); if (affected_by_floating_point_precision) + { + // Mitigation/hack for very large range: assume last time height constitute line height. clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries. + window->DC.CursorPos.y = (float)(clipper->StartPosY + clipper->ItemsHeight); + } + else + { + clipper->ItemsHeight = (float)(window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart); + } if (clipper->ItemsHeight == 0.0f && clipper->ItemsCount == INT_MAX) // Accept that no item have been submitted if in indeterminate mode. return false; IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!"); @@ -3120,8 +3348,13 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { // Add range selected to be included for navigation const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav); + const int nav_off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; + const int nav_off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; if (is_nav_request) - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, 0, 0)); + { + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringRect.Min.y, g.NavScoringRect.Max.y, nav_off_min, nav_off_max)); + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(g.NavScoringNoClipRect.Min.y, g.NavScoringNoClipRect.Max.y, nav_off_min, nav_off_max)); + } if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && g.NavTabbingDir == -1) data->Ranges.push_back(ImGuiListClipperRange::FromIndices(clipper->ItemsCount - 1, clipper->ItemsCount)); @@ -3130,7 +3363,6 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) if (g.NavId != 0 && window->NavLastIds[0] == g.NavId) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(nav_rect_abs.Min.y, nav_rect_abs.Max.y, 0, 0)); - // Add visible range float min_y = window->ClipRect.Min.y; float max_y = window->ClipRect.Max.y; @@ -3149,9 +3381,8 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) data->Ranges.push_back(ImGuiListClipperRange::FromPositions(bs->UnclipRect.Min.y, bs->UnclipRect.Max.y, 0, 0)); } - const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0; - const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0; - data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, off_min, off_max)); + // Add main visible range + data->Ranges.push_back(ImGuiListClipperRange::FromPositions(min_y, max_y, nav_off_min, nav_off_max)); } // Convert position ranges to item index ranges @@ -3175,11 +3406,11 @@ static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper) { clipper->DisplayStart = ImMax(data->Ranges[data->StepNo].Min, already_submitted); clipper->DisplayEnd = ImMin(data->Ranges[data->StepNo].Max, clipper->ItemsCount); - if (clipper->DisplayStart > already_submitted) //-V1051 - clipper->SeekCursorForItem(clipper->DisplayStart); data->StepNo++; - if (clipper->DisplayStart == clipper->DisplayEnd && data->StepNo < data->Ranges.Size) + if (clipper->DisplayStart >= clipper->DisplayEnd) continue; + if (clipper->DisplayStart > already_submitted) + clipper->SeekCursorForItem(clipper->DisplayStart); return true; } @@ -3196,7 +3427,7 @@ bool ImGuiListClipper::Step() ImGuiContext& g = *Ctx; bool need_items_height = (ItemsHeight <= 0.0f); bool ret = ImGuiListClipper_StepInternal(this); - if (ret && (DisplayStart == DisplayEnd)) + if (ret && (DisplayStart >= DisplayEnd)) ret = false; if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false) IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n"); @@ -3214,6 +3445,13 @@ bool ImGuiListClipper::Step() return ret; } +// Generic helper, equivalent to old ImGui::CalcListClipping() but statelesss +void ImGui::CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end) +{ + *out_visible_start = ImMax((int)((clip_rect.Min.y - pos.y) / items_height), 0); + *out_visible_end = ImMax((int)ImCeil((clip_rect.Max.y - pos.y) / items_height), *out_visible_start); +} + //----------------------------------------------------------------------------- // [SECTION] STYLING //----------------------------------------------------------------------------- @@ -3297,55 +3535,61 @@ void ImGui::PopStyleColor(int count) } } -static const ImGuiDataVarInfo GStyleVarInfo[] = -{ - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding +static const ImGuiStyleVarInfo GStyleVarsInfo[] = +{ + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ScrollbarPadding) }, // ImGuiStyleVar_ScrollbarPadding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ImageBorderSize) }, // ImGuiStyleVar_ImageBorderSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthBase) }, // ImGuiStyleVar_TabMinWidthBase + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabMinWidthShrink) }, // ImGuiStyleVar_TabMinWidthShrink + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TabBarOverlineSize) }, // ImGuiStyleVar_TabBarOverlineSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesSize)}, // ImGuiStyleVar_TreeLinesSize + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, TreeLinesRounding)}, // ImGuiStyleVar_TreeLinesRounding + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { 1, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign + { 2, ImGuiDataType_Float, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding }; -const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) +const ImGuiStyleVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) { IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT); - IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT); - return &GStyleVarInfo[idx]; + IM_STATIC_ASSERT(IM_ARRAYSIZE(GStyleVarsInfo) == ImGuiStyleVar_COUNT); + return &GStyleVarsInfo[idx]; } void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) { ImGuiContext& g = *GImGui; - const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type != ImGuiDataType_Float || var_info->Count != 1) + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 1) { IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; @@ -3358,8 +3602,8 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) { ImGuiContext& g = *GImGui; - const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) { IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; @@ -3372,8 +3616,8 @@ void ImGui::PushStyleVarX(ImGuiStyleVar idx, float val_x) void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) { ImGuiContext& g = *GImGui; - const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) { IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; @@ -3386,8 +3630,8 @@ void ImGui::PushStyleVarY(ImGuiStyleVar idx, float val_y) void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) { ImGuiContext& g = *GImGui; - const ImGuiDataVarInfo* var_info = GetStyleVarInfo(idx); - if (var_info->Type != ImGuiDataType_Float || var_info->Count != 2) + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx); + if (var_info->DataType != ImGuiDataType_Float || var_info->Count != 2) { IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); return; @@ -3409,10 +3653,10 @@ void ImGui::PopStyleVar(int count) { // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it. ImGuiStyleMod& backup = g.StyleVarStack.back(); - const ImGuiDataVarInfo* info = GetStyleVarInfo(backup.VarIdx); - void* data = info->GetVarPtr(&g.Style); - if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } - else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } + const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(backup.VarIdx); + void* data = var_info->GetVarPtr(&g.Style); + if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; } + else if (var_info->DataType == ImGuiDataType_Float && var_info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; } g.StyleVarStack.pop_back(); count--; } @@ -3456,6 +3700,7 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ResizeGrip: return "ResizeGrip"; case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_InputTextCursor: return "InputTextCursor"; case ImGuiCol_TabHovered: return "TabHovered"; case ImGuiCol_Tab: return "Tab"; case ImGuiCol_TabSelected: return "TabSelected"; @@ -3474,8 +3719,11 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt"; case ImGuiCol_TextLink: return "TextLink"; case ImGuiCol_TextSelectedBg: return "TextSelectedBg"; + case ImGuiCol_TreeLines: return "TreeLines"; case ImGuiCol_DragDropTarget: return "DragDropTarget"; - case ImGuiCol_NavHighlight: return "NavHighlight"; + case ImGuiCol_DragDropTargetBg: return "DragDropTargetBg"; + case ImGuiCol_UnsavedMarker: return "UnsavedMarker"; + case ImGuiCol_NavCursor: return "NavCursor"; case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight"; case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg"; case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg"; @@ -3484,7 +3732,6 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) return "Unknown"; } - //----------------------------------------------------------------------------- // [SECTION] RENDER HELPERS // Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change, @@ -3519,7 +3766,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool else { if (!text_end) - text_end = text + strlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT text_display_end = text_end; } @@ -3537,7 +3784,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end ImGuiWindow* window = g.CurrentWindow; if (!text_end) - text_end = text + strlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT if (text != text_end) { @@ -3549,7 +3796,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end // Default clip_rect uses (pos_min,pos_max) // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) -// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especally for text above draw_list->DrawList. +// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especially for text above draw_list->DrawList. // Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take // better advantage of the render function taking size into account for coarse clipping. void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) @@ -3596,18 +3843,19 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons } // Another overly complex function until we reorganize everything into a nice all-in-one helper. -// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display. +// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) from 'ellipsis_max_x' which may be beyond it. // This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move. -void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known) +// (BREAKING) On 2025/04/16 we removed the 'float clip_max_x' parameters which was preceding 'float ellipsis_max' and was the same value for 99% of users. +void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known) { ImGuiContext& g = *GImGui; if (text_end_full == NULL) text_end_full = FindRenderedTextEnd(text); const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f); - //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255)); - //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0, 255)); - //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255)); + //draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255)); + //draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255)); + // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels. if (text_size.x > pos_max.x - pos_min.x) { @@ -3616,21 +3864,16 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con // min max ellipsis_max // <-> this is generally some padding value - const ImFont* font = draw_list->_Data->Font; + ImFont* font = draw_list->_Data->Font; const float font_size = draw_list->_Data->FontSize; const float font_scale = draw_list->_Data->FontScale; const char* text_end_ellipsis = NULL; - const float ellipsis_width = font->EllipsisWidth * font_scale; + ImFontBaked* baked = font->GetFontBaked(font_size); + const float ellipsis_width = baked->GetCharAdvance(font->EllipsisChar) * font_scale; // We can now claim the space between pos_max.x and ellipsis_max.x const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f); float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x; - if (text == text_end_ellipsis && text_end_ellipsis < text_end_full) - { - // Always display at least 1 character if there's no room for character + ellipsis - text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full); - text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x; - } while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1])) { // Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text) @@ -3639,15 +3882,14 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con } // Render text, render ellipsis - RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); + RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f)); + ImVec4 cpu_fine_clip_rect(pos_min.x, pos_min.y, pos_max.x, pos_max.y); ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y)); - if (ellipsis_pos.x + ellipsis_width <= ellipsis_max_x) - for (int i = 0; i < font->EllipsisCharCount; i++, ellipsis_pos.x += font->EllipsisCharStep * font_scale) - font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar); + font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect); } else { - RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size, ImVec2(0.0f, 0.0f)); + RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_full, &text_size, ImVec2(0.0f, 0.0f)); } if (g.LogEnabled) @@ -3680,24 +3922,26 @@ void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding) } } -void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags) +void ImGui::RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags) { ImGuiContext& g = *GImGui; if (id != g.NavId) return; - if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw)) + if (!g.NavCursorVisible && !(flags & ImGuiNavRenderCursorFlags_AlwaysDraw)) + return; + if (id == g.LastItemData.ID && (g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav)) return; ImGuiWindow* window = g.CurrentWindow; if (window->DC.NavHideHighlightOneFrame) return; - float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; + float rounding = (flags & ImGuiNavRenderCursorFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); const float thickness = 2.0f; - if (flags & ImGuiNavHighlightFlags_Compact) + if (flags & ImGuiNavRenderCursorFlags_Compact) { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); } else { @@ -3706,7 +3950,7 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavCursor), rounding, 0, thickness); if (!fully_visible) window->DrawList->PopClipRect(); } @@ -3717,25 +3961,32 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso ImGuiContext& g = *GImGui; if (mouse_cursor <= ImGuiMouseCursor_None || mouse_cursor >= ImGuiMouseCursor_COUNT) // We intentionally accept out of bound values. mouse_cursor = ImGuiMouseCursor_Arrow; - ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas; + ImFontAtlas* font_atlas = g.DrawListSharedData.FontAtlas; for (ImGuiViewportP* viewport : g.Viewports) { // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor. ImVec2 offset, size, uv[4]; - if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) + if (!ImFontAtlasGetMouseCursorTexData(font_atlas, mouse_cursor, &offset, &size, &uv[0], &uv[2])) continue; const ImVec2 pos = base_pos - offset; const float scale = base_scale; if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) continue; ImDrawList* draw_list = GetForegroundDrawList(viewport); - ImTextureID tex_id = font_atlas->TexID; - draw_list->PushTextureID(tex_id); - draw_list->AddImage(tex_id, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[2], uv[3], col_border); - draw_list->AddImage(tex_id, pos, pos + size * scale, uv[0], uv[1], col_fill); - draw_list->PopTextureID(); + ImTextureRef tex_ref = font_atlas->TexRef; + draw_list->PushTexture(tex_ref); + draw_list->AddImage(tex_ref, pos + ImVec2(1, 0) * scale, pos + (ImVec2(1, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos + ImVec2(2, 0) * scale, pos + (ImVec2(2, 0) + size) * scale, uv[2], uv[3], col_shadow); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[2], uv[3], col_border); + draw_list->AddImage(tex_ref, pos, pos + size * scale, uv[0], uv[1], col_fill); + if (mouse_cursor == ImGuiMouseCursor_Wait || mouse_cursor == ImGuiMouseCursor_Progress) + { + float a_min = ImFmod((float)g.Time * 5.0f, 2.0f * IM_PI); + float a_max = a_min + IM_PI * 1.65f; + draw_list->PathArcTo(pos + ImVec2(14, -1) * scale, 6.0f * scale, a_min, a_max); + draw_list->PathStroke(col_fill, ImDrawFlags_None, 3.0f * scale); + } + draw_list->PopTexture(); } } @@ -3817,23 +4068,28 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) InputTextState.Ctx = this; Initialized = false; - FontAtlasOwnedByContext = shared_font_atlas ? false : true; - Font = NULL; - FontSize = FontBaseSize = FontScale = CurrentDpiScale = 0.0f; - IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); - Time = 0.0f; + WithinFrameScope = WithinFrameScopeWithImplicitWindow = false; + TestEngineHookItems = false; FrameCount = 0; FrameCountEnded = FrameCountRendered = -1; - WithinFrameScope = WithinFrameScopeWithImplicitWindow = WithinEndChild = false; - GcCompactAll = false; - TestEngineHookItems = false; - TestEngine = NULL; + Time = 0.0f; memset(ContextName, 0, sizeof(ContextName)); + Font = NULL; + FontBaked = NULL; + FontSize = FontSizeBase = FontBakedScale = CurrentDpiScale = 0.0f; + FontRasterizerDensity = 1.0f; + IO.Fonts = shared_font_atlas ? shared_font_atlas : IM_NEW(ImFontAtlas)(); + if (shared_font_atlas == NULL) + IO.Fonts->OwnerContext = this; + WithinEndChildID = 0; + TestEngine = NULL; + InputEventsNextMouseSource = ImGuiMouseSource_Mouse; InputEventsNextEventId = 1; WindowsActiveCount = 0; + WindowsBorderHoverPadding = 0.0f; CurrentWindow = NULL; HoveredWindow = NULL; HoveredWindowUnderMovingWindow = NULL; @@ -3843,8 +4099,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) WheelingWindowStartFrame = WheelingWindowScrolledFrame = -1; WheelingWindowReleaseTimer = 0.0f; - DebugDrawIdConflicts = 0; - DebugHookIdInfo = 0; + DebugDrawIdConflictsId = 0; + DebugHookIdInfoId = 0; HoveredId = HoveredIdPreviousFrame = 0; HoveredIdPreviousFrameItemCount = 0; HoveredIdAllowOverlap = false; @@ -3862,13 +4118,13 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) ActiveIdHasBeenEditedThisFrame = false; ActiveIdFromShortcut = false; ActiveIdClickOffset = ImVec2(-1, -1); - ActiveIdWindow = NULL; ActiveIdSource = ImGuiInputSource_None; + ActiveIdWindow = NULL; ActiveIdMouseButton = -1; + ActiveIdDisabledId = 0; ActiveIdPreviousFrame = 0; - ActiveIdPreviousFrameIsAlive = false; - ActiveIdPreviousFrameHasBeenEditedBefore = false; - ActiveIdPreviousFrameWindow = NULL; + memset(&DeactivatedItemData, 0, sizeof(DeactivatedItemData)); + memset(&ActiveIdValueOnActivation, 0, sizeof(ActiveIdValueOnActivation)); LastActiveId = 0; LastActiveIdTimer = 0.0f; @@ -3880,9 +4136,15 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; DebugShowGroupRects = false; + GcCompactAll = false; + NavCursorVisible = false; + NavHighlightItemUnderNav = false; + NavMousePosDirty = false; + NavIdIsAlive = false; + NavId = 0; NavWindow = NULL; - NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; + NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavLayer = ImGuiNavLayer_Main; NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; @@ -3890,10 +4152,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) NavHighlightActivatedTimer = 0.0f; NavInputSource = ImGuiInputSource_Keyboard; NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; - NavIdIsAlive = false; - NavMousePosDirty = false; - NavDisableHighlight = true; - NavDisableMouseHover = false; + NavCursorHideFrames = 0; NavAnyRequest = false; NavInitRequest = false; @@ -3916,9 +4175,11 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) // All platforms use Ctrl+Tab but Ctrl<>Super are swapped on Mac... // FIXME: Because this value is stored, it annoyingly interfere with toggling io.ConfigMacOSXBehaviors updating this.. + ConfigNavWindowingWithGamepad = true; ConfigNavWindowingKeyNext = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiKey_Tab); ConfigNavWindowingKeyPrev = IO.ConfigMacOSXBehaviors ? (ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab) : (ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab); NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; + NavWindowingInputSource = ImGuiInputSource_None; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; NavWindowingToggleKey = ImGuiKey_None; @@ -3930,7 +4191,8 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DragDropSourceFrameCount = -1; DragDropMouseButton = -1; DragDropTargetId = 0; - DragDropAcceptFlags = ImGuiDragDropFlags_None; + DragDropTargetFullViewport = 0; + DragDropAcceptFlagsCurr = DragDropAcceptFlagsPrev = ImGuiDragDropFlags_None; DragDropAcceptIdCurrRectSurface = 0.0f; DragDropAcceptIdPrev = DragDropAcceptIdCurr = 0; DragDropAcceptFrameCount = -1; @@ -3951,6 +4213,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) MouseCursor = ImGuiMouseCursor_Arrow; MouseStationaryTimer = 0.0f; + InputTextPasswordFontBackupFlags = ImFontFlags_None; TempInputId = 0; memset(&DataTypeZeroValue, 0, sizeof(DataTypeZeroValue)); BeginMenuDepth = BeginComboDepth = 0; @@ -3969,7 +4232,6 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) DragSpeedDefaultRatio = 1.0f / 100.0f; DisabledAlphaBackup = 0.0f; DisabledStackSize = 0; - LockMarkEdited = 0; TooltipOverrideCount = 0; TooltipPreviousWindow = NULL; @@ -3983,11 +4245,12 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) memset(LocalizationTable, 0, sizeof(LocalizationTable)); LogEnabled = false; - LogType = ImGuiLogType_None; + LogLineFirstItem = false; + LogFlags = ImGuiLogFlags_None; + LogWindow = NULL; LogNextPrefix = LogNextSuffix = NULL; LogFile = NULL; LogLinePosY = FLT_MAX; - LogLineFirstItem = false; LogDepthRef = 0; LogDepthToExpand = LogDepthToExpandDefault = 2; @@ -4025,6 +4288,11 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas) memset(TempKeychordName, 0, sizeof(TempKeychordName)); } +ImGuiContext::~ImGuiContext() +{ + IM_ASSERT(Initialized == false && "Forgot to call DestroyContext()?"); +} + void ImGui::Initialize() { ImGuiContext& g = *GImGui; @@ -4070,6 +4338,18 @@ void ImGui::Initialize() #ifdef IMGUI_HAS_DOCK #endif + // Print a debug message when running with debug feature IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS because it is very slow. + // DO NOT COMMENT OUT THIS MESSAGE. IT IS DESIGNED TO REMIND YOU THAT IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS SHOULD ONLY BE TEMPORARILY ENABLED. +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + DebugLog("IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + + // ImDrawList/ImFontAtlas are designed to function without ImGui, and 99% of it works without an ImGui context. + // But this link allows us to facilitate/handle a few edge cases better. + ImFontAtlas* atlas = g.IO.Fonts; + g.DrawListSharedData.Context = &g; + RegisterFontAtlas(atlas); + g.Initialized = true; } @@ -4081,12 +4361,15 @@ void ImGui::Shutdown() IM_ASSERT_USER_ERROR(g.IO.BackendRendererUserData == NULL, "Forgot to shutdown Renderer backend?"); // The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which would happen if we never called NewFrame) - if (g.IO.Fonts && g.FontAtlasOwnedByContext) + for (ImFontAtlas* atlas : g.FontAtlases) { - g.IO.Fonts->Locked = false; - IM_DELETE(g.IO.Fonts); + UnregisterFontAtlas(atlas); + if (atlas->RefCount == 0) + { + atlas->Locked = false; + IM_DELETE(atlas); + } } - g.IO.Fonts = NULL; g.DrawListSharedData.TempBuffer.clear(); // Cleanup of other data are conditional on actually having initialized Dear ImGui. @@ -4108,7 +4391,7 @@ void ImGui::Shutdown() g.WindowsById.Clear(); g.NavWindow = NULL; g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL; - g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; + g.ActiveIdWindow = NULL; g.MovingWindow = NULL; g.KeysRoutingTable.Clear(); @@ -4138,6 +4421,7 @@ void ImGui::Shutdown() g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); + g.InputTextLineIndex.clear(); g.InputTextDeactivatedState.ClearFreeMemory(); g.SettingsWindows.clear(); @@ -4188,7 +4472,6 @@ void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type) hook.Callback(&g, &hook); } - //----------------------------------------------------------------------------- // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) //----------------------------------------------------------------------------- @@ -4199,23 +4482,24 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL memset(this, 0, sizeof(*this)); Ctx = ctx; Name = ImStrdup(name); - NameBufLen = (int)strlen(name) + 1; + NameBufLen = (int)ImStrlen(name) + 1; ID = ImHashStr(name); IDStack.push_back(ID); MoveId = GetID("#MOVE"); ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); - AutoFitFramesX = AutoFitFramesY = -1; AutoPosLastDirection = ImGuiDir_None; + AutoFitFramesX = AutoFitFramesY = -1; SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; LastTimeActive = -1.0f; - FontWindowScale = 1.0f; + FontRefSize = 0.0f; + FontWindowScale = FontWindowScaleParents = 1.0f; SettingsOffset = -1; DrawList = &DrawListInst; - DrawList->_Data = &Ctx->DrawListSharedData; DrawList->_OwnerName = Name; + DrawList->_SetDrawListSharedData(&Ctx->DrawListSharedData); NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX); } @@ -4235,8 +4519,15 @@ static void SetCurrentWindow(ImGuiWindow* window) g.CurrentDpiScale = 1.0f; // FIXME-DPI: WIP this is modified in docking if (window) { - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + bool backup_skip_items = window->SkipItems; + window->SkipItems = false; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + { + ImGuiViewport* viewport = window->Viewport; + g.FontRasterizerDensity = (viewport->FramebufferScale.x != 0.0f) ? viewport->FramebufferScale.x : g.IO.DisplayFramebufferScale.x; // == SetFontRasterizerDensity() + } + ImGui::UpdateCurrentFontSize(0.0f); + window->SkipItems = backup_skip_items; ImGui::NavUpdateCurrentWindowIsScrollPushableX(); } } @@ -4246,9 +4537,12 @@ void ImGui::GcCompactTransientMiscBuffers() ImGuiContext& g = *GImGui; g.ItemFlagsStack.clear(); g.GroupStack.clear(); + g.InputTextLineIndex.clear(); g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->CompactCache(); } // Free up/compact internal window buffers, we can use this when a window becomes unused. @@ -4284,20 +4578,27 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) // Clear previous active id if (g.ActiveId != 0) { + // Store deactivate data + ImGuiDeactivatedItemData* deactivated_data = &g.DeactivatedItemData; + deactivated_data->ID = g.ActiveId; + deactivated_data->ElapseFrame = (g.LastItemData.ID == g.ActiveId) ? g.FrameCount : g.FrameCount + 1; // FIXME: OK to use LastItemData? + deactivated_data->HasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; + deactivated_data->IsAlive = (g.ActiveIdIsAlive == g.ActiveId); + + // This could be written in a more general way (e.g associate a hook to ActiveId), + // but since this is currently quite an exception we'll leave it as is. + // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveID() + if (g.InputTextState.ID == g.ActiveId) + InputTextDeactivateHook(g.ActiveId); + // While most behaved code would make an effort to not steal active id during window move/drag operations, // we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that. if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId) { IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n"); - g.MovingWindow = NULL; + StopMouseMovingWindow(); } - - // This could be written in a more general way (e.g associate a hook to ActiveId), - // but since this is currently quite an exception we'll leave it as is. - // One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveId() - if (g.InputTextState.ID == g.ActiveId) - InputTextDeactivateHook(g.ActiveId); } // Set active id @@ -4321,6 +4622,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdWindow = window; g.ActiveIdHasBeenEditedThisFrame = false; g.ActiveIdFromShortcut = false; + g.ActiveIdDisabledId = 0; if (id) { g.ActiveIdIsAlive = id; @@ -4356,20 +4658,24 @@ ImGuiID ImGui::GetHoveredID() void ImGui::MarkItemEdited(ImGuiID id) { - // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). + // This marking is to be able to provide info for IsItemDeactivatedAfterEdit(). // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data. ImGuiContext& g = *GImGui; - if (g.LockMarkEdited > 0) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited) return; if (g.ActiveId == id || g.ActiveId == 0) { + // FIXME: Can't we fully rely on LastItemData yet? g.ActiveIdHasBeenEditedThisFrame = true; g.ActiveIdHasBeenEditedBefore = true; + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.HasBeenEditedBefore = true; } // We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343) // We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714) - IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); + // FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction. + IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive)); //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; @@ -4425,14 +4731,14 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0 && "Invalid flags for IsItemHovered()!"); + IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsItemHovered) == 0, "Invalid flags for IsItemHovered()!"); - if (g.NavDisableMouseHover && !g.NavDisableHighlight && !(flags & ImGuiHoveredFlags_NoNavOverride)) + if (g.NavHighlightItemUnderNav && g.NavCursorVisible && !(flags & ImGuiHoveredFlags_NoNavOverride)) { - if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) - return false; if (!IsItemFocused()) return false; + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + return false; if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipNav); @@ -4447,8 +4753,6 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) if (flags & ImGuiHoveredFlags_ForTooltip) flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse); - IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) == 0); // Flags not supported by this function - // Done with rectangle culling so we can perform heavier checks now // Test if we are hovering the right window (our window could be behind another window) // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) @@ -4463,16 +4767,25 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) const ImGuiID id = g.LastItemData.ID; if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0) if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) - if (g.ActiveId != window->MoveId) + { + // When ActiveId == MoveId it means that either: + // - (1) user clicked on void _or_ an item with no id, which triggers moving window (ActiveId is set even when window has _NoMove flag) + // - the (id == 0) test handles it, however, IsItemHovered() will leak between id==0 items (mostly visible when using _NoMove). // FIXME: May be fixed. + // - (2) user clicked a disabled item. UpdateMouseMovingWindowEndFrame() uses ActiveId == MoveId to avoid interference with item logic + sets ActiveIdDisabledId. + bool cancel_is_hovered = true; + if (g.ActiveId == window->MoveId && (id == 0 || g.ActiveIdDisabledId == id)) + cancel_is_hovered = false; + if (cancel_is_hovered) return false; + } // Test if interactions on this window are blocked by an active popup or modal. // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. - if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.InFlags & ImGuiItemFlags_NoWindowHoverableCheck)) + if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_NoWindowHoverableCheck)) return false; // Test if the item is disabled - if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; // Special handling for calling after Begin() which represent the title bar or tab. @@ -4482,7 +4795,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; // Test if using AllowOverlap and overlapped - if ((g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap) && id != 0) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap) && id != 0) if ((flags & ImGuiHoveredFlags_AllowWhenOverlappedByItem) == 0) if (g.HoveredIdPreviousFrame != g.LastItemData.ID) return false; @@ -4511,11 +4824,12 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return true; } -// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). +// Internal facing ItemHoverable() used when submitting widgets. THIS IS A SUBMISSION NOT A HOVER CHECK. +// Returns whether the item was hovered, logic differs slightly from IsItemHovered(). // (this does not rely on LastItemData it can be called from a ButtonBehavior() call not following an ItemAdd() call) // FIXME-LEGACY: the 'ImGuiItemFlags item_flags' parameter was added on 2023-06-28. // If you used this in your legacy/custom widgets code: -// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.InFlags'. +// - Commonly: if your ItemHoverable() call comes after an ItemAdd() call: pass 'item_flags = g.LastItemData.ItemFlags'. // - Rare: otherwise you may pass 'item_flags = 0' (ImGuiItemFlags_None) unless you want to benefit from special behavior handled by ItemHoverable. bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags) { @@ -4523,11 +4837,12 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag ImGuiWindow* window = g.CurrentWindow; // Detect ID conflicts + // (this is specifically done here by comparing on hover because it allows us a detection of duplicates that is algorithmically extra cheap, 1 u32 compare per item. No O(log N) lookup whatsoever) #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0 && g.HoveredIdPreviousFrame == id && (item_flags & ImGuiItemFlags_AllowDuplicateId) == 0) { g.HoveredIdPreviousFrameItemCount++; - if (g.DebugDrawIdConflicts == id) + if (g.DebugDrawIdConflictsId == id) window->DrawList->AddRect(bb.Min - ImVec2(1,1), bb.Max + ImVec2(1,1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); } #endif @@ -4543,7 +4858,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag if (!g.ActiveIdFromShortcut) return false; - // Done with rectangle culling so we can perform heavier checks now. + // We are done with rectangle culling so we can perform heavier checks now. if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdIsDisabled = true; @@ -4600,7 +4915,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag } #endif - if (g.NavDisableMouseHover) + if (g.NavHighlightItemUnderNav && (item_flags & ImGuiItemFlags_NoNavDisableMouseHover) == 0) return false; return true; @@ -4621,15 +4936,27 @@ bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) // This is also inlined in ItemAdd() // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set g.LastItemData.DisplayRect. -void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags item_flags, const ImRect& item_rect) +void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect) { ImGuiContext& g = *GImGui; g.LastItemData.ID = item_id; - g.LastItemData.InFlags = in_flags; - g.LastItemData.StatusFlags = item_flags; + g.LastItemData.ItemFlags = item_flags; + g.LastItemData.StatusFlags = status_flags; g.LastItemData.Rect = g.LastItemData.NavRect = item_rect; } +static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DC.WindowItemStatusFlags, rect); +} + +static void ImGui::SetLastItemDataForChildWindowItem(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + SetLastItemData(window->ChildId, g.CurrentItemFlags, window->DC.ChildItemStatusFlags, rect); +} + float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x) { if (wrap_pos_x < 0.0f) @@ -4690,15 +5017,15 @@ void ImGui::DebugAllocHook(ImGuiDebugAllocInfo* info, int frame_count, void* ptr } if (size != (size_t)-1) { + //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, (int)size, ptr); entry->AllocCount++; info->TotalAllocCount++; - //printf("[%05d] MemAlloc(%d) -> 0x%p\n", frame_count, size, ptr); } else { + //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); entry->FreeCount++; info->TotalFreeCount++; - //printf("[%05d] MemFree(0x%p)\n", frame_count, ptr); } } @@ -4726,12 +5053,26 @@ ImGuiIO& ImGui::GetIO() return GImGui->IO; } +// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) +ImGuiIO& ImGui::GetIO(ImGuiContext* ctx) +{ + IM_ASSERT(ctx != NULL); + return ctx->IO; +} + ImGuiPlatformIO& ImGui::GetPlatformIO() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext()?"); return GImGui->PlatformIO; } +// This variant exists to facilitate backends experimenting with multi-threaded parallel context. (#8069, #6293, #5856) +ImGuiPlatformIO& ImGui::GetPlatformIO(ImGuiContext* ctx) +{ + IM_ASSERT(ctx != NULL); + return ctx->PlatformIO; +} + // Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame() ImDrawData* ImGui::GetDrawData() { @@ -4767,7 +5108,7 @@ static ImDrawList* GetViewportBgFgDrawList(ImGuiViewportP* viewport, size_t draw if (viewport->BgFgDrawListsLastFrame[drawlist_no] != g.FrameCount) { draw_list->_ResetForNewFrame(); - draw_list->PushTextureID(g.IO.Fonts->TexID); + draw_list->PushTexture(g.IO.Fonts->TexRef); draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); viewport->BgFgDrawListsLastFrame[drawlist_no] = g.FrameCount; } @@ -4809,7 +5150,8 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) ImGuiContext& g = *GImGui; FocusWindow(window); SetActiveID(window->MoveId, window); - g.NavDisableHighlight = true; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos; g.ActiveIdNoClearOnFocusLoss = true; SetActiveIdUsingAllKeyboardKeys(); @@ -4821,9 +5163,22 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window) g.MovingWindow = window; } +// This is not 100% symetric with StartMouseMovingWindow(). +// We do NOT clear ActiveID, because: +// - It would lead to rather confusing recursive code paths. Caller can call ClearActiveID() if desired. +// - Some code intentionally cancel moving but keep the ActiveID to lock inputs (e.g. code path taken when clicking a disabled item). +void ImGui::StopMouseMovingWindow() +{ + ImGuiContext& g = *GImGui; + + // [nb: docking branch has more stuff in this function] + + g.MovingWindow = NULL; +} + // Handle mouse moving window -// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing() -// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId. +// Note: moving window with the navigation keys (Square + d-pad / Ctrl+Tab + Arrows) are processed in NavUpdateWindowing() +// FIXME: We don't have strong guarantee that g.MovingWindow stay synced with g.ActiveId == g.MovingWindow->MoveId. // This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs, // but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other. void ImGui::UpdateMouseMovingWindowNewFrame() @@ -4844,7 +5199,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } else { - g.MovingWindow = NULL; + StopMouseMovingWindow(); ClearActiveID(); } } @@ -4860,42 +5215,52 @@ void ImGui::UpdateMouseMovingWindowNewFrame() } } -// Initiate moving window when clicking on empty space or title bar. +// Initiate focusing and moving window when clicking on empty space or title bar. +// Initiate focusing window when clicking on a disabled item. // Handle left-click and right-click focus. void ImGui::UpdateMouseMovingWindowEndFrame() { ImGuiContext& g = *GImGui; - if (g.ActiveId != 0 || g.HoveredId != 0) + if (g.ActiveId != 0 || (g.HoveredId != 0 && !g.HoveredIdIsDisabled)) return; // Unless we just made a window/popup appear if (g.NavWindow && g.NavWindow->Appearing) return; + ImGuiWindow* hovered_window = g.HoveredWindow; + // Click on empty space to focus window and start moving // (after we're done with all our widgets) if (g.IO.MouseClicked[0]) { // Handle the edge case of a popup being closed while clicking in its empty space. // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more. - ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; - const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel); + ImGuiWindow* hovered_root = hovered_window ? hovered_window->RootWindow : NULL; + const bool is_closed_popup = hovered_root && (hovered_root->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(hovered_root->PopupId, ImGuiPopupFlags_AnyPopupLevel); - if (root_window != NULL && !is_closed_popup) + if (hovered_window != NULL && !is_closed_popup) { - StartMouseMovingWindow(g.HoveredWindow); //-V595 + StartMouseMovingWindow(hovered_window); //-V595 + + // FIXME: In principle we might be able to call StopMouseMovingWindow() below. + // Please note how StartMouseMovingWindow() and StopMouseMovingWindow() and not entirely symetrical, at the later doesn't clear ActiveId. // Cancel moving if clicked outside of title bar - if (g.IO.ConfigWindowsMoveFromTitleBarOnly) - if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar)) - if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) + if ((hovered_window->BgClickFlags & ImGuiWindowBgClickFlags_Move) == 0) // set by io.ConfigWindowsMoveFromTitleBarOnly + if (!(hovered_root->Flags & ImGuiWindowFlags_NoTitleBar)) + if (!hovered_root->TitleBarRect().Contains(g.IO.MouseClickedPos[0])) g.MovingWindow = NULL; - // Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already) + // Cancel moving if clicked over an item which was disabled or inhibited by popups + // (when g.HoveredIdIsDisabled == true && g.HoveredId == 0 we are inhibited by popups, when g.HoveredIdIsDisabled == true && g.HoveredId != 0 we are over a disabled item) if (g.HoveredIdIsDisabled) + { g.MovingWindow = NULL; + g.ActiveIdDisabledId = g.HoveredId; + } } - else if (root_window == NULL && g.NavWindow != NULL) + else if (hovered_window == NULL && g.NavWindow != NULL) { // Clicking on void disable focus FocusWindow(NULL, ImGuiFocusRequestFlags_UnlessBelowModal); @@ -4905,13 +5270,13 @@ void ImGui::UpdateMouseMovingWindowEndFrame() // With right mouse button we close popups without changing focus based on where the mouse is aimed // Instead, focus will be restored to the window under the bottom-most closed popup. // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger) - if (g.IO.MouseClicked[1]) + if (g.IO.MouseClicked[1] && g.HoveredId == 0) { // Find the top-most window between HoveredWindow and the top-most Modal Window. // This is where we can trim the popup stack. ImGuiWindow* modal = GetTopMostPopupModal(); - bool hovered_window_above_modal = g.HoveredWindow && (modal == NULL || IsWindowAbove(g.HoveredWindow, modal)); - ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true); + bool hovered_window_above_modal = hovered_window && (modal == NULL || IsWindowAbove(hovered_window, modal)); + ClosePopupsOverWindow(hovered_window_above_modal ? hovered_window : modal, true); } } @@ -4921,21 +5286,21 @@ static bool IsWindowActiveAndVisible(ImGuiWindow* window) } // The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to dispatch inputs to the right target (imgui vs imgui+app) -void ImGui::UpdateHoveredWindowAndCaptureFlags() +void ImGui::UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos) { ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow. - g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING)); + g.WindowsBorderHoverPadding = ImMax(ImMax(g.Style.TouchExtraPadding.x, g.Style.TouchExtraPadding.y), g.Style.WindowBorderHoverPadding); // Find the window hovered by mouse: // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow. // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame. // - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms. bool clear_hovered_windows = false; - FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); + FindHoveredWindowEx(mouse_pos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow); g.HoveredWindowBeforeClear = g.HoveredWindow; // Modal windows prevents mouse from hovering behind them. @@ -4990,9 +5355,14 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() } // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false = dispatch keyboard info to Dear ImGui + underlying app) - io.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL); - if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard)) - io.WantCaptureKeyboard = true; + io.WantCaptureKeyboard = false; + if ((io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) == 0) + { + if ((g.ActiveId != 0) || (modal_window != NULL)) + io.WantCaptureKeyboard = true; + else if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && io.ConfigNavCaptureKeyboard) + io.WantCaptureKeyboard = true; + } if (g.WantCaptureKeyboardNextFrame != -1) // Manual override io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0); @@ -5000,7 +5370,8 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } -// Calling SetupDrawListSharedData() is followed by SetCurrentFont() which sets up the remaining data. +// Called once a frame. Followed by SetCurrentFont() which sets up the remaining data. +// FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! static void SetupDrawListSharedData() { ImGuiContext& g = *GImGui; @@ -5019,6 +5390,7 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; + g.DrawListSharedData.InitialFringeScale = 1.0f; // FIXME-DPI: Change this for some DPI scaling experiments. } void ImGui::NewFrame() @@ -5041,7 +5413,6 @@ void ImGui::NewFrame() UpdateSettings(); g.Time += g.IO.DeltaTime; - g.WithinFrameScope = true; g.FrameCount += 1; g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; @@ -5061,11 +5432,14 @@ void ImGui::NewFrame() // Update viewports (after processing input queue, so io.MouseHoveredViewport is set) UpdateViewportsNewFrame(); + // Update texture list (collect destroyed textures, etc.) + UpdateTexturesNewFrame(); + // Setup current font and draw list shared data - g.IO.Fonts->Locked = true; SetupDrawListSharedData(); - SetCurrentFont(GetDefaultFont()); - IM_ASSERT(g.Font->IsLoaded()); + UpdateFontsNewFrame(); + + g.WithinFrameScope = true; // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) @@ -5076,10 +5450,10 @@ void ImGui::NewFrame() KeepAliveID(g.DragDropPayload.SourceId); // [DEBUG] - if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding CTRL - g.DebugDrawIdConflicts = 0; + if (!g.IO.ConfigDebugHighlightIdConflicts || !g.IO.KeyCtrl) // Count is locked while holding Ctrl + g.DebugDrawIdConflictsId = 0; if (g.IO.ConfigDebugHighlightIdConflicts && g.HoveredIdPreviousFrameItemCount > 1) - g.DebugDrawIdConflicts = g.HoveredIdPreviousFrame; + g.DebugDrawIdConflictsId = g.HoveredIdPreviousFrame; // Update HoveredId data if (!g.HoveredIdPreviousFrame) @@ -5110,11 +5484,8 @@ void ImGui::NewFrame() g.ActiveIdTimer += g.IO.DeltaTime; g.LastActiveIdTimer += g.IO.DeltaTime; g.ActiveIdPreviousFrame = g.ActiveId; - g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow; - g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore; g.ActiveIdIsAlive = 0; g.ActiveIdHasBeenEditedThisFrame = false; - g.ActiveIdPreviousFrameIsAlive = false; g.ActiveIdIsJustActivated = false; if (g.TempInputId != 0 && g.ActiveId != g.TempInputId) g.TempInputId = 0; @@ -5123,6 +5494,9 @@ void ImGui::NewFrame() g.ActiveIdUsingNavDirMask = 0x00; g.ActiveIdUsingAllKeyboardKeys = false; } + if (g.DeactivatedItemData.ElapseFrame < g.FrameCount) + g.DeactivatedItemData.ID = 0; + g.DeactivatedItemData.IsAlive = false; // Record when we have been stationary as this state is preserved while over same item. // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. @@ -5153,15 +5527,6 @@ void ImGui::NewFrame() g.HoverItemDelayTimer = g.HoverItemDelayClearTimer = 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer. } - // Drag and drop - g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; - g.DragDropAcceptIdCurr = 0; - g.DragDropAcceptIdCurrRectSurface = FLT_MAX; - g.DragDropWithinSource = false; - g.DragDropWithinTarget = false; - g.DragDropHoldJustPressedId = 0; - g.TooltipPreviousWindow = NULL; - // Close popups on focus lost (currently wip/opt-in) //if (g.IO.AppFocusLost) // ClosePopupsExceptModals(); @@ -5174,7 +5539,31 @@ void ImGui::NewFrame() //IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt)); //IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper)); - // Update gamepad/keyboard navigation + // Drag and drop + g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr; + g.DragDropAcceptIdCurr = 0; + g.DragDropAcceptFlagsPrev = g.DragDropAcceptFlagsCurr; + g.DragDropAcceptFlagsCurr = ImGuiDragDropFlags_None; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropWithinSource = false; + g.DragDropWithinTarget = false; + g.DragDropHoldJustPressedId = 0; + if (g.DragDropActive) + { + // Also works when g.ActiveId==0 (aka leftover payload in progress, no active id) + // You may disable this externally by hijacking the input route: + // 'if (GetDragDropPayload() != NULL) { Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive); } + // but you will not get a return value from Shortcut() due to ActiveIdUsingAllKeyboardKeys logic. You can however poll IsKeyPressed(ImGuiKey_Escape) afterwards. + ImGuiID owner_id = g.ActiveId ? g.ActiveId : ImHashStr("##DragDropCancelHandler"); + if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteGlobal, owner_id)) + { + ClearActiveID(); + ClearDragDrop(); + } + } + g.TooltipPreviousWindow = NULL; + + // Update keyboard/gamepad navigation NavUpdate(); // Update mouse input state @@ -5199,7 +5588,7 @@ void ImGui::NewFrame() // Find hovered window // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame) // (currently needs to be done after the WasActive=Active loop and FindHoveredWindowEx uses ->Active) - UpdateHoveredWindowAndCaptureFlags(); + UpdateHoveredWindowAndCaptureFlags(g.IO.MousePos); // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) UpdateMouseMovingWindowNewFrame(); @@ -5215,7 +5604,7 @@ void ImGui::NewFrame() // Platform IME data: reset for the frame g.PlatformImeDataPrev = g.PlatformImeData; - g.PlatformImeData.WantVisible = false; + g.PlatformImeData.WantVisible = g.PlatformImeData.WantTextInput = false; // Mouse wheel scrolling, scale UpdateMouseWheel(); @@ -5247,7 +5636,7 @@ void ImGui::NewFrame() // [DEBUG] Update debug features #ifndef IMGUI_DISABLE_DEBUG_TOOLS UpdateDebugToolItemPicker(); - UpdateDebugToolStackQueries(); + UpdateDebugToolItemPathQuery(); UpdateDebugToolFlashStyleColor(); if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0) { @@ -5373,6 +5762,7 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) draw_data->DisplaySize = viewport->Size; draw_data->FramebufferScale = io.DisplayFramebufferScale; draw_data->OwnerViewport = viewport; + draw_data->Textures = &ImGui::GetPlatformIO().Textures; } // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. @@ -5381,7 +5771,7 @@ static void InitViewportDrawData(ImGuiViewportP* viewport) // - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect(): // some frequently called functions which to modify both channels and clipping simultaneously tend to use the // more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds. -// - This is analoguous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack, +// - This is analogous to PushFont()/PopFont() in the sense that are a mixing a global stack and a window stack, // which in the case of ClipRect is not so problematic but tends to be more restrictive for fonts. void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect) { @@ -5460,10 +5850,10 @@ static void ImGui::RenderDimmedBackgrounds() } else if (dim_bg_for_window_list) { - // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window + // Draw dimming behind Ctrl+Tab target window and behind Ctrl+Tab UI window RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio)); - // Draw border around CTRL+Tab target window + // Draw border around Ctrl+Tab target window ImGuiWindow* window = g.NavWindowingTargetAnim; ImGuiViewport* viewport = GetMainViewport(); float distance = g.FontSize; @@ -5474,7 +5864,7 @@ static void ImGui::RenderDimmedBackgrounds() if (window->DrawList->CmdBuffer.Size == 0) window->DrawList->AddDrawCmd(); window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f); // FIXME-DPI window->DrawList->PopClipRect(); } } @@ -5488,7 +5878,11 @@ void ImGui::EndFrame() // Don't process EndFrame() multiple times. if (g.FrameCountEnded == g.FrameCount) return; - IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?"); + if (!g.WithinFrameScope) + { + IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?"); + return; + } CallContextHooks(&g, ImGuiContextHookType_EndFramePre); @@ -5502,10 +5896,12 @@ void ImGui::EndFrame() ImGuiPlatformImeData* ime_data = &g.PlatformImeData; if (g.PlatformIO.Platform_SetImeDataFn != NULL && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0) { - IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); + IM_ASSERT(ime_data->ViewportId == IMGUI_VIEWPORT_DEFAULT_ID); // master branch ImGuiViewport* viewport = GetMainViewport(); + IMGUI_DEBUG_LOG_IO("[io] Calling Platform_SetImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f) for Viewport 0x%08X\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y, viewport->ID); g.PlatformIO.Platform_SetImeDataFn(&g, viewport, ime_data); } + g.WantTextInputNextFrame = ime_data->WantTextInput ? 1 : 0; // Hide implicit/fallback "Debug" window if it hasn't been used g.WithinFrameScopeWithImplicitWindow = false; @@ -5513,7 +5909,7 @@ void ImGui::EndFrame() g.CurrentWindow->Active = false; End(); - // Update navigation: CTRL+Tab, wrap-around requests + // Update navigation: Ctrl+Tab, wrap-around requests NavEndFrame(); // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) @@ -5540,6 +5936,7 @@ void ImGui::EndFrame() // End frame g.WithinFrameScope = false; g.FrameCountEnded = g.FrameCount; + UpdateFontsEndFrame(); // Initiate moving window + handle left-click and right-click focus UpdateMouseMovingWindowEndFrame(); @@ -5560,8 +5957,11 @@ void ImGui::EndFrame() g.Windows.swap(g.WindowsTempSortBuffer); g.IO.MetricsActiveWindows = g.WindowsActiveCount; + UpdateTexturesEndFrame(); + // Unlock font atlas - g.IO.Fonts->Locked = false; + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = false; // Clear Input data for next frame g.IO.MousePosPrev = g.IO.MousePos; @@ -5573,7 +5973,7 @@ void ImGui::EndFrame() } // Prepare the data for rendering so you can call GetDrawData() -// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all: +// (As with anything within the ImGui:: namespace this doesn't touch your GPU or graphics API at all: // it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend) void ImGui::Render() { @@ -5638,6 +6038,12 @@ void ImGui::Render() g.IO.MetricsRenderIndices += draw_data->TotalIdxCount; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + for (ImFontAtlas* atlas : g.FontAtlases) + ImFontAtlasDebugLogTextureRequests(atlas); +#endif + CallContextHooks(&g, ImGuiContextHookType_RenderPost); } @@ -5685,7 +6091,7 @@ void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_vi hovered_window = g.MovingWindow; ImVec2 padding_regular = g.Style.TouchExtraPadding; - ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular; + ImVec2 padding_for_resize = ImMax(g.Style.TouchExtraPadding, ImVec2(g.Style.WindowBorderHoverPadding, g.Style.WindowBorderHoverPadding)); for (int i = g.Windows.Size - 1; i >= 0; i--) { ImGuiWindow* window = g.Windows[i]; @@ -5754,22 +6160,20 @@ bool ImGui::IsItemDeactivated() ImGuiContext& g = *GImGui; if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated) return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0; - return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID); + return (g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount); } bool ImGui::IsItemDeactivatedAfterEdit() { ImGuiContext& g = *GImGui; - return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)); + return IsItemDeactivated() && g.DeactivatedItemData.HasBeenEditedBefore; } -// == GetItemID() == GetFocusID() +// == (GetItemID() == GetFocusID() && GetFocusID() != 0) bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; - if (g.NavId != g.LastItemData.ID || g.NavId == 0) - return false; - return true; + return g.NavId == g.LastItemData.ID && g.NavId != 0; } // Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! @@ -5815,7 +6219,7 @@ bool ImGui::IsAnyItemActive() bool ImGui::IsAnyItemFocused() { ImGuiContext& g = *GImGui; - return g.NavId != 0 && !g.NavDisableHighlight; + return g.NavId != 0 && g.NavCursorVisible; } bool ImGui::IsItemVisible() @@ -5841,16 +6245,16 @@ void ImGui::SetNextItemAllowOverlap() #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. -// FIXME-LEGACY: Use SetNextItemAllowOverlap() *before* your item instead. -void ImGui::SetItemAllowOverlap() -{ - ImGuiContext& g = *GImGui; - ImGuiID id = g.LastItemData.ID; - if (g.HoveredId == id) - g.HoveredIdAllowOverlap = true; - if (g.ActiveId == id) // Before we made this obsolete, most calls to SetItemAllowOverlap() used to avoid this path by testing g.ActiveId != id. - g.ActiveIdAllowOverlap = true; -} +// Use SetNextItemAllowOverlap() *before* your item instead of calling this! +//void ImGui::SetItemAllowOverlap() +//{ +// ImGuiContext& g = *GImGui; +// ImGuiID id = g.LastItemData.ID; +// if (g.HoveredId == id) +// g.HoveredIdAllowOverlap = true; +// if (g.ActiveId == id) // Before we made this obsolete, most calls to SetItemAllowOverlap() used to avoid this path by testing g.ActiveId != id. +// g.ActiveIdAllowOverlap = true; +//} #endif // This is a shortcut for not taking ownership of 100+ keys, frequently used by drag operations. @@ -5918,10 +6322,10 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I IM_ASSERT((child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) != 0 && "Must use ImGuiChildFlags_AutoResizeX or ImGuiChildFlags_AutoResizeY with ImGuiChildFlags_AlwaysAutoResize!"); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) - child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; - if (window_flags & ImGuiWindowFlags_NavFlattened) - child_flags |= ImGuiChildFlags_NavFlattened; + //if (window_flags & ImGuiWindowFlags_AlwaysUseWindowPadding) + // child_flags |= ImGuiChildFlags_AlwaysUseWindowPadding; + //if (window_flags & ImGuiWindowFlags_NavFlattened) + // child_flags |= ImGuiChildFlags_NavFlattened; #endif if (child_flags & ImGuiChildFlags_AutoResizeX) child_flags &= ~ImGuiChildFlags_ResizeX; @@ -5957,7 +6361,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I // A SetNextWindowSize() call always has priority (#8020) // (since the code in Begin() never supported SizeVal==0.0f aka auto-resize via SetNextWindowSize() call, we don't support it here for now) // FIXME: We only support ImGuiCond_Always in this path. Supporting other paths would requires to obtain window pointer. - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) != 0 && (g.NextWindowData.SizeCond & ImGuiCond_Always) != 0) { if (g.NextWindowData.SizeVal.x > 0.0f) { @@ -5972,9 +6376,12 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I } SetNextWindowSize(size); - // Forward child flags - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasChildFlags; - g.NextWindowData.ChildFlags = child_flags; + // Forward child flags (we allow prior settings to merge but it'll only work for adding flags) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) + g.NextWindowData.ChildFlags |= child_flags; + else + g.NextWindowData.ChildFlags = child_flags; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasChildFlags; // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. // FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround. @@ -6032,10 +6439,10 @@ void ImGui::EndChild() ImGuiContext& g = *GImGui; ImGuiWindow* child_window = g.CurrentWindow; - IM_ASSERT(g.WithinEndChild == false); + const ImGuiID backup_within_end_child_id = g.WithinEndChildID; IM_ASSERT(child_window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls - g.WithinEndChild = true; + g.WithinEndChildID = child_window->ID; ImVec2 child_size = child_window->Size; End(); if (child_window->BeginCount == 1) @@ -6047,11 +6454,11 @@ void ImGui::EndChild() if ((child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavWindowHasScrollY) && !nav_flattened) { ItemAdd(bb, child_window->ChildId); - RenderNavHighlight(bb, child_window->ChildId); + RenderNavCursor(bb, child_window->ChildId); // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying) if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow) - RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_Compact); + RenderNavCursor(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavRenderCursorFlags_Compact); } else { @@ -6066,8 +6473,15 @@ void ImGui::EndChild() } if (g.HoveredWindow == child_window) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; + child_window->DC.ChildItemStatusFlags = g.LastItemData.StatusFlags; + //SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); // Not needed, effectively done by ItemAdd() + } + else + { + SetLastItemDataForChildWindowItem(child_window, child_window->Rect()); } - g.WithinEndChild = false; + + g.WithinEndChildID = backup_within_end_child_id; g.LogLinePosY = -FLT_MAX; // To enforce a carriage return } @@ -6098,39 +6512,16 @@ static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settin window->Collapsed = settings->Collapsed; } -static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) +static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) { - ImGuiContext& g = *GImGui; + // Initial window state with e.g. default/arbitrary window position + // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. + const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + window->Pos = main_viewport->Pos + ImVec2(60, 60); + window->Size = window->SizeFull = ImVec2(0, 0); + window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); - const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; - if ((just_created || child_flag_changed) && !new_is_explicit_child) - { - IM_ASSERT(!g.WindowsFocusOrder.contains(window)); - g.WindowsFocusOrder.push_back(window); - window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); - } - else if (!just_created && child_flag_changed && new_is_explicit_child) - { - IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window); - for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++) - g.WindowsFocusOrder[n]->FocusOrder--; - g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder); - window->FocusOrder = -1; - } - window->IsExplicitChild = new_is_explicit_child; -} - -static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings) -{ - // Initial window state with e.g. default/arbitrary window position - // Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. - const ImGuiViewport* main_viewport = ImGui::GetMainViewport(); - window->Pos = main_viewport->Pos + ImVec2(60, 60); - window->Size = window->SizeFull = ImVec2(0, 0); - window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; - - if (settings != NULL) + if (settings != NULL) { SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); ApplyWindowSettings(window, settings); @@ -6204,7 +6595,7 @@ static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& s { ImGuiContext& g = *GImGui; ImVec2 new_size = size_desired; - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint) { // See comments in SetNextWindowSizeConstraints() for details about setting size_min an size_max. ImRect cr = g.NextWindowData.SizeConstraintRect; @@ -6243,39 +6634,36 @@ static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_cur return; } - content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); - content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); - content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_TRUNC(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); - content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_TRUNC(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); + content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x); + content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y); + content_size_ideal->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : ImTrunc64(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x); + content_size_ideal->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : ImTrunc64(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y); } -static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents) +static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents, int axis_mask) { ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; const float decoration_w_without_scrollbars = window->DecoOuterSizeX1 + window->DecoOuterSizeX2 - window->ScrollbarSizes.x; const float decoration_h_without_scrollbars = window->DecoOuterSizeY1 + window->DecoOuterSizeY2 - window->ScrollbarSizes.y; ImVec2 size_pad = window->WindowPadding * 2.0f; - ImVec2 size_desired = size_contents + size_pad + ImVec2(decoration_w_without_scrollbars, decoration_h_without_scrollbars); + ImVec2 size_desired; + size_desired[ImGuiAxis_X] = (axis_mask & 1) ? size_contents.x + size_pad.x + decoration_w_without_scrollbars : window->Size.x; + size_desired[ImGuiAxis_Y] = (axis_mask & 2) ? size_contents.y + size_pad.y + decoration_h_without_scrollbars : window->Size.y; + + // Determine maximum window size + // Child windows are layed within their parent (unless they are also popups/menus) and thus have no restriction + ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; + if (window->Flags & ImGuiWindowFlags_Tooltip) { - // Tooltip always resize - return size_desired; + // Tooltip always resize (up to maximum size) + return ImMin(size_desired, size_max); } else { - // Maximum window size is determined by the viewport size or monitor size ImVec2 size_min = CalcWindowMinSize(window); - ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; - ImVec2 size_auto_fit = ImClamp(size_desired, size_min, size_max); - - // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars, - // we may need to compute/store three variants of size_auto_fit, for x/y/xy. - // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well: - if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY)) - size_auto_fit.y = window->SizeFull.y; - else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY)) - size_auto_fit.x = window->SizeFull.x; + ImVec2 size_auto_fit = ImClamp(size_desired, ImMin(size_min, size_max), size_max); // When the window cannot fit all contents (either because of constraints, either because screen is too small), // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding. @@ -6295,7 +6683,7 @@ ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window) ImVec2 size_contents_current; ImVec2 size_contents_ideal; CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal); - ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal); + ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal, ~0); ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit); return size_final; } @@ -6309,8 +6697,20 @@ static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window) return ImGuiCol_WindowBg; } -static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) +static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target_arg, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size) { + ImVec2 corner_target = corner_target_arg; + if (window->Flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent + { + ImGuiWindow* parent_window = window->ParentWindow; + ImGuiWindowFlags parent_flags = parent_window->Flags; + ImRect limit_rect = parent_window->InnerRect; + limit_rect.Expand(ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize), -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize))); + if ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar)) + corner_target.x = ImClamp(corner_target.x, limit_rect.Min.x, limit_rect.Max.x); + if (parent_flags & ImGuiWindowFlags_NoScrollbar) + corner_target.y = ImClamp(corner_target.y, limit_rect.Min.y, limit_rect.Max.y); + } ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right ImVec2 size_expected = pos_max - pos_min; @@ -6389,12 +6789,14 @@ ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir) // Handle resize for: Resize Grips, Borders, Gamepad // Return true when using auto-fit (double-click on resize grip) -static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect) +static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, int* border_hovered, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect) { ImGuiContext& g = *GImGui; ImGuiWindowFlags flags = window->Flags; - if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + if ((flags & ImGuiWindowFlags_NoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + return false; + if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && (window->ChildFlags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) return false; if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window. return false; @@ -6402,10 +6804,10 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si int ret_auto_fit_mask = 0x00; const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f; - const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; + const float grip_hover_outer_size = g.WindowsBorderHoverPadding; ImRect clamp_rect = visibility_rect; - const bool window_move_from_title_bar = g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar); + const bool window_move_from_title_bar = !(window->BgClickFlags & ImGuiWindowBgClickFlags_Move) && !(window->Flags & ImGuiWindowFlags_NoTitleBar); if (window_move_from_title_bar) clamp_rect.Min.y -= window->TitleBarHeight; @@ -6437,8 +6839,9 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si if (held && g.IO.MouseDoubleClicked[0]) { // Auto-fit when double-clicking + ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal, ~0); size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit); - ret_auto_fit_mask = 0x03; // Both axises + ret_auto_fit_mask = 0x03; // Both axes ClearActiveID(); } else if (held) @@ -6453,7 +6856,8 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si } // Only lower-left grip is visible before hovering/activating - if (resize_grip_n == 0 || held || hovered) + const bool resize_grip_visible = held || hovered || (resize_grip_n == 0 && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0); + if (resize_grip_visible) resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); } @@ -6470,7 +6874,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y; bool hovered, held; - ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING); + ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, g.WindowsBorderHoverPadding); ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID() ItemAdd(border_rect, border_id, NULL, ImGuiItemFlags_NoNav); ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus); @@ -6482,10 +6886,10 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si if (held && g.IO.MouseDoubleClicked[0]) { // Double-clicking bottom or right border auto-fit on this axis - // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one side may be auto-fit when calculating scrollbars. // FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both cases. if (border_n == 1 || border_n == 3) // Right and bottom border { + ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal, 1 << axis); size_target[axis] = CalcWindowSizeAfterConstraint(window, size_auto_fit)[axis]; ret_auto_fit_mask |= (1 << axis); hovered = held = false; // So border doesn't show highlighted at new position @@ -6508,7 +6912,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si const ImVec2 border_curr = (window->Pos + ImMin(def.SegmentN1, def.SegmentN2) * window->Size); const float border_target_rel_mode_for_axis = border_curr[axis] + g.IO.MouseDelta[axis]; - const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING; // Match ButtonBehavior() padding above. + const float border_target_abs_mode_for_axis = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + g.WindowsBorderHoverPadding; // Match ButtonBehavior() padding above. // Use absolute mode position ImVec2 border_target = window->Pos; @@ -6528,17 +6932,6 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si ImVec2 clamp_min(border_n == ImGuiDir_Right ? clamp_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down || (border_n == ImGuiDir_Up && window_move_from_title_bar) ? clamp_rect.Min.y : -FLT_MAX); ImVec2 clamp_max(border_n == ImGuiDir_Left ? clamp_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? clamp_rect.Max.y : +FLT_MAX); border_target = ImClamp(border_target, clamp_min, clamp_max); - if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent - { - ImGuiWindow* parent_window = window->ParentWindow; - ImGuiWindowFlags parent_flags = parent_window->Flags; - ImRect border_limit_rect = parent_window->InnerRect; - border_limit_rect.Expand(ImVec2(-ImMax(parent_window->WindowPadding.x, parent_window->WindowBorderSize), -ImMax(parent_window->WindowPadding.y, parent_window->WindowBorderSize))); - if ((axis == ImGuiAxis_X) && ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar))) - border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x); - if ((axis == ImGuiAxis_Y) && (parent_flags & ImGuiWindowFlags_NoScrollbar)) - border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y); - } if (!ignore_resize) CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); } @@ -6569,7 +6962,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step; g.NavWindowingAccumDeltaSize = ImMax(g.NavWindowingAccumDeltaSize, clamp_rect.Min - window->Pos - window->Size); // We need Pos+Size >= clmap_rect.Min, so Size >= clmap_rect.Min - Pos, so size_delta >= clmap_rect.Min - window->Pos - window->Size g.NavWindowingToggleLayer = false; - g.NavDisableMouseHover = true; + g.NavHighlightItemUnderNav = true; resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive); ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaSize); if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) @@ -6582,8 +6975,8 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si } // Apply back modified position/size to window - const ImVec2 curr_pos = window->Pos; - const ImVec2 curr_size = window->SizeFull; + const ImVec2 old_pos = window->Pos; + const ImVec2 old_size = window->SizeFull; if (size_target.x != FLT_MAX && (window->Size.x != size_target.x || window->SizeFull.x != size_target.x)) window->Size.x = window->SizeFull.x = size_target.x; if (size_target.y != FLT_MAX && (window->Size.y != size_target.y || window->SizeFull.y != size_target.y)) @@ -6592,21 +6985,20 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si window->Pos.x = ImTrunc(pos_target.x); if (pos_target.y != FLT_MAX && window->Pos.y != ImTrunc(pos_target.y)) window->Pos.y = ImTrunc(pos_target.y); - if (curr_pos.x != window->Pos.x || curr_pos.y != window->Pos.y || curr_size.x != window->SizeFull.x || curr_size.y != window->SizeFull.y) + if (old_pos.x != window->Pos.x || old_pos.y != window->Pos.y || old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y) MarkIniSettingsDirty(window); // Recalculate next expected border expected coordinates if (*border_held != -1) - g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, WINDOWS_HOVER_PADDING); + g.WindowResizeBorderExpectedRect = GetResizeBorderRect(window, *border_held, grip_hover_inner_size, g.WindowsBorderHoverPadding); return ret_auto_fit_mask; } static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect) { - ImGuiContext& g = *GImGui; ImVec2 size_for_clamping = window->Size; - if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) + if (!(window->BgClickFlags & ImGuiWindowBgClickFlags_Move) && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) size_for_clamping.y = window->TitleBarHeight; window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max); } @@ -6644,7 +7036,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { float y = window->Pos.y + window->TitleBarHeight - 1; - window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), border_col, g.Style.FrameBorderSize); + window->DrawList->AddLine(ImVec2(window->Pos.x + border_size * 0.5f, y), ImVec2(window->Pos.x + window->Size.x - border_size * 0.5f, y), border_col, g.Style.FrameBorderSize); } } @@ -6656,9 +7048,10 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImGuiStyle& style = g.Style; ImGuiWindowFlags flags = window->Flags; - // Ensure that ScrollBar doesn't read last frame's SkipItems + // Ensure that Scrollbar() doesn't read last frame's SkipItems IM_ASSERT(window->BeginCount == 0); window->SkipItems = false; + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; // Draw window + handle manual resize // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame. @@ -6669,7 +7062,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar // Title bar only const float backup_border_size = style.FrameBorderSize; g.Style.FrameBorderSize = window->WindowBorderSize; - ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); + ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && g.NavCursorVisible) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed); RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); g.Style.FrameBorderSize = backup_border_size; } @@ -6681,7 +7074,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window)); bool override_alpha = false; float alpha = 1.0f; - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasBgAlpha) { alpha = g.NextWindowData.BgAlphaVal; override_alpha = true; @@ -6703,9 +7096,9 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLine(menu_bar_rect.GetBL() + ImVec2(window_border_size * 0.5f, 0.0f), menu_bar_rect.GetBR() - ImVec2(window_border_size * 0.5f, 0.0f), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } // Scrollbars @@ -6724,9 +7117,10 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar continue; const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size))); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size))); - window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); + const float border_inner = IM_ROUND(window_border_size * 0.5f); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(border_inner, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, border_inner))); + window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, border_inner) : ImVec2(border_inner, resize_grip_draw_size))); + window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + border_inner), corner.y + grip.InnerDir.y * (window_rounding + border_inner)), window_rounding, grip.AngleMin12, grip.AngleMax12); window->DrawList->PathFillConvex(col); } } @@ -6735,6 +7129,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (handle_borders_and_resize_grips) RenderWindowOuterBorders(window); } + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; } // Render title text, collapse button, close button @@ -6783,8 +7178,13 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl // Close button if (has_close_button) + { + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_NoFocus; if (CloseButton(window->GetID("#CLOSE"), close_button_pos)) *p_open = false; + g.CurrentItemFlags = backup_item_flags; + } window->DC.NavLayerCurrent = ImGuiNavLayer_Main; g.CurrentItemFlags = item_flags_backup; @@ -6817,7 +7217,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f; if (marker_pos.x > layout_r.Min.x) { - RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text)); + RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_UnsavedMarker)); clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f)); } } @@ -6834,7 +7234,7 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags window->RootWindow = parent_window->RootWindow; if (parent_window && (flags & ImGuiWindowFlags_Popup)) window->RootWindowPopupTree = parent_window->RootWindowPopupTree; - if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) + if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip))) window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; while (window->RootWindowForNav->ChildFlags & ImGuiChildFlags_NavFlattened) { @@ -6849,7 +7249,7 @@ void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) { ImGuiContext& g = *GImGui; window->SkipRefresh = false; - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0) return; if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh) { @@ -6880,42 +7280,6 @@ static void SetWindowActiveForSkipRefresh(ImGuiWindow* window) } } -// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) -// should be positioned behind that modal window, unless the window was created inside the modal begin-stack. -// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. -// - WindowA // FindBlockingModal() returns Modal1 -// - WindowB // .. returns Modal1 -// - Modal1 // .. returns Modal2 -// - WindowC // .. returns Modal2 -// - WindowD // .. returns Modal2 -// - Modal2 // .. returns Modal2 -// - WindowE // .. returns NULL -// Notes: -// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. -// Only difference is here we check for ->Active/WasActive but it may be unnecessary. -ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.Size <= 0) - return NULL; - - // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal. - for (ImGuiPopupData& popup_data : g.OpenPopupStack) - { - ImGuiWindow* popup_window = popup_data.Window; - if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal)) - continue; - if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows. - continue; - if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. - return popup_window; - if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal - continue; - return popup_window; // Place window right below first block modal - } - return NULL; -} - // Push a new Dear ImGui window to add widgets to. // - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets without explicitly calling a Begin/End pair. // - Begin/End can be called multiple times during the frame with the same window name to append content. @@ -6966,7 +7330,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { UpdateWindowInFocusOrderList(window, window_just_created, flags); window->Flags = (ImGuiWindowFlags)flags; - window->ChildFlags = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0; + window->ChildFlags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0; window->LastFrameActive = current_frame; window->LastTimeActive = (float)g.Time; window->BeginOrderWithinParent = 0; @@ -6979,7 +7343,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window; - ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow; + ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) ? parent_window_in_stack : NULL) : window->ParentWindow; IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow)); // We allow window memory to be compacted so recreate the base stack when needed. @@ -6993,6 +7357,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.Window = window; window_stack_data.ParentLastItemDataBackup = g.LastItemData; window_stack_data.DisabledOverrideReenable = (flags & ImGuiWindowFlags_Tooltip) && (g.CurrentItemFlags & ImGuiItemFlags_Disabled); + window_stack_data.DisabledOverrideReenableAlphaBackup = 0.0f; ErrorRecoveryStoreState(&window_stack_data.StackSizesInBegin); g.StackSizesInBeginForCurrentWindow = &window_stack_data.StackSizesInBegin; if (flags & ImGuiWindowFlags_ChildMenu) @@ -7007,6 +7372,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack, // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL; + + // Inherent SetWindowFontScale() from parent until we fix this system... + window->FontWindowScaleParents = parent_window ? parent_window->FontWindowScaleParents * parent_window->FontWindowScale : 1.0f; } // Add to focus scope stack @@ -7024,10 +7392,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } // Process SetNextWindow***() calls - // (FIXME: Consider splitting the HasXXX flags into X/Y components + // (FIXME: Consider splitting the HasXXX flags into X/Y components) bool window_pos_set_by_api = false; bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) { window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) @@ -7043,7 +7411,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); } } - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) { window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); @@ -7053,7 +7421,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) g.NextWindowData.SizeVal.y = window->SizeFull.y; SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); } - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll) { if (g.NextWindowData.ScrollVal.x >= 0.0f) { @@ -7066,13 +7434,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ScrollTargetCenterRatio.y = 0.0f; } } - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasContentSize) window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal; else if (first_begin_of_the_frame) window->ContentSizeExplicit = ImVec2(0.0f, 0.0f); - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasCollapsed) SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasFocus) FocusWindow(window); if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); @@ -7107,7 +7475,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged). // The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere. bool window_title_visible_elsewhere = false; - if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB + if (g.NavWindowingListWindow != NULL && (flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using Ctrl+Tab + window_title_visible_elsewhere = true; + if (flags & ImGuiWindowFlags_ChildMenu) window_title_visible_elsewhere = true; if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0) { @@ -7168,6 +7538,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; window->TitleBarHeight = (flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : g.FontSize + g.Style.FramePadding.y * 2.0f; window->MenuBarHeight = (flags & ImGuiWindowFlags_MenuBar) ? window->DC.MenuBarOffset.y + g.FontSize + g.Style.FramePadding.y * 2.0f : 0.0f; + window->FontRefSize = g.FontSize; // Lock this to discourage calling window->CalcFontSize() outside of current window. // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. // Those flags will be altered further down in the function depending on more conditions. @@ -7214,36 +7585,39 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->ScrollbarSizes = ImVec2(0.0f, 0.0f); // Calculate auto-fit size, handle automatic resize - const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal); - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) - { - // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. - if (!window_size_x_set_by_api) - { - window->SizeFull.x = size_auto_fit.x; - use_current_size_for_scrollbar_x = true; - } - if (!window_size_y_set_by_api) - { - window->SizeFull.y = size_auto_fit.y; - use_current_size_for_scrollbar_y = true; - } - } - else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) + // - Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. + // - We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. + // - Auto-fit may only grow window during the first few frames. { - // Auto-fit may only grow window during the first few frames - // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. - if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) + const bool size_auto_fit_x_always = !window_size_x_set_by_api && (flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed; + const bool size_auto_fit_y_always = !window_size_y_set_by_api && (flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed; + const bool size_auto_fit_x_current = !window_size_x_set_by_api && (window->AutoFitFramesX > 0); + const bool size_auto_fit_y_current = !window_size_y_set_by_api && (window->AutoFitFramesY > 0); + int size_auto_fit_mask = 0; + if (size_auto_fit_x_always || size_auto_fit_x_current) + size_auto_fit_mask |= (1 << ImGuiAxis_X); + if (size_auto_fit_y_always || size_auto_fit_y_current) + size_auto_fit_mask |= (1 << ImGuiAxis_Y); + const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal, size_auto_fit_mask); + + const ImVec2 old_size = window->SizeFull; + if (size_auto_fit_x_always || size_auto_fit_x_current) { - window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; + if (size_auto_fit_x_always) + window->SizeFull.x = size_auto_fit.x; + else + window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; use_current_size_for_scrollbar_x = true; } - if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) + if (size_auto_fit_y_always || size_auto_fit_y_current) { - window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; + if (size_auto_fit_y_always) + window->SizeFull.y = size_auto_fit.y; + else + window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; use_current_size_for_scrollbar_y = true; } - if (!window->Collapsed) + if (old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y) MarkIniSettingsDirty(window); } @@ -7319,19 +7693,35 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { IM_ASSERT(window->IDStack.Size == 1); window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself. + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; IMGUI_TEST_ENGINE_ITEM_ADD(window->ID, window->Rect(), NULL); IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0); window->IDStack.Size = 1; + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + } #endif + // Decide if we are going to handle borders and resize grips + // 'window->SkipItems' is not updated yet so for child windows we rely on ParentWindow to avoid submitting decorations. (#8815) + // Whenever we add support for full decorated child windows we will likely make this logic more general. + bool handle_borders_and_resize_grips = true; + if ((flags & ImGuiWindowFlags_ChildWindow) && window->ParentWindow->SkipItems) + handle_borders_and_resize_grips = false; + // Handle manual resize: Resize Grips, Borders, Gamepad + // Child windows can only be resized when they have the flags set. The resize grip allows resizing in both directions, so it should appear only if both flags are set. int border_hovered = -1, border_held = -1; ImU32 resize_grip_col[4] = {}; - const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. + int resize_grip_count; + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) + resize_grip_count = ((window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY)) ? 1 : 0; + else + resize_grip_count = g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. + const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - if (!window->Collapsed) - if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) + if (handle_borders_and_resize_grips && !window->Collapsed) + if (int auto_fit_mask = UpdateWindowManualResize(window, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) { if (auto_fit_mask & (1 << ImGuiAxis_X)) use_current_size_for_scrollbar_x = true; @@ -7354,9 +7744,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f; float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x; float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y; + bool scrollbar_x_prev = window->ScrollbarX; //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons? window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((needed_size_from_last_frame.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); + + // Track when ScrollbarX visibility keeps toggling, which is a sign of a feedback loop, and stabilize by enforcing visibility (#3285, #8488) + // (Feedback loops of this sort can manifest in various situations, but combining horizontal + vertical scrollbar + using a clipper with varying width items is one frequent cause. + // The better solution is to, either (1) enforce visibility by using ImGuiWindowFlags_AlwaysHorizontalScrollbar or (2) declare stable contents width with SetNextWindowContentSize(), if you can compute it) + window->ScrollbarXStabilizeToggledHistory <<= 1; + window->ScrollbarXStabilizeToggledHistory |= (scrollbar_x_prev != window->ScrollbarX) ? 0x01 : 0x00; + const bool scrollbar_x_stabilize = (window->ScrollbarXStabilizeToggledHistory != 0) && ImCountSetBits(window->ScrollbarXStabilizeToggledHistory) >= 4; // 4 == half of bits in our U8 history. + if (scrollbar_x_stabilize) + window->ScrollbarX = true; + //if (scrollbar_x_stabilize && !window->ScrollbarXStabilizeEnabled) + // IMGUI_DEBUG_LOG("[scroll] Stabilize ScrollbarX for Window '%s'\n", window->Name); + window->ScrollbarXStabilizeEnabled = scrollbar_x_stabilize; + if (window->ScrollbarX && !window->ScrollbarY) window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); @@ -7412,12 +7816,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerClipRect.Max.y = ImFloor(window->InnerRect.Max.y - window->WindowBorderSize * 0.5f); window->InnerClipRect.ClipWithFull(host_rect); - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); - else - window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); - // SCROLLING // Lock down maximum scrolling @@ -7435,7 +7833,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Setup draw list and outer clipping rectangle IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); + window->DrawList->PushTexture(g.Font->OwnerAtlas->TexRef); PushClipRect(host_rect.Min, host_rect.Max, false); // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71) @@ -7459,7 +7857,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Handle title bar, scrollbar, resize grips and resize borders const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow; const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight); - const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch. RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size); if (render_decorations_in_parent) @@ -7523,13 +7920,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarAppending = false; window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user); window->DC.TreeDepth = 0; - window->DC.TreeHasStackDataDepthMask = 0x00; + window->DC.TreeHasStackDataDepthMask = window->DC.TreeRecordsClippedNodesY2Mask = 0x00; window->DC.ChildWindows.resize(0); window->DC.StateStorage = &window->StateStorage; window->DC.CurrentColumns = NULL; window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; + // Default item width. Make it proportional to window size if window manually resizes + if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) + window->ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); + else + window->ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); window->DC.ItemWidth = window->ItemWidthDefault; window->DC.TextWrapPos = -1.0f; // disabled window->DC.ItemWidthStack.resize(0); @@ -7551,6 +7953,13 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (want_focus && window == g.NavWindow) NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls + // Pressing Ctrl+C copy window content into the clipboard + // [EXPERIMENTAL] Breaks on nested Begin/End pairs. We need to work that out and add better logging scope. + // [EXPERIMENTAL] Text outputs has many issues. + if (g.IO.ConfigWindowsCopyContentsWithCtrlC) + if (g.NavWindow && g.NavWindow->RootWindow == window && g.ActiveId == 0 && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C)) + LogToClipboard(0); + // Title bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open); @@ -7561,18 +7970,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (flags & ImGuiWindowFlags_Tooltip) g.TooltipPreviousWindow = window; - // Pressing CTRL+C while holding on a window copy its content to the clipboard - // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. - // Maybe we can support CTRL+C on every element? - /* - //if (g.NavWindow == window && g.ActiveId == 0) - if (g.ActiveId == window->MoveId) - if (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_C)) - LogToClipboard(); - */ + // Set default BgClickFlags + // This is set at the end of this function, so UpdateWindowManualResize()/ClampWindowPos() may use last-frame value if overriden by user code. + // FIXME: The general intent is that we will later expose config options to default to enable scrolling + select scrolling mouse button. + window->BgClickFlags = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->BgClickFlags : (g.IO.ConfigWindowsMoveFromTitleBarOnly ? ImGuiWindowBgClickFlags_None : ImGuiWindowBgClickFlags_Move); // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. + window->DC.WindowItemStatusFlags = ImGuiItemStatusFlags_None; + window->DC.WindowItemStatusFlags |= IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; SetLastItemDataForWindow(window, title_bar_rect); // [DEBUG] @@ -7584,7 +7990,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // [Test Engine] Register title bar / tab with MoveId. #ifdef IMGUI_ENABLE_TEST_ENGINE if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) + { + window->DC.NavLayerCurrent = ImGuiNavLayer_Menu; IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.ID, g.LastItemData.Rect, &g.LastItemData); + window->DC.NavLayerCurrent = ImGuiNavLayer_Main; + } #endif } else @@ -7627,7 +8037,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Hide along with parent or if parent is collapsed if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0)) window->HiddenFramesCanSkipItems = 1; - if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0)) + if (parent_window && parent_window->HiddenFramesCannotSkipItems > 0) window->HiddenFramesCannotSkipItems = 1; } @@ -7674,12 +8084,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) return !window->SkipItems; } -static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) -{ - ImGuiContext& g = *GImGui; - SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); -} - void ImGui::End() { ImGuiContext& g = *GImGui; @@ -7695,7 +8099,7 @@ void ImGui::End() // Error checking: verify that user doesn't directly call End() on a child window. if (window->Flags & ImGuiWindowFlags_ChildWindow) - IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!"); + IM_ASSERT_USER_ERROR(g.WithinEndChildID == window->ID, "Must call EndChild() and not End()!"); // Close anything that is open if (window->DC.CurrentColumns) @@ -7713,7 +8117,7 @@ void ImGui::End() } // Stop logging - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging + if (g.LogWindow == window) // FIXME: add more options for scope of logging LogFinish(); if (window->DC.IsSetPos) @@ -7734,326 +8138,107 @@ void ImGui::End() SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window); } -void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) { ImGuiContext& g = *GImGui; - IM_ASSERT(window == window->RootWindow); + ImGuiItemFlags item_flags = g.CurrentItemFlags; + IM_ASSERT(item_flags == g.ItemFlagsStack.back()); + if (enabled) + item_flags |= option; + else + item_flags &= ~option; + g.CurrentItemFlags = item_flags; + g.ItemFlagsStack.push_back(item_flags); +} - const int cur_order = window->FocusOrder; - IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); - if (g.WindowsFocusOrder.back() == window) +void ImGui::PopItemFlag() +{ + ImGuiContext& g = *GImGui; + if (g.ItemFlagsStack.Size <= 1) + { + IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!"); return; + } + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); +} - const int new_order = g.WindowsFocusOrder.Size - 1; - for (int n = cur_order; n < new_order; n++) +// BeginDisabled()/EndDisabled() +// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) +// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. +// - Feedback welcome at https://github.com/ocornut/imgui/issues/211 +// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions. +// (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) +// - Note: mixing up BeginDisabled() and PushItemFlag(ImGuiItemFlags_Disabled) is currently NOT SUPPORTED. +void ImGui::BeginDisabled(bool disabled) +{ + ImGuiContext& g = *GImGui; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (!was_disabled && disabled) { - g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; - g.WindowsFocusOrder[n]->FocusOrder--; - IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + g.DisabledAlphaBackup = g.Style.Alpha; + g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha); } - g.WindowsFocusOrder[new_order] = window; - window->FocusOrder = (short)new_order; + if (was_disabled || disabled) + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize? + g.DisabledStackSize++; } -void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) +void ImGui::EndDisabled() { ImGuiContext& g = *GImGui; - ImGuiWindow* current_front_window = g.Windows.back(); - if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + if (g.DisabledStackSize <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!"); return; - for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window - if (g.Windows[i] == window) - { - memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); - g.Windows[g.Windows.Size - 1] = window; - break; - } + } + g.DisabledStackSize--; + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + //PopItemFlag(); + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); + if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) + g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar(); } -void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) +// Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function name. +// Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal. +// The whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled(). +void ImGui::BeginDisabledOverrideReenable() { ImGuiContext& g = *GImGui; - if (g.Windows[0] == window) - return; - for (int i = 0; i < g.Windows.Size; i++) - if (g.Windows[i] == window) - { - memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); - g.Windows[0] = window; - break; - } + IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled); + g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup = g.Style.Alpha; + g.Style.Alpha = g.DisabledAlphaBackup; + g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; + g.ItemFlagsStack.push_back(g.CurrentItemFlags); + g.DisabledStackSize++; } -void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window) +void ImGui::EndDisabledOverrideReenable() { - IM_ASSERT(window != NULL && behind_window != NULL); ImGuiContext& g = *GImGui; - window = window->RootWindow; - behind_window = behind_window->RootWindow; - int pos_wnd = FindWindowDisplayIndex(window); - int pos_beh = FindWindowDisplayIndex(behind_window); - if (pos_wnd < pos_beh) - { - size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*); - memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes); - g.Windows[pos_beh - 1] = window; - } - else - { - size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*); - memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes); - g.Windows[pos_beh] = window; - } + g.DisabledStackSize--; + IM_ASSERT(g.DisabledStackSize > 0); + g.ItemFlagsStack.pop_back(); + g.CurrentItemFlags = g.ItemFlagsStack.back(); + g.Style.Alpha = g.CurrentWindowStack.back().DisabledOverrideReenableAlphaBackup; } -int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) +void ImGui::PushTextWrapPos(float wrap_pos_x) { ImGuiContext& g = *GImGui; - return g.Windows.index_from_ptr(g.Windows.find(window)); + ImGuiWindow* window = g.CurrentWindow; + window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); + window->DC.TextWrapPos = wrap_pos_x; } -// Moving window to front of display and set focus (which happens to be back of our sorted list) -void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) +void ImGui::PopTextWrapPos() { ImGuiContext& g = *GImGui; - - // Modal check? - if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. - if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) - { - // This block would typically be reached in two situations: - // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag. - // - User clicking on void or anything behind a modal while a modal is open (window == NULL) - IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); - if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) - BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?) - ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals - return; - } - - // Find last focused child (if any) and focus it instead. - if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL) - window = NavRestoreLastChildNavWindow(window); - - // Apply focus - if (g.NavWindow != window) - { - SetNavWindow(window); - if (window && g.NavDisableMouseHover) - g.NavMousePosDirty = true; - g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId - g.NavLayer = ImGuiNavLayer_Main; - SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); - g.NavIdIsAlive = false; - g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; - - // Close popups if any - ClosePopupsOverWindow(window, false); - } - - // Move the root window to the top of the pile - IM_ASSERT(window == NULL || window->RootWindow != NULL); - ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop - ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; - - // Steal active widgets. Some of the cases it triggers includes: - // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. - // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) - if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) - if (!g.ActiveIdNoClearOnFocusLoss) - ClearActiveID(); - - // Passing NULL allow to disable keyboard focus - if (!window) - return; - - // Bring to front - BringWindowToFocusFront(focus_front_window); - if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) - BringWindowToDisplayFront(display_front_window); -} - -void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) -{ - ImGuiContext& g = *GImGui; - IM_UNUSED(filter_viewport); // Unused in master branch. - int start_idx = g.WindowsFocusOrder.Size - 1; - if (under_this_window != NULL) - { - // Aim at root window behind us, if we are in a child window that's our own root (see #4640) - int offset = -1; - while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow) - { - under_this_window = under_this_window->ParentWindow; - offset = 0; - } - start_idx = FindWindowFocusIndex(under_this_window) + offset; - } - for (int i = start_idx; i >= 0; i--) - { - // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. - ImGuiWindow* window = g.WindowsFocusOrder[i]; - if (window == ignore_window || !window->WasActive) - continue; - if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) - { - FocusWindow(window, flags); - return; - } - } - FocusWindow(NULL, flags); -} - -// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only. -void ImGui::SetCurrentFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? - IM_ASSERT(font->Scale > 0.0f); - g.Font = font; - g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale); - g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f; - g.FontScale = g.FontSize / g.Font->FontSize; - - ImFontAtlas* atlas = g.Font->ContainerAtlas; - g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvLines = atlas->TexUvLines; - g.DrawListSharedData.Font = g.Font; - g.DrawListSharedData.FontSize = g.FontSize; - g.DrawListSharedData.FontScale = g.FontScale; -} - -// Use ImDrawList::_SetTextureID(), making our shared g.FontStack[] authorative against window-local ImDrawList. -// - Whereas ImDrawList::PushTextureID()/PopTextureID() is not to be used across Begin() calls. -// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... -// - Some code paths never really fully worked with multiple atlas textures. -// - The right-ish solution may be to remove _SetTextureID() and make AddText/RenderText lazily call PushTextureID()/PopTextureID() -// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem -// because we have a concrete need and a test bed for multiple atlas textures. -void ImGui::PushFont(ImFont* font) -{ - ImGuiContext& g = *GImGui; - if (font == NULL) - font = GetDefaultFont(); - g.FontStack.push_back(font); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} - -void ImGui::PopFont() -{ - ImGuiContext& g = *GImGui; - if (g.FontStack.Size <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); - return; - } - g.FontStack.pop_back(); - ImFont* font = g.FontStack.Size == 0 ? GetDefaultFont() : g.FontStack.back(); - SetCurrentFont(font); - g.CurrentWindow->DrawList->_SetTextureID(font->ContainerAtlas->TexID); -} - -void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled) -{ - ImGuiContext& g = *GImGui; - ImGuiItemFlags item_flags = g.CurrentItemFlags; - IM_ASSERT(item_flags == g.ItemFlagsStack.back()); - if (enabled) - item_flags |= option; - else - item_flags &= ~option; - g.CurrentItemFlags = item_flags; - g.ItemFlagsStack.push_back(item_flags); -} - -void ImGui::PopItemFlag() -{ - ImGuiContext& g = *GImGui; - if (g.ItemFlagsStack.Size <= 1) - { - IM_ASSERT_USER_ERROR(0, "Calling PopItemFlag() too many times!"); - return; - } - g.ItemFlagsStack.pop_back(); - g.CurrentItemFlags = g.ItemFlagsStack.back(); -} - -// BeginDisabled()/EndDisabled() -// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) -// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. -// - Feedback welcome at https://github.com/ocornut/imgui/issues/211 -// - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions. -// (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) -// - Note: mixing up BeginDisabled() and PushItemFlag(ImGuiItemFlags_Disabled) is currently NOT SUPPORTED. -void ImGui::BeginDisabled(bool disabled) -{ - ImGuiContext& g = *GImGui; - bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; - if (!was_disabled && disabled) - { - g.DisabledAlphaBackup = g.Style.Alpha; - g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha); - } - if (was_disabled || disabled) - g.CurrentItemFlags |= ImGuiItemFlags_Disabled; - g.ItemFlagsStack.push_back(g.CurrentItemFlags); // FIXME-OPT: can we simply skip this and use DisabledStackSize? - g.DisabledStackSize++; -} - -void ImGui::EndDisabled() -{ - ImGuiContext& g = *GImGui; - if (g.DisabledStackSize <= 0) - { - IM_ASSERT_USER_ERROR(0, "Calling EndDisabled() too many times!"); - return; - } - g.DisabledStackSize--; - bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; - //PopItemFlag(); - g.ItemFlagsStack.pop_back(); - g.CurrentItemFlags = g.ItemFlagsStack.back(); - if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0) - g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar(); -} - -// Could have been called BeginDisabledDisable() but it didn't want to be award nominated for most awkward function name. -// Ideally we would use a shared e.g. BeginDisabled()->BeginDisabledEx() but earlier needs to be optimal. -// The whole code for this is awkward, will reevaluate if we find a way to implement SetNextItemDisabled(). -void ImGui::BeginDisabledOverrideReenable() -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.CurrentItemFlags & ImGuiItemFlags_Disabled); - g.Style.Alpha = g.DisabledAlphaBackup; - g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; - g.ItemFlagsStack.push_back(g.CurrentItemFlags); - g.DisabledStackSize++; -} - -void ImGui::EndDisabledOverrideReenable() -{ - ImGuiContext& g = *GImGui; - g.DisabledStackSize--; - IM_ASSERT(g.DisabledStackSize > 0); - g.ItemFlagsStack.pop_back(); - g.CurrentItemFlags = g.ItemFlagsStack.back(); - g.Style.Alpha = g.DisabledAlphaBackup * g.Style.DisabledAlpha; -} - -void ImGui::PushTextWrapPos(float wrap_pos_x) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos); - window->DC.TextWrapPos = wrap_pos_x; -} - -void ImGui::PopTextWrapPos() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (window->DC.TextWrapPosStack.Size <= 0) + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.TextWrapPosStack.Size <= 0) { IM_ASSERT_USER_ERROR(0, "Calling PopTextWrapPos() too many times!"); return; @@ -8091,6 +8276,15 @@ bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, return false; } +bool ImGui::IsWindowInBeginStack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + for (int n = g.CurrentWindowStack.Size - 1; n >= 0; n--) + if (g.CurrentWindowStack[n].Window == window) + return true; + return false; +} + bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent) { if (window->RootWindow == potential_parent) @@ -8130,9 +8324,9 @@ bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_b // Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details. bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { - IM_ASSERT((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0 && "Invalid flags for IsWindowHovered()!"); - ImGuiContext& g = *GImGui; + IM_ASSERT_USER_ERROR((flags & ~ImGuiHoveredFlags_AllowedMaskForIsWindowHovered) == 0, "Invalid flags for IsWindowHovered()!"); + ImGuiWindow* ref_window = g.HoveredWindow; ImGuiWindow* cur_window = g.CurrentWindow; if (ref_window == NULL) @@ -8173,36 +8367,6 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) return true; } -bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* ref_window = g.NavWindow; - ImGuiWindow* cur_window = g.CurrentWindow; - - if (ref_window == NULL) - return false; - if (flags & ImGuiFocusedFlags_AnyWindow) - return true; - - IM_ASSERT(cur_window); // Not inside a Begin()/End() - const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; - if (flags & ImGuiHoveredFlags_RootWindow) - cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); - - if (flags & ImGuiHoveredFlags_ChildWindows) - return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); - else - return (ref_window == cur_window); -} - -// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) -// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. -// If you want a window to never be focused, you may use the e.g. NoInputs flag. -bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) -{ - return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); -} - float ImGui::GetWindowWidth() { ImGuiWindow* window = GImGui->CurrentWindow; @@ -8310,8 +8474,10 @@ void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond co return; window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - // Set - window->Collapsed = collapsed; + // Queue applying in Begin() + if (window->WantCollapseToggle) + window->Collapsed ^= 1; + window->WantCollapseToggle = (window->Collapsed != collapsed); } void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size) @@ -8350,29 +8516,11 @@ void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond) SetWindowCollapsed(window, collapsed, cond); } -void ImGui::SetWindowFocus() -{ - FocusWindow(GImGui->CurrentWindow); -} - -void ImGui::SetWindowFocus(const char* name) -{ - if (name) - { - if (ImGuiWindow* window = FindWindowByName(name)) - FocusWindow(window); - } - else - { - FocusWindow(NULL); - } -} - void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot) { ImGuiContext& g = *GImGui; IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasPos; g.NextWindowData.PosVal = pos; g.NextWindowData.PosPivotVal = pivot; g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always; @@ -8382,7 +8530,7 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) { ImGuiContext& g = *GImGui; IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSize; g.NextWindowData.SizeVal = size; g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always; } @@ -8394,25 +8542,25 @@ void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data) { ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasSizeConstraint; g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max); g.NextWindowData.SizeCallback = custom_callback; g.NextWindowData.SizeCallbackUserData = custom_callback_user_data; } // Content size = inner scrollable rectangle, padded with WindowPadding. -// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item. +// SetNextWindowContentSize(ImVec2(100,100)) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item. void ImGui::SetNextWindowContentSize(const ImVec2& size) { ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasContentSize; g.NextWindowData.ContentSizeVal = ImTrunc(size); } void ImGui::SetNextWindowScroll(const ImVec2& scroll) { ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasScroll; g.NextWindowData.ScrollVal = scroll; } @@ -8420,21 +8568,15 @@ void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond) { ImGuiContext& g = *GImGui; IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags. - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasCollapsed; g.NextWindowData.CollapsedVal = collapsed; g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always; } -void ImGui::SetNextWindowFocus() -{ - ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus; -} - void ImGui::SetNextWindowBgAlpha(float alpha) { ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasBgAlpha; g.NextWindowData.BgAlphaVal = alpha; } @@ -8442,7 +8584,7 @@ void ImGui::SetNextWindowBgAlpha(float alpha) void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) { ImGuiContext& g = *GImGui; - g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasRefreshPolicy; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasRefreshPolicy; g.NextWindowData.RefreshFlagsVal = flags; } @@ -8457,6 +8599,14 @@ ImFont* ImGui::GetFont() return GImGui->Font; } +ImFontBaked* ImGui::GetFontBaked() +{ + return GImGui->FontBaked; +} + +// Get current font size (= height in pixels) of current font, with global scale factors applied. +// - Use style.FontSizeBase to get value before global scale factors. +// - recap: ImGui::GetFontSize() == style.FontSizeBase * (style.FontScaleMain * style.FontScaleDpi * other_scaling_factors) float ImGui::GetFontSize() { return GImGui->FontSize; @@ -8467,15 +8617,16 @@ ImVec2 ImGui::GetFontTexUvWhitePixel() return GImGui->DrawListSharedData.TexUvWhitePixel; } +// Prefer using PushFont(NULL, style.FontSizeBase * factor), or use style.FontScaleMain to scale all windows. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS void ImGui::SetWindowFontScale(float scale) { IM_ASSERT(scale > 0.0f); - ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); window->FontWindowScale = scale; - g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize(); - g.FontScale = g.DrawListSharedData.FontScale = g.FontSize / g.Font->FontSize; + UpdateCurrentFontSize(0.0f); } +#endif void ImGui::PushFocusScope(ImGuiID id) { @@ -8538,7 +8689,7 @@ void ImGui::FocusItem() return; } - ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavHighlight | ImGuiNavMoveFlags_NoSelect; + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible | ImGuiNavMoveFlags_NoSelect; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; SetNavWindow(window); NavMoveRequestSubmit(ImGuiDir_None, ImGuiDir_Up, move_flags, scroll_flags); @@ -8573,7 +8724,7 @@ void ImGui::SetKeyboardFocusHere(int offset) SetNavWindow(window); - ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavHighlight; + ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate | ImGuiNavMoveFlags_FocusApi | ImGuiNavMoveFlags_NoSetNavCursorVisible; ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; NavMoveRequestSubmit(ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. if (offset == -1) @@ -8630,19 +8781,285 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) } //----------------------------------------------------------------------------- -// [SECTION] ID STACK +// [SECTION] FONTS, TEXTURES +//----------------------------------------------------------------------------- +// Most of the relevant font logic is in imgui_draw.cpp. +// Those are high-level support functions. +//----------------------------------------------------------------------------- +// - UpdateTexturesNewFrame() [Internal] +// - UpdateTexturesEndFrame() [Internal] +// - UpdateFontsNewFrame() [Internal] +// - UpdateFontsEndFrame() [Internal] +// - GetDefaultFont() [Internal] +// - RegisterUserTexture() [Internal] +// - UnregisterUserTexture() [Internal] +// - RegisterFontAtlas() [Internal] +// - UnregisterFontAtlas() [Internal] +// - SetCurrentFont() [Internal] +// - UpdateCurrentFontSize() [Internal] +// - SetFontRasterizerDensity() [Internal] +// - PushFont() +// - PopFont() //----------------------------------------------------------------------------- -// This is one of the very rare legacy case where we use ImGuiWindow methods, -// it should ideally be flattened at some point but it's been used a lots by widgets. -IM_MSVC_RUNTIME_CHECKS_OFF -ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +static void ImGui::UpdateTexturesNewFrame() +{ + // Cannot update every atlases based on atlas's FrameCount < g.FrameCount, because an atlas may be shared by multiple contexts with different frame count. + ImGuiContext& g = *GImGui; + const bool has_textures = (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + for (ImFontAtlas* atlas : g.FontAtlases) + { + if (atlas->OwnerContext == &g) + { + ImFontAtlasUpdateNewFrame(atlas, g.FrameCount, has_textures); + } + else + { + // (1) If you manage font atlases yourself, e.g. create a ImFontAtlas yourself you need to call ImFontAtlasUpdateNewFrame() on it. + // Otherwise, calling ImGui::CreateContext() without parameter will create an atlas owned by the context. + // (2) If you have multiple font atlases, make sure the 'atlas->RendererHasTextures' as specified in the ImFontAtlasUpdateNewFrame() call matches for that. + // (3) If you have multiple imgui contexts, they also need to have a matching value for ImGuiBackendFlags_RendererHasTextures. + IM_ASSERT(atlas->Builder != NULL && atlas->Builder->FrameCount != -1); + IM_ASSERT(atlas->RendererHasTextures == has_textures); + } + } +} + +// Build a single texture list +static void ImGui::UpdateTexturesEndFrame() +{ + ImGuiContext& g = *GImGui; + g.PlatformIO.Textures.resize(0); + for (ImFontAtlas* atlas : g.FontAtlases) + for (ImTextureData* tex : atlas->TexList) + { + // We provide this information so backends can decide whether to destroy textures. + // This means in practice that if N imgui contexts are created with a shared atlas, we assume all of them have a backend initialized. + tex->RefCount = (unsigned short)atlas->RefCount; + g.PlatformIO.Textures.push_back(tex); + } + for (ImTextureData* tex : g.UserTextures) + g.PlatformIO.Textures.push_back(tex); +} + +void ImGui::UpdateFontsNewFrame() +{ + ImGuiContext& g = *GImGui; + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + for (ImFontAtlas* atlas : g.FontAtlases) + atlas->Locked = true; + + if (g.Style._NextFrameFontSizeBase != 0.0f) + { + g.Style.FontSizeBase = g.Style._NextFrameFontSizeBase; + g.Style._NextFrameFontSizeBase = 0.0f; + } + + // Apply default font size the first time + ImFont* font = ImGui::GetDefaultFont(); + if (g.Style.FontSizeBase <= 0.0f) + g.Style.FontSizeBase = (font->LegacySize > 0.0f ? font->LegacySize : FONT_DEFAULT_SIZE); + + // Set initial font + g.Font = font; + g.FontSizeBase = g.Style.FontSizeBase; + g.FontSize = 0.0f; + ImFontStackData font_stack_data = { font, g.Style.FontSizeBase, g.Style.FontSizeBase }; // <--- Will restore FontSize + SetCurrentFont(font_stack_data.Font, font_stack_data.FontSizeBeforeScaling, 0.0f); // <--- but use 0.0f to enable scale + g.FontStack.push_back(font_stack_data); + IM_ASSERT(g.Font->IsLoaded()); +} + +void ImGui::UpdateFontsEndFrame() +{ + PopFont(); +} + +ImFont* ImGui::GetDefaultFont() +{ + ImGuiContext& g = *GImGui; + ImFontAtlas* atlas = g.IO.Fonts; + if (atlas->Builder == NULL || atlas->Fonts.Size == 0) + ImFontAtlasBuildMain(atlas); + return g.IO.FontDefault ? g.IO.FontDefault : atlas->Fonts[0]; +} + +// EXPERIMENTAL: DO NOT USE YET. +void ImGui::RegisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + tex->RefCount++; + g.UserTextures.push_back(tex); +} + +void ImGui::UnregisterUserTexture(ImTextureData* tex) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(tex->RefCount > 0); + tex->RefCount--; + g.UserTextures.find_erase(tex); +} + +void ImGui::RegisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + if (g.FontAtlases.Size == 0) + IM_ASSERT(atlas == g.IO.Fonts); + atlas->RefCount++; + g.FontAtlases.push_back(atlas); + ImFontAtlasAddDrawListSharedData(atlas, &g.DrawListSharedData); + for (ImTextureData* tex : atlas->TexList) + tex->RefCount = (unsigned short)atlas->RefCount; +} + +void ImGui::UnregisterFontAtlas(ImFontAtlas* atlas) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(atlas->RefCount > 0); + ImFontAtlasRemoveDrawListSharedData(atlas, &g.DrawListSharedData); + g.FontAtlases.find_erase(atlas); + atlas->RefCount--; + for (ImTextureData* tex : atlas->TexList) + tex->RefCount = (unsigned short)atlas->RefCount; +} + +// Use ImDrawList::_SetTexture(), making our shared g.FontStack[] authoritative against window-local ImDrawList. +// - Whereas ImDrawList::PushTexture()/PopTexture() is not to be used across Begin() calls. +// - Note that we don't propagate current texture id when e.g. Begin()-ing into a new window, we never really did... +// - Some code paths never really fully worked with multiple atlas textures. +// - The right-ish solution may be to remove _SetTexture() and make AddText/RenderText lazily call PushTexture()/PopTexture() +// the same way AddImage() does, but then all other primitives would also need to? I don't think we should tackle this problem +// because we have a concrete need and a test bed for multiple atlas textures. +// FIXME-NEWATLAS-V2: perhaps we can now leverage ImFontAtlasUpdateDrawListsTextures() ? +void ImGui::SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + g.Font = font; + g.FontSizeBase = font_size_before_scaling; + UpdateCurrentFontSize(font_size_after_scaling); + + if (font != NULL) + { + IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ? +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IM_ASSERT(font->Scale > 0.0f); +#endif + ImFontAtlas* atlas = font->OwnerAtlas; + g.DrawListSharedData.FontAtlas = atlas; + g.DrawListSharedData.Font = font; + ImFontAtlasUpdateDrawListsSharedData(atlas); + if (g.CurrentWindow != NULL) + g.CurrentWindow->DrawList->_SetTexture(atlas->TexRef); + } +} + +void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + g.Style.FontSizeBase = g.FontSizeBase; + + // Early out to avoid hidden window keeping bakes referenced and out of GC reach. + // However this would leave a pretty subtle and damning error surface area if g.FontBaked was mismatching. + // FIXME: perhaps g.FontSize should be updated? + if (window != NULL && window->SkipItems) + { + ImGuiTable* table = g.CurrentTable; + if (table == NULL || (table->CurrentColumn != -1 && table->Columns[table->CurrentColumn].IsSkipItems == false)) // See 8465#issuecomment-2951509561 and #8865. Ideally the SkipItems=true in tables would be amended with extra data. + return; + } + + // Restoring is pretty much only used by PopFont() + float final_size = (restore_font_size_after_scaling > 0.0f) ? restore_font_size_after_scaling : 0.0f; + if (final_size == 0.0f) + { + final_size = g.FontSizeBase; + + // Global scale factors + final_size *= g.Style.FontScaleMain; // Main global scale factor + final_size *= g.Style.FontScaleDpi; // Per-monitor/viewport DPI scale factor, automatically updated when io.ConfigDpiScaleFonts is enabled. + + // Window scale (mostly obsolete now) + if (window != NULL) + final_size *= window->FontWindowScale; + + // Legacy scale factors +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + final_size *= g.IO.FontGlobalScale; // Use style.FontScaleMain instead! + if (g.Font != NULL) + final_size *= g.Font->Scale; // Was never really useful. +#endif + } + + // Round font size + // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. + // - We may support it better later and remove this rounding. + final_size = GetRoundedFontSize(final_size); + final_size = ImClamp(final_size, 1.0f, IMGUI_FONT_SIZE_MAX); + if (g.Font != NULL && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures)) + g.Font->CurrentRasterizerDensity = g.FontRasterizerDensity; + g.FontSize = final_size; + g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; + g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.DrawListSharedData.FontSize = g.FontSize; + g.DrawListSharedData.FontScale = g.FontBakedScale; +} + +// Exposed in case user may want to override setting density. +// IMPORTANT: Begin()/End() is overriding density. Be considerate of this you change it. +void ImGui::SetFontRasterizerDensity(float rasterizer_density) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures); + if (g.FontRasterizerDensity == rasterizer_density) + return; + g.FontRasterizerDensity = rasterizer_density; + UpdateCurrentFontSize(0.0f); +} + +// If you want to scale an existing font size! Read comments in imgui.h! +void ImGui::PushFont(ImFont* font, float font_size_base) +{ + ImGuiContext& g = *GImGui; + if (font == NULL) // Before 1.92 (June 2025), PushFont(NULL) == PushFont(GetDefaultFont()) + font = g.Font; + IM_ASSERT(font != NULL); + IM_ASSERT(font_size_base >= 0.0f); + + g.FontStack.push_back({ g.Font, g.FontSizeBase, g.FontSize }); + if (font_size_base == 0.0f) + font_size_base = g.FontSizeBase; // Keep current font size + SetCurrentFont(font, font_size_base, 0.0f); +} + +void ImGui::PopFont() +{ + ImGuiContext& g = *GImGui; + if (g.FontStack.Size <= 0) + { + IM_ASSERT_USER_ERROR(0, "Calling PopFont() too many times!"); + return; + } + ImFontStackData* font_stack_data = &g.FontStack.back(); + SetCurrentFont(font_stack_data->Font, font_stack_data->FontSizeBeforeScaling, font_stack_data->FontSizeAfterScaling); + g.FontStack.pop_back(); +} + +//----------------------------------------------------------------------------- +// [SECTION] ID STACK +//----------------------------------------------------------------------------- + +// This is one of the very rare legacy case where we use ImGuiWindow methods, +// it should ideally be flattened at some point but it's been used a lots by widgets. +IM_MSVC_RUNTIME_CHECKS_OFF +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) { ImGuiID seed = IDStack.back(); ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); #endif return id; @@ -8654,7 +9071,7 @@ ImGuiID ImGuiWindow::GetID(const void* ptr) ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); #endif return id; @@ -8666,7 +9083,7 @@ ImGuiID ImGuiWindow::GetID(int n) ImGuiID id = ImHashData(&n, sizeof(n), seed); #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); #endif return id; @@ -8729,7 +9146,7 @@ void ImGui::PushOverrideID(ImGuiID id) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; #ifndef IMGUI_DISABLE_DEBUG_TOOLS - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL); #endif window->IDStack.push_back(id); @@ -8743,7 +9160,7 @@ ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); #endif return id; @@ -8754,7 +9171,7 @@ ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) ImGuiID id = ImHashData(&n, sizeof(n), seed); #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - if (g.DebugHookIdInfo == id) + if (g.DebugHookIdInfoId == id) DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); #endif return id; @@ -8892,28 +9309,11 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END); - if (IsLegacyKey(key) && g.IO.KeyMap[key] != -1) - key = (ImGuiKey)g.IO.KeyMap[key]; // Remap native->imgui or imgui->native -#else IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code."); -#endif - return &g.IO.KeysData[key - ImGuiKey_KeysData_OFFSET]; -} - -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// Formally moved to obsolete section in 1.90.5 in spite of documented as obsolete since 1.87 -ImGuiKey ImGui::GetKeyIndex(ImGuiKey key) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(IsNamedKey(key)); - const ImGuiKeyData* key_data = GetKeyData(key); - return (ImGuiKey)(key_data - g.IO.KeysData); + return &g.IO.KeysData[key - ImGuiKey_NamedKey_BEGIN]; } -#endif -// Those names a provided for debugging purpose and are not meant to be saved persistently not compared. +// Those names are provided for debugging purpose and are not meant to be saved persistently nor compared. static const char* const GKeyNames[] = { "Tab", "LeftArrow", "RightArrow", "UpArrow", "DownArrow", "PageUp", "PageDown", @@ -8928,7 +9328,7 @@ static const char* const GKeyNames[] = "Pause", "Keypad0", "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "KeypadDecimal", "KeypadDivide", "KeypadMultiply", "KeypadSubtract", "KeypadAdd", "KeypadEnter", "KeypadEqual", - "AppBack", "AppForward", + "AppBack", "AppForward", "Oem102", "GamepadStart", "GamepadBack", "GamepadFaceLeft", "GamepadFaceRight", "GamepadFaceUp", "GamepadFaceDown", "GamepadDpadLeft", "GamepadDpadRight", "GamepadDpadUp", "GamepadDpadDown", @@ -8944,18 +9344,7 @@ const char* ImGui::GetKeyName(ImGuiKey key) { if (key == ImGuiKey_None) return "None"; -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO IM_ASSERT(IsNamedKeyOrMod(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code."); -#else - ImGuiContext& g = *GImGui; - if (IsLegacyKey(key)) - { - if (g.IO.KeyMap[key] == -1) - return "N/A"; - IM_ASSERT(IsNamedKey((ImGuiKey)g.IO.KeyMap[key])); - key = (ImGuiKey)g.IO.KeyMap[key]; - } -#endif if (key & ImGuiMod_Mask_) key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) @@ -8981,7 +9370,7 @@ const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : ""); size_t len; if (key == ImGuiKey_None && key_chord != 0) - if ((len = strlen(g.TempKeychordName)) != 0) // Remove trailing '+' + if ((len = ImStrlen(g.TempKeychordName)) != 0) // Remove trailing '+' g.TempKeychordName[len - 1] = 0; return g.TempKeychordName; } @@ -9053,7 +9442,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore; routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry routing_entry->RoutingNext = ImGuiKeyOwner_NoOwner; - routing_entry->RoutingNextScore = 255; + routing_entry->RoutingNextScore = 0; if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner) continue; rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer @@ -9122,23 +9511,24 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) return routing_data; } -// Current score encoding (lower is highest priority): -// - 0: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive -// - 1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active) -// - 2: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused -// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack) -// - 254: ImGuiInputFlags_RouteGlobal -// - 255: never route +// Current score encoding +// - 0: Never route +// - 1: ImGuiInputFlags_RouteGlobal (lower priority) +// - 100..199: ImGuiInputFlags_RouteFocused (if window in focus-stack) +// 200: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused +// 300: ImGuiInputFlags_RouteActive or ImGuiInputFlags_RouteFocused (if item active) +// 400: ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverActive +// - 500..599: ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteOverActive (if window in focus-stack) (higher priority) // 'flags' should include an explicit routing policy static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; if (flags & ImGuiInputFlags_RouteFocused) { - // ActiveID gets top priority + // ActiveID gets high priority // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) if (owner_id != 0 && g.ActiveId == owner_id) - return 1; + return 300; // Score based on distance to focused window (lower is better) // Assuming both windows are submitting a routing request, @@ -9148,25 +9538,32 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. // This essentially follow the window->ParentWindowForFocusRoute chain. if (focus_scope_id == 0) - return 255; + return 0; for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++) if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id) - return 3 + index_in_focus_path; - return 255; + { + if (flags & ImGuiInputFlags_RouteOverActive) // && g.ActiveId != 0 && g.ActiveId != owner_id) + return 599 - index_in_focus_path; + else + return 199 - index_in_focus_path; + } + return 0; } else if (flags & ImGuiInputFlags_RouteActive) { if (owner_id != 0 && g.ActiveId == owner_id) - return 1; - return 255; + return 300; + return 0; } else if (flags & ImGuiInputFlags_RouteGlobal) { if (flags & ImGuiInputFlags_RouteOverActive) - return 0; + return 400; + if (owner_id != 0 && g.ActiveId == owner_id) + return 300; if (flags & ImGuiInputFlags_RouteOverFocused) - return 2; - return 254; + return 200; + return 1; } IM_ASSERT(0); return 0; @@ -9206,8 +9603,10 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I else IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteTypeMask_)); // Check that only 1 routing flag is used IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner); - if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused)) + if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteUnlessBgFocused)) IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal); + if (flags & ImGuiInputFlags_RouteOverActive) + IM_ASSERT(flags & (ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteFocused)); // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. key_chord = FixupKeyChord(key_chord); @@ -9262,17 +9661,17 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, I const int score = CalcRoutingScore(focus_scope_id, owner_id, flags); IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\n", GetKeyChordName(key_chord), flags, owner_id, score); - if (score == 255) + if (score == 0) return false; // Submit routing for NEXT frame (assuming score is sufficient) - // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). + // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using >= instead of >). ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); - //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); - if (score < routing_data->RoutingNextScore) + //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score >= routing_data->RoutingNextScore) : (score > routing_data->RoutingNextScore); + if (score > routing_data->RoutingNextScore) { routing_data->RoutingNext = owner_id; - routing_data->RoutingNextScore = (ImU8)score; + routing_data->RoutingNextScore = (ImU16)score; } // Return routing state for CURRENT frame @@ -9423,6 +9822,17 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id) return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id) } +// Use if you absolutely need to distinguish single-click from double-click by introducing a delay. +// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test. +// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. +bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]); + return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay); +} + bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button) { ImGuiContext& g = *GImGui; @@ -9599,74 +10009,6 @@ static void ImGui::UpdateKeyboardInputs() if (io.ConfigFlags & ImGuiConfigFlags_NoKeyboard) io.ClearInputKeys(); - // Import legacy keys or verify they are not used -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - if (io.BackendUsingLegacyKeyArrays == 0) - { - // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally. - for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!"); - } - else - { - if (g.FrameCount == 0) - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!"); - - // Build reverse KeyMap (Named -> Legacy) - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++) - if (io.KeyMap[n] != -1) - { - IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n])); - io.KeyMap[io.KeyMap[n]] = n; - } - - // Import legacy keys into new ones - for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++) - if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1) - { - const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n); - IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key)); - io.KeysData[key].Down = io.KeysDown[n]; - if (key != n) - io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends - io.BackendUsingLegacyKeyArrays = 1; - } - if (io.BackendUsingLegacyKeyArrays == 1) - { - GetKeyData(ImGuiMod_Ctrl)->Down = io.KeyCtrl; - GetKeyData(ImGuiMod_Shift)->Down = io.KeyShift; - GetKeyData(ImGuiMod_Alt)->Down = io.KeyAlt; - GetKeyData(ImGuiMod_Super)->Down = io.KeySuper; - } - } -#endif - - // Import legacy ImGuiNavInput_ io inputs and convert to gamepad keys -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active) - { - #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0) - #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0) - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow); - MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp); - MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown); - #undef NAV_MAP_KEY - } -#endif - // Update aliases for (int n = 0; n < ImGuiMouseButton_COUNT; n++) UpdateAliasKey(MouseButtonToKey(n), io.MouseDown[n], io.MouseDown[n] ? 1.0f : 0.0f); @@ -9690,22 +10032,21 @@ static void ImGui::UpdateKeyboardInputs() // Clear gamepad data if disabled if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0) - for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++) + for (int key = ImGuiKey_Gamepad_BEGIN; key < ImGuiKey_Gamepad_END; key++) { - io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false; - io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].Down = false; + io.KeysData[key - ImGuiKey_NamedKey_BEGIN].AnalogValue = 0.0f; } // Update keys - for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++) + for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key++) { - ImGuiKeyData* key_data = &io.KeysData[i]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; key_data->DownDurationPrev = key_data->DownDuration; key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f; if (key_data->DownDuration == 0.0f) { - ImGuiKey key = (ImGuiKey)(ImGuiKey_KeysData_OFFSET + i); - if (IsKeyboardKey(key)) + if (IsKeyboardKey((ImGuiKey)key)) g.LastKeyboardKeyPressTime = g.Time; else if (key == ImGuiKey_ReservedForModCtrl || key == ImGuiKey_ReservedForModShift || key == ImGuiKey_ReservedForModAlt || key == ImGuiKey_ReservedForModSuper) g.LastKeyboardKeyPressTime = g.Time; @@ -9715,7 +10056,7 @@ static void ImGui::UpdateKeyboardInputs() // Update keys/input owner (named keys only): one entry per key for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { - ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET]; + ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_NamedKey_BEGIN]; ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN]; owner_data->OwnerCurr = owner_data->OwnerNext; if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp. @@ -9733,7 +10074,7 @@ static void ImGui::UpdateMouseInputs() ImGuiIO& io = g.IO; // Mouse Wheel swapping flag - // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead + // As a standard behavior holding Shift while using Vertical Mouse Wheel triggers Horizontal scroll instead // - We avoid doing it on OSX as it the OS input layer handles this already. // - FIXME: However this means when running on OSX over Emscripten, Shift+WheelY will incur two swapping (1 in OS, 1 here), canceling the feature. // - FIXME: When we can distinguish e.g. touchpad scroll events from mouse ones, we'll set this accordingly based on input source. @@ -9756,15 +10097,17 @@ static void ImGui::UpdateMouseInputs() g.MouseStationaryTimer = mouse_stationary ? (g.MouseStationaryTimer + io.DeltaTime) : 0.0f; //IMGUI_DEBUG_LOG("%.4f\n", g.MouseStationaryTimer); - // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. + // If mouse moved we re-enable mouse hovering in case it was disabled by keyboard/gamepad. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) - g.NavDisableMouseHover = false; + g.NavHighlightItemUnderNav = false; for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) { io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f; io.MouseClickedCount[i] = 0; // Will be filled below io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f; + if (io.MouseReleased[i]) + io.MouseReleasedTime[i] = g.Time; io.MouseDownDurationPrev[i] = io.MouseDownDuration[i]; io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f; if (io.MouseClicked[i]) @@ -9795,9 +10138,9 @@ static void ImGui::UpdateMouseInputs() // We provide io.MouseDoubleClicked[] as a legacy service io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2); - // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation + // Clicking any mouse button reactivate mouse hovering which may have been deactivated by keyboard/gamepad navigation if (io.MouseClicked[i]) - g.NavDisableMouseHover = false; + g.NavHighlightItemUnderNav = false; } } @@ -9896,8 +10239,9 @@ void ImGui::UpdateMouseWheel() { const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size; SetWindowPos(window, window->Pos + offset, 0); - window->Size = ImTrunc(window->Size * scale); + window->Size = ImTrunc(window->Size * scale); // FIXME: Legacy-ish code, call SetWindowSize()? window->SizeFull = ImTrunc(window->SizeFull * scale); + MarkIniSettingsDirty(window); } return; } @@ -9909,7 +10253,7 @@ void ImGui::UpdateMouseWheel() if (g.IO.MouseWheelRequestAxisSwap) wheel = ImVec2(wheel.y, 0.0f); - // Maintain a rough average of moving magnitude on both axises + // Maintain a rough average of moving magnitude on both axes // FIXME: should by based on wall clock time rather than frame-counter g.WheelingAxisAvg.x = ImExponentialMovingAverage(g.WheelingAxisAvg.x, ImAbs(wheel.x), 30); g.WheelingAxisAvg.y = ImExponentialMovingAverage(g.WheelingAxisAvg.y, ImAbs(wheel.y), 30); @@ -9922,7 +10266,7 @@ void ImGui::UpdateMouseWheel() // Mouse wheel scrolling: find target and apply // - don't renew lock if axis doesn't apply on the window. - // - select a main axis when both axises are being moved. + // - select a main axis when both axes are being moved. if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel))) if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs)) { @@ -9933,7 +10277,7 @@ void ImGui::UpdateMouseWheel() { LockWheelingWindow(window, wheel.x); float max_step = window->InnerRect.GetWidth() * 0.67f; - float scroll_step = ImTrunc(ImMin(2 * window->CalcFontSize(), max_step)); + float scroll_step = ImTrunc(ImMin(2 * window->FontRefSize, max_step)); SetScrollX(window, window->Scroll.x - wheel.x * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } @@ -9941,7 +10285,7 @@ void ImGui::UpdateMouseWheel() { LockWheelingWindow(window, wheel.y); float max_step = window->InnerRect.GetHeight() * 0.67f; - float scroll_step = ImTrunc(ImMin(5 * window->CalcFontSize(), max_step)); + float scroll_step = ImTrunc(ImMin(5 * window->FontRefSize, max_step)); SetScrollY(window, window->Scroll.y - wheel.y * scroll_step); g.WheelingWindowScrolledFrame = g.FrameCount; } @@ -9964,13 +10308,17 @@ void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse) static const char* GetInputSourceName(ImGuiInputSource source) { const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad" }; - IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT); + IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); + if (source < 0 || source >= ImGuiInputSource_COUNT) + return "Unknown"; return input_source_names[source]; } static const char* GetMouseSourceName(ImGuiMouseSource source) { const char* mouse_source_names[] = { "Mouse", "TouchScreen", "Pen" }; - IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT && source >= 0 && source < ImGuiMouseSource_COUNT); + IM_ASSERT(IM_ARRAYSIZE(mouse_source_names) == ImGuiMouseSource_COUNT); + if (source < 0 || source >= ImGuiMouseSource_COUNT) + return "Unknown"; return mouse_source_names[source]; } static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) @@ -10001,7 +10349,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) bool mouse_moved = false, mouse_wheeled = false, key_changed = false, key_changed_nonchar = false, text_inputted = false; int mouse_button_changed = 0x00; - ImBitArray key_changed_mask; + ImBitArray key_changed_mask; int event_n = 0; for (; event_n < g.InputEventsQueue.Size; event_n++) @@ -10058,19 +10406,16 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) if (trickle_interleaved_nonchar_keys_and_text && (text_inputted && !key_is_potentially_for_char_input)) break; + if (key_data->Down != e->Key.Down) // Analog change only do not trigger this, so it won't block e.g. further mouse pos events testing key_changed. + { + key_changed = true; + key_changed_mask.SetBit(key_data_index); + if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) + key_changed_nonchar = true; + } + key_data->Down = e->Key.Down; key_data->AnalogValue = e->Key.AnalogValue; - key_changed = true; - key_changed_mask.SetBit(key_data_index); - if (trickle_interleaved_nonchar_keys_and_text && !key_is_potentially_for_char_input) - key_changed_nonchar = true; - - // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - io.KeysDown[key_data_index] = key_data->Down; - if (io.KeyMap[key_data_index] != -1) - io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; -#endif } else if (e->Type == ImGuiInputEventType_Text) { @@ -10260,7 +10605,7 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, Im void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasShortcut; g.NextItemData.Shortcut = key_chord; g.NextItemData.ShortcutFlags = flags; } @@ -10272,7 +10617,7 @@ void ImGui::ItemHandleShortcut(ImGuiID id) ImGuiInputFlags flags = g.NextItemData.ShortcutFlags; IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()! - if (g.LastItemData.InFlags & ImGuiItemFlags_Disabled) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_Disabled) return; if (flags & ImGuiInputFlags_Tooltip) { @@ -10331,7 +10676,6 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own return true; } - //----------------------------------------------------------------------------- // [SECTION] ERROR CHECKING, STATE RECOVERY //----------------------------------------------------------------------------- @@ -10368,36 +10712,44 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si return !error; } -// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell) -// This is causing issues and ambiguity and we need to retire that. -// See https://github.com/ocornut/imgui/issues/5548 for more details. -// [Scenario 1] +// Until 1.89 (August 2022, IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos()/SetCursorScreenPos() +// to extend contents size of our parent container (e.g. window contents size, which is used for auto-resizing +// windows, table column contents size used for auto-resizing columns, group size). +// This was causing issues and ambiguities and we needed to retire that. +// From 1.89, extending contents size boundaries REQUIRES AN ITEM TO BE SUBMITTED. +// // Previously this would make the window content size ~200x200: -// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK +// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK ANYMORE // Instead, please submit an item: // Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK // Alternative: // Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK -// [Scenario 2] -// For reference this is one of the issue what we aim to fix with this change: -// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() -// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller! -// While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue. +// +// The assert below detects when the _last_ call in a window was a SetCursorPos() not followed by an Item, +// and with a position that would grow the parent contents size. +// +// Advanced: +// - For reference, old logic was causing issues because it meant that SetCursorScreenPos(GetCursorScreenPos()) +// had a side-effect on layout! In particular this caused problem to compute group boundaries. +// e.g. BeginGroup() + SomeItem() + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup() would cause the +// group to be taller because auto-sizing generally adds padding on bottom and right side. +// - While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. +// Using vertical alignment patterns would frequently trigger this sorts of issue. +// - See https://github.com/ocornut/imgui/issues/5548 for more details. void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(window->DC.IsSetPos); window->DC.IsSetPos = false; -#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y) return; if (window->SkipItems) return; - IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent."); -#else - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); -#endif + IM_ASSERT_USER_ERROR(0, "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries.\nPlease submit an item e.g. Dummy() afterwards in order to grow window/parent boundaries."); + + // For reference, the old behavior was essentially: + //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos); } static void ImGui::ErrorCheckNewFrameSanityChecks() @@ -10425,28 +10777,36 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!"); IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?"); IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!"); - IM_ASSERT(g.IO.Fonts->IsBuilt() && "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()"); IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!"); IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations - IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); + IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting!"); + IM_ASSERT(g.Style.WindowBorderHoverPadding > 0.0f && "Invalid style setting!"); // Required otherwise cannot resize from borders. IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right); -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++) - IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < ImGuiKey_LegacyNativeKey_END && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)"); - - // Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP) - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1) - IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation."); -#endif + IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes); // Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way. if (g.IO.ConfigErrorRecovery) IM_ASSERT(g.IO.ConfigErrorRecoveryEnableAssert || g.IO.ConfigErrorRecoveryEnableDebugLog || g.IO.ConfigErrorRecoveryEnableTooltip || g.ErrorCallback != NULL); - // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + if (g.IO.FontGlobalScale > 1.0f) + IM_ASSERT(g.Style.FontScaleMain == 1.0f && "Since 1.92: use style.FontScaleMain instead of g.IO.FontGlobalScale!"); + + // Remap legacy names + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) + { + g.IO.ConfigNavMoveSetMousePos = true; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavEnableSetMousePos; + } + if (g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard) + { + g.IO.ConfigNavCaptureKeyboard = false; + g.IO.ConfigFlags &= ~ImGuiConfigFlags_NavNoCaptureKeyboard; + } + + // Remap legacy clipboard handlers (OBSOLETED in 1.91.1, August 2024) if (g.IO.GetClipboardTextFn != NULL && (g.PlatformIO.Platform_GetClipboardTextFn == NULL || g.PlatformIO.Platform_GetClipboardTextFn == Platform_GetClipboardTextFn_DefaultImpl)) g.PlatformIO.Platform_GetClipboardTextFn = [](ImGuiContext* ctx) { return ctx->IO.GetClipboardTextFn(ctx->IO.ClipboardUserData); }; if (g.IO.SetClipboardTextFn != NULL && (g.PlatformIO.Platform_SetClipboardTextFn == NULL || g.PlatformIO.Platform_SetClipboardTextFn == Platform_SetClipboardTextFn_DefaultImpl)) @@ -10507,8 +10867,16 @@ void ImGui::ErrorRecoveryTryToRecoverState(const ImGuiErrorRecoveryState* state_ ImGuiWindow* window = g.CurrentWindow; if (window->Flags & ImGuiWindowFlags_ChildWindow) { - IM_ASSERT_USER_ERROR(0, "Missing EndChild()"); - EndChild(); + if (g.CurrentTable != NULL && g.CurrentTable->InnerWindow == g.CurrentWindow) + { + IM_ASSERT_USER_ERROR(0, "Missing EndTable()"); + EndTable(); + } + else + { + IM_ASSERT_USER_ERROR(0, "Missing EndChild()"); + EndChild(); + } } else { @@ -10546,6 +10914,11 @@ void ImGui::ErrorRecoveryTryToRecoverWindowState(const ImGuiErrorRecoveryStat IM_ASSERT_USER_ERROR(0, "Missing EndMultiSelect()"); EndMultiSelect(); } + if (window->DC.MenuBarAppending) //-V1044 + { + IM_ASSERT_USER_ERROR(0, "Missing EndMenuBar()"); + EndMenuBar(); + } while (window->DC.TreeDepth > state_in->SizeOfTreeStack) //-V1044 { IM_ASSERT_USER_ERROR(0, "Missing TreePop()"); @@ -10622,7 +10995,7 @@ bool ImGui::ErrorLog(const char* msg) // Output to tooltip if (g.IO.ConfigErrorRecoveryEnableTooltip) { - if (BeginErrorTooltip()) + if (g.WithinFrameScope && BeginErrorTooltip()) { if (g.ErrorCountCurrentFrame < 20) { @@ -10651,33 +11024,41 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() { #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - if (g.DebugDrawIdConflicts != 0 && g.IO.KeyCtrl == false) + if (g.DebugDrawIdConflictsId != 0 && g.IO.KeyCtrl == false) g.DebugDrawIdConflictsCount = g.HoveredIdPreviousFrameItemCount; - if (g.DebugDrawIdConflicts != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) + if (g.DebugDrawIdConflictsId != 0 && g.DebugItemPickerActive == false && BeginErrorTooltip()) { Text("Programmer error: %d visible items with conflicting ID!", g.DebugDrawIdConflictsCount); BulletText("Code should use PushID()/PopID() in loops, or append \"##xx\" to same-label identifiers!"); BulletText("Empty label e.g. Button(\"\") == same ID as parent widget/node. Use Button(\"##xx\") instead!"); //BulletText("Code intending to use duplicate ID may use e.g. PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()"); // Not making this too visible for fear of it being abused. - BulletText("Set io.ConfigDebugDetectIdConflicts=false to disable this warning in non-programmers builds."); + BulletText("Set io.ConfigDebugHighlightIdConflicts=false to disable this warning in non-programmers builds."); Separator(); - Text("(Hold CTRL to: use"); - SameLine(); - if (SmallButton("Item Picker")) - DebugStartItemPicker(); - SameLine(); - Text("to break in item call-stack, or"); - SameLine(); - if (SmallButton("Open FAQ->About ID Stack System") && g.PlatformIO.Platform_OpenInShellFn != NULL) - g.PlatformIO.Platform_OpenInShellFn(&g, "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage"); + if (g.IO.ConfigDebugHighlightIdConflictsShowItemPicker) + { + Text("(Hold Ctrl to: use "); + SameLine(0.0f, 0.0f); + if (SmallButton("Item Picker")) + DebugStartItemPicker(); + SameLine(0.0f, 0.0f); + Text(" to break in item call-stack, or "); + } + else + { + Text("(Hold Ctrl to: "); + } + SameLine(0.0f, 0.0f); + TextLinkOpenURL("read FAQ \"About ID Stack System\"", "https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#qa-usage"); + SameLine(0.0f, 0.0f); + Text(")"); EndErrorTooltip(); } if (g.ErrorCountCurrentFrame > 0 && BeginErrorTooltip()) // Amend at end of frame { Separator(); - Text("(Hold CTRL to:"); - SameLine(); + Text("(Hold Ctrl to: "); + SameLine(0.0f, 0.0f); if (SmallButton("Enable Asserts")) g.IO.ConfigErrorRecoveryEnableAssert = true; //SameLine(); @@ -10690,7 +11071,7 @@ void ImGui::ErrorCheckEndFrameFinalizeErrorTooltip() #endif } -// Pseudo-tooltip. Follow mouse until CTRL is held. When CTRL is held we lock position, allowing to click it. +// Pseudo-tooltip. Follow mouse until Ctrl is held. When Ctrl is held we lock position, allowing to click it. bool ImGui::BeginErrorTooltip() { ImGuiContext& g = *GImGui; @@ -10733,8 +11114,8 @@ void ImGui::KeepAliveID(ImGuiID id) ImGuiContext& g = *GImGui; if (g.ActiveId == id) g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; + if (g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.IsAlive = true; } // Declare item bounding box for clipping and interaction. @@ -10752,7 +11133,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu g.LastItemData.ID = id; g.LastItemData.Rect = bb; g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; - g.LastItemData.InFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.ItemFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. @@ -10770,9 +11151,9 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. - if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) + if (!(g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav)) { - // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. + // FIXME-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); if (g.NavId == id || g.NavAnyRequest) if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) @@ -10780,12 +11161,12 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu NavProcessItem(); } - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasShortcut) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasShortcut) ItemHandleShortcut(id); } // Lightweight clear of SetNextItemXXX data. - g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + g.NextItemData.HasFlags = ImGuiNextItemDataFlags_None; g.NextItemData.ItemFlags = ImGuiItemFlags_None; #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -10813,12 +11194,30 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGu // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". // READ THE FAQ: https://dearimgui.com/faq IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); + + // [DEBUG] Highlight all conflicts WITHOUT needing to hover. THIS WILL SLOW DOWN DEAR IMGUI. DON'T KEEP ACTIVATED. + // This will only work for items submitted with ItemAdd(). Some very rare/odd/unrecommended code patterns are calling ButtonBehavior() without ItemAdd(). +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_AllowDuplicateId) == 0) + { + int* p_alive = g.DebugDrawIdConflictsAliveCount.GetIntRef(id, -1); // Could halve lookups if we knew ImGuiStorage can store 64-bit, or by storing FrameCount as 30-bits + highlight as 2-bits. But the point is that we should not pretend that this is fast. + int* p_highlight = g.DebugDrawIdConflictsHighlightSet.GetIntRef(id, -1); + if (*p_alive == g.FrameCount) + *p_highlight = g.FrameCount; + *p_alive = g.FrameCount; + if (*p_highlight >= g.FrameCount - 1) + window->DrawList->AddRect(bb.Min - ImVec2(1, 1), bb.Max + ImVec2(1, 1), IM_COL32(255, 0, 0, 255), 0.0f, ImDrawFlags_None, 2.0f); + } +#endif } //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] - //if ((g.LastItemData.InFlags & ImGuiItemFlags_NoNav) == 0) + //if ((g.LastItemData.ItemFlags & ImGuiItemFlags_NoNav) == 0) // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] #endif + if (id != 0 && g.DeactivatedItemData.ID == id) + g.DeactivatedItemData.ElapseFrame = g.FrameCount; + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) if (is_rect_visible) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; @@ -11014,7 +11413,7 @@ void ImGui::Unindent(float indent_w) void ImGui::SetNextItemWidth(float item_width) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasWidth; g.NextItemData.Width = item_width; } @@ -11025,7 +11424,7 @@ void ImGui::PushItemWidth(float item_width) ImGuiWindow* window = g.CurrentWindow; window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width); - g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } void ImGui::PushMultiItemsWidths(int components, float w_full) @@ -11044,7 +11443,7 @@ void ImGui::PushMultiItemsWidths(int components, float w_full) prev_split = next_split; } window->DC.ItemWidth = ImMax(prev_split, 1.0f); - g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth; + g.NextItemData.HasFlags &= ~ImGuiNextItemDataFlags_HasWidth; } void ImGui::PopItemWidth() @@ -11067,7 +11466,7 @@ float ImGui::CalcItemWidth() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; float w; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth) w = g.NextItemData.Width; else w = window->DC.ItemWidth; @@ -11178,7 +11577,8 @@ void ImGui::BeginGroup() group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive; group_data.BackupHoveredIdIsAlive = g.HoveredId != 0; group_data.BackupIsSameLine = window->DC.IsSameLine; - group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive; + group_data.BackupActiveIdHasBeenEditedThisFrame = g.ActiveIdHasBeenEditedThisFrame; + group_data.BackupDeactivatedIdIsAlive = g.DeactivatedItemData.IsAlive; group_data.EmitItem = true; window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x; @@ -11229,11 +11629,11 @@ void ImGui::EndGroup() // Also if you grep for LastItemId you'll notice it is only used in that context. // (The two tests not the same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.) const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId; - const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true); + const bool group_contains_deactivated_id = (group_data.BackupDeactivatedIdIsAlive == false) && (g.DeactivatedItemData.IsAlive == true); if (group_contains_curr_active_id) g.LastItemData.ID = g.ActiveId; - else if (group_contains_prev_active_id) - g.LastItemData.ID = g.ActiveIdPreviousFrame; + else if (group_contains_deactivated_id) + g.LastItemData.ID = g.DeactivatedItemData.ID; g.LastItemData.Rect = group_bb; // Forward Hovered flag @@ -11242,12 +11642,12 @@ void ImGui::EndGroup() g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow; // Forward Edited flag - if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame) + if (g.ActiveIdHasBeenEditedThisFrame && !group_data.BackupActiveIdHasBeenEditedThisFrame) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited; // Forward Deactivated flag g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated; - if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame) + if (group_contains_deactivated_id) g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated; g.GroupStack.pop_back(); @@ -11291,7 +11691,7 @@ static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window) } scroll[axis] = scroll_target - center_ratio * (window->SizeFull[axis] - decoration_size[axis]); } - scroll[axis] = IM_ROUND(ImMax(scroll[axis], 0.0f)); + scroll[axis] = ImRound64(ImMax(scroll[axis], 0.0f)); if (!window->Collapsed && !window->SkipItems) scroll[axis] = ImMin(scroll[axis], window->ScrollMax[axis]); } @@ -11524,10 +11924,10 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext // - offset visibility to increase visibility around mouse. // - never clamp within outer viewport boundary. // We call SetNextWindowPos() to enforce position and disable clamping. - // See FindBestWindowPosForPopup() for positionning logic of other tooltips (not drag and drop ones). + // See FindBestWindowPosForPopup() for positioning logic of other tooltips (not drag and drop ones). //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; const bool is_touchscreen = (g.IO.MouseSource == ImGuiMouseSource_TouchScreen); - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0) { ImVec2 tooltip_pos = is_touchscreen ? (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * g.Style.MouseCursorScale) : (g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * g.Style.MouseCursorScale); ImVec2 tooltip_pivot = is_touchscreen ? TOOLTIP_DEFAULT_PIVOT_TOUCH : ImVec2(0.0f, 0.0f); @@ -11535,20 +11935,21 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext } SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f); - //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( + //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkerboard has issue with transparent colors :( tooltip_flags |= ImGuiTooltipFlags_OverridePrevious; } - const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; - char window_name[32]; - ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); - if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL && g.TooltipPreviousWindow->Active) + // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. + if ((tooltip_flags & ImGuiTooltipFlags_OverridePrevious) && g.TooltipPreviousWindow != NULL && g.TooltipPreviousWindow->Active && !IsWindowInBeginStack(g.TooltipPreviousWindow)) { - // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. //IMGUI_DEBUG_LOG("[tooltip] '%s' already active, using +1 for this frame\n", window_name); SetWindowHiddenAndSkipItemsForCurrentFrame(g.TooltipPreviousWindow); - ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, ++g.TooltipOverrideCount); + g.TooltipOverrideCount++; } + + const char* window_name_template = is_dragdrop_tooltip ? "##Tooltip_DragDrop_%02d" : "##Tooltip_%02d"; + char window_name[32]; + ImFormatString(window_name, IM_ARRAYSIZE(window_name), window_name_template, g.TooltipOverrideCount); ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; Begin(window_name, NULL, flags | extra_window_flags); // 2023-03-09: Added bool return value to the API, but currently always returning true. @@ -11666,6 +12067,43 @@ ImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal() return NULL; } + +// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) +// should be positioned behind that modal window, unless the window was created inside the modal begin-stack. +// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. +// - WindowA // FindBlockingModal() returns Modal1 +// - WindowB // .. returns Modal1 +// - Modal1 // .. returns Modal2 +// - WindowC // .. returns Modal2 +// - WindowD // .. returns Modal2 +// - Modal2 // .. returns Modal2 +// - WindowE // .. returns NULL +// Notes: +// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL. +// Only difference is here we check for ->Active/WasActive but it may be unnecessary. +ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= 0) + return NULL; + + // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal. + for (ImGuiPopupData& popup_data : g.OpenPopupStack) + { + ImGuiWindow* popup_window = popup_data.Window; + if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal)) + continue; + if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows. + continue; + if (window == NULL) // FindBlockingModal(NULL) test for if FocusWindow(NULL) is naturally possible via a mouse click. + return popup_window; + if (IsWindowWithinBeginStackOf(window, popup_window)) // Window may be over modal + continue; + return popup_window; // Place window right below first block modal + } + return NULL; +} + void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags) { ImGuiContext& g = *GImGui; @@ -11866,29 +12304,44 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags) } char name[20]; - if (extra_window_flags & ImGuiWindowFlags_ChildMenu) - ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuDepth); // Recycle windows based on depth - else - ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame + IM_ASSERT((extra_window_flags & ImGuiWindowFlags_ChildMenu) == 0); // Use BeginPopupMenuEx() + ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // No recycling, so we can close/open during the same frame bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); - //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; - return is_open; } -bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +bool ImGui::BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags) { ImGuiContext& g = *GImGui; - if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance + if (!IsPopupOpen(id, ImGuiPopupFlags_None)) { g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values return false; } - flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings; + + char name[128]; + IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu); + ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth + bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup); + if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) + EndPopup(); + //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; + return is_open; +} + +bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance + { + g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values + return false; + } + flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings; ImGuiID id = g.CurrentWindow->GetID(str_id); return BeginPopupEx(id, flags); } @@ -11913,7 +12366,7 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla // Center modal windows by default for increased visibility // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves) // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasPos) == 0) { const ImGuiViewport* viewport = GetMainViewport(); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); @@ -11935,19 +12388,22 @@ void ImGui::EndPopup() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls - IM_ASSERT(g.BeginPopupStack.Size > 0); + if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || g.BeginPopupStack.Size == 0) + { + IM_ASSERT_USER_ERROR(0, "Calling EndPopup() too many times or in wrong window!"); + return; + } // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests) if (g.NavWindow == window) NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY); // Child-popups don't need to be laid out - IM_ASSERT(g.WithinEndChild == false); + const ImGuiID backup_within_end_child_id = g.WithinEndChildID; if (window->Flags & ImGuiWindowFlags_ChildWindow) - g.WithinEndChild = true; + g.WithinEndChildID = window->ID; End(); - g.WithinEndChild = false; + g.WithinEndChildID = backup_within_end_child_id; } // Helper to open a popup if mouse button is released over the item @@ -12037,10 +12493,10 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s // Combo Box policy (we want a connecting edge) if (policy == ImGuiPopupPositionPolicy_ComboBox) { - const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; + const ImGuiDir dir_preferred_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up }; for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) { - const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + const ImGuiDir dir = (n == -1) ? *last_dir : dir_preferred_order[n]; if (n != -1 && dir == *last_dir) // Already tried this direction? continue; ImVec2 pos; @@ -12059,10 +12515,10 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s // (Always first try the direction we used on the last frame, if any) if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default) { - const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; + const ImGuiDir dir_preferred_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left }; for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++) { - const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n]; + const ImGuiDir dir = (n == -1) ? *last_dir : dir_preferred_order[n]; if (n != -1 && dir == *last_dir) // Already tried this direction? continue; @@ -12095,78 +12551,345 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s if (policy == ImGuiPopupPositionPolicy_Tooltip) return ref_pos + ImVec2(2, 2); - // Otherwise try to keep within display - ImVec2 pos = ref_pos; - pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); - pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); - return pos; -} + // Otherwise try to keep within display + ImVec2 pos = ref_pos; + pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x); + pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y); + return pos; +} + +// Note that this is used for popups, which can overlap the non work-area of individual viewports. +ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(window); + ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect(); + ImVec2 padding = g.Style.DisplaySafeAreaPadding; + r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); + return r_screen; +} + +ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + + ImRect r_outer = GetPopupAllowedExtentRect(window); + if (window->Flags & ImGuiWindowFlags_ChildMenu) + { + // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. + // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. + IM_ASSERT(g.CurrentWindow == window); + ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window; + float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). + ImRect r_avoid; + if (parent_window->DC.MenuBarAppending) + r_avoid = ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX, parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field + else + r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); + } + if (window->Flags & ImGuiWindowFlags_Popup) + { + return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, ImRect(window->Pos, window->Pos), ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here + } + if (window->Flags & ImGuiWindowFlags_Tooltip) + { + // Position tooltip (always follows mouse + clamp within outer boundaries) + // FIXME: + // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default). + // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position. + // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling + // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). + IM_ASSERT(g.CurrentWindow == window); + const float scale = g.Style.MouseCursorScale; + const ImVec2 ref_pos = NavCalcPreferredRefPos(); + + if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen && NavCalcPreferredRefPosSource() == ImGuiInputSource_Mouse) + { + ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); + if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) + return tooltip_pos; + } + + ImVec2 tooltip_pos = ref_pos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale; + ImRect r_avoid; + if (g.NavCursorVisible && g.NavHighlightItemUnderNav && !g.IO.ConfigNavMoveSetMousePos) + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); + else + r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. + //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255)); + + return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); + } + IM_ASSERT(0); + return window->Pos; +} + +//----------------------------------------------------------------------------- +// [SECTION] WINDOW FOCUS +//---------------------------------------------------------------------------- +// - SetWindowFocus() +// - SetNextWindowFocus() +// - IsWindowFocused() +// - UpdateWindowInFocusOrderList() [Internal] +// - BringWindowToFocusFront() [Internal] +// - BringWindowToDisplayFront() [Internal] +// - BringWindowToDisplayBack() [Internal] +// - BringWindowToDisplayBehind() [Internal] +// - FindWindowDisplayIndex() [Internal] +// - FocusWindow() [Internal] +// - FocusTopMostWindowUnderOne() [Internal] +//----------------------------------------------------------------------------- + +void ImGui::SetWindowFocus() +{ + FocusWindow(GImGui->CurrentWindow); +} + +void ImGui::SetWindowFocus(const char* name) +{ + if (name) + { + if (ImGuiWindow* window = FindWindowByName(name)) + FocusWindow(window); + } + else + { + FocusWindow(NULL); + } +} + +void ImGui::SetNextWindowFocus() +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.HasFlags |= ImGuiNextWindowDataFlags_HasFocus; +} + +// Similar to IsWindowHovered() +bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* ref_window = g.NavWindow; + ImGuiWindow* cur_window = g.CurrentWindow; + + if (ref_window == NULL) + return false; + if (flags & ImGuiFocusedFlags_AnyWindow) + return true; + + IM_ASSERT(cur_window); // Not inside a Begin()/End() + const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0; + if (flags & ImGuiFocusedFlags_RootWindow) + cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy); + + if (flags & ImGuiFocusedFlags_ChildWindows) + return IsWindowChildOf(ref_window, cur_window, popup_hierarchy); + else + return (ref_window == cur_window); +} + +static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + int order = window->FocusOrder; + IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking) + IM_ASSERT(g.WindowsFocusOrder[order] == window); + return order; +} + +static void ImGui::UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags) +{ + ImGuiContext& g = *GImGui; + + const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0); + const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild; + if ((just_created || child_flag_changed) && !new_is_explicit_child) + { + IM_ASSERT(!g.WindowsFocusOrder.contains(window)); + g.WindowsFocusOrder.push_back(window); + window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1); + } + else if (!just_created && child_flag_changed && new_is_explicit_child) + { + IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window); + for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++) + g.WindowsFocusOrder[n]->FocusOrder--; + g.WindowsFocusOrder.erase(g.WindowsFocusOrder.Data + window->FocusOrder); + window->FocusOrder = -1; + } + window->IsExplicitChild = new_is_explicit_child; +} + +void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == window->RootWindow); + + const int cur_order = window->FocusOrder; + IM_ASSERT(g.WindowsFocusOrder[cur_order] == window); + if (g.WindowsFocusOrder.back() == window) + return; + + const int new_order = g.WindowsFocusOrder.Size - 1; + for (int n = cur_order; n < new_order; n++) + { + g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1]; + g.WindowsFocusOrder[n]->FocusOrder--; + IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n); + } + g.WindowsFocusOrder[new_order] = window; + window->FocusOrder = (short)new_order; +} + +// Note technically focus related but rather adjacent and close to BringWindowToFocusFront() +void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* current_front_window = g.Windows.back(); + if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better) + return; + for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window + if (g.Windows[i] == window) + { + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); + g.Windows[g.Windows.Size - 1] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.Windows[0] == window) + return; + for (int i = 0; i < g.Windows.Size; i++) + if (g.Windows[i] == window) + { + memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*)); + g.Windows[0] = window; + break; + } +} + +void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window) +{ + IM_ASSERT(window != NULL && behind_window != NULL); + ImGuiContext& g = *GImGui; + window = window->RootWindow; + behind_window = behind_window->RootWindow; + int pos_wnd = FindWindowDisplayIndex(window); + int pos_beh = FindWindowDisplayIndex(behind_window); + if (pos_wnd < pos_beh) + { + size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*); + memmove(&g.Windows.Data[pos_wnd], &g.Windows.Data[pos_wnd + 1], copy_bytes); + g.Windows[pos_beh - 1] = window; + } + else + { + size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*); + memmove(&g.Windows.Data[pos_beh + 1], &g.Windows.Data[pos_beh], copy_bytes); + g.Windows[pos_beh] = window; + } +} + +int ImGui::FindWindowDisplayIndex(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + return g.Windows.index_from_ptr(g.Windows.find(window)); +} + +// Moving window to front of display and set focus (which happens to be back of our sorted list) +void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) +{ + ImGuiContext& g = *GImGui; + + // Modal check? + if ((flags & ImGuiFocusRequestFlags_UnlessBelowModal) && (g.NavWindow != window)) // Early out in common case. + if (ImGuiWindow* blocking_modal = FindBlockingModal(window)) + { + // This block would typically be reached in two situations: + // - API call to FocusWindow() with a window under a modal and ImGuiFocusRequestFlags_UnlessBelowModal flag. + // - User clicking on void or anything behind a modal while a modal is open (window == NULL) + IMGUI_DEBUG_LOG_FOCUS("[focus] FocusWindow(\"%s\", UnlessBelowModal): prevented by \"%s\".\n", window ? window->Name : "", blocking_modal->Name); + if (window && window == window->RootWindow && (window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayBehind(window, blocking_modal); // Still bring right under modal. (FIXME: Could move in focus list too?) + ClosePopupsOverWindow(GetTopMostPopupModal(), false); // Note how we need to use GetTopMostPopupModal() aad NOT blocking_modal, to handle nested modals + return; + } + + // Find last focused child (if any) and focus it instead. + if ((flags & ImGuiFocusRequestFlags_RestoreFocusedChild) && window != NULL) + window = NavRestoreLastChildNavWindow(window); + + // Apply focus + if (g.NavWindow != window) + { + SetNavWindow(window); + if (window && g.NavHighlightItemUnderNav) + g.NavMousePosDirty = true; + g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId + g.NavLayer = ImGuiNavLayer_Main; + SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); + g.NavIdIsAlive = false; + g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; + + // Close popups if any + ClosePopupsOverWindow(window, false); + } + + // Move the root window to the top of the pile + IM_ASSERT(window == NULL || window->RootWindow != NULL); + ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop + ImGuiWindow* display_front_window = window ? window->RootWindow : NULL; + + // Steal active widgets. Some of the cases it triggers includes: + // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run. + // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId) + if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window) + if (!g.ActiveIdNoClearOnFocusLoss) + ClearActiveID(); + + // Passing NULL allow to disable keyboard focus + if (!window) + return; -// Note that this is used for popups, which can overlap the non work-area of individual viewports. -ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window) -{ - ImGuiContext& g = *GImGui; - IM_UNUSED(window); - ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect(); - ImVec2 padding = g.Style.DisplaySafeAreaPadding; - r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f)); - return r_screen; + // Bring to front + BringWindowToFocusFront(focus_front_window); + if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0) + BringWindowToDisplayFront(display_front_window); } -ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags) { ImGuiContext& g = *GImGui; - - ImRect r_outer = GetPopupAllowedExtentRect(window); - if (window->Flags & ImGuiWindowFlags_ChildMenu) - { - // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds. - // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu. - IM_ASSERT(g.CurrentWindow == window); - ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window; - float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x). - ImRect r_avoid; - if (parent_window->DC.MenuBarAppending) - r_avoid = ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX, parent_window->ClipRect.Max.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field - else - r_avoid = ImRect(parent_window->Pos.x + horizontal_overlap, -FLT_MAX, parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x, FLT_MAX); - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Default); - } - if (window->Flags & ImGuiWindowFlags_Popup) + IM_UNUSED(filter_viewport); // Unused in master branch. + int start_idx = g.WindowsFocusOrder.Size - 1; + if (under_this_window != NULL) { - return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, ImRect(window->Pos, window->Pos), ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here + // Aim at root window behind us, if we are in a child window that's our own root (see #4640) + int offset = -1; + while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow) + { + under_this_window = under_this_window->ParentWindow; + offset = 0; + } + start_idx = FindWindowFocusIndex(under_this_window) + offset; } - if (window->Flags & ImGuiWindowFlags_Tooltip) + for (int i = start_idx; i >= 0; i--) { - // Position tooltip (always follows mouse + clamp within outer boundaries) - // FIXME: - // - Too many paths. One problem is that FindBestWindowPosForPopupEx() doesn't allow passing a suggested position (so touch screen path doesn't use it by default). - // - Drag and drop tooltips are not using this path either: BeginTooltipEx() manually sets their position. - // - Require some tidying up. In theory we could handle both cases in same location, but requires a bit of shuffling - // as drag and drop tooltips are calling SetNextWindowPos() leading to 'window_pos_set_by_api' being set in Begin(). - IM_ASSERT(g.CurrentWindow == window); - const float scale = g.Style.MouseCursorScale; - const ImVec2 ref_pos = NavCalcPreferredRefPos(); - - if (g.IO.MouseSource == ImGuiMouseSource_TouchScreen) + // We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but that may feel more confusing to the user. + ImGuiWindow* window = g.WindowsFocusOrder[i]; + if (window == ignore_window || !window->WasActive) + continue; + if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) { - ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_TOUCH * scale - (TOOLTIP_DEFAULT_PIVOT_TOUCH * window->Size); - if (r_outer.Contains(ImRect(tooltip_pos, tooltip_pos + window->Size))) - return tooltip_pos; + FocusWindow(window, flags); + return; } - - ImVec2 tooltip_pos = g.IO.MousePos + TOOLTIP_DEFAULT_OFFSET_MOUSE * scale; - ImRect r_avoid; - if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) - r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); - else - r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * scale, ref_pos.y + 24 * scale); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact dimension not very important. - //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255, 0, 255, 255)); - - return FindBestWindowPosForPopupEx(tooltip_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid, ImGuiPopupPositionPolicy_Tooltip); } - IM_ASSERT(0); - return window->Pos; + FocusWindow(NULL, flags); } //----------------------------------------------------------------------------- @@ -12177,6 +12900,23 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // In our terminology those should be interchangeable, yet right now this is super confusing. // Those two functions are merely a legacy artifact, so at minimum naming should be clarified. +void ImGui::SetNavCursorVisible(bool visible) +{ + ImGuiContext& g = *GImGui; + if (g.IO.ConfigNavCursorVisibleAlways) + visible = true; + g.NavCursorVisible = visible; +} + +// (was called NavRestoreHighlightAfterMove() before 1.91.4) +void ImGui::SetNavCursorVisibleAfterMove() +{ + ImGuiContext& g = *GImGui; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; + g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; +} + void ImGui::SetNavWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -12238,9 +12978,9 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) - g.NavDisableMouseHover = true; - else - g.NavDisableHighlight = true; + g.NavHighlightItemUnderNav = true; + else if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; // Clear preferred scoring position (NavMoveRequestApplyResult() will tend to restore it) NavClearPreferredPosForAxis(ImGuiAxis_X); @@ -12263,8 +13003,8 @@ static float inline NavScoreItemDistInterval(float cand_min, float cand_max, flo return 0.0f; } -// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool ImGui::NavScoreItem(ImGuiNavItemData* result) +// Scoring function for keyboard/gamepad directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool ImGui::NavScoreItem(ImGuiNavItemData* result, const ImRect& nav_bb) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -12272,7 +13012,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) return false; // FIXME: Those are not good variables names - ImRect cand = g.LastItemData.NavRect; // Current item nav rectangle + ImRect cand = nav_bb; // Current item nav rectangle const ImRect curr = g.NavScoringRect; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) g.NavScoringDebugCount++; @@ -12329,7 +13069,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) const ImGuiDir move_dir = g.NavMoveDir; #if IMGUI_DEBUG_NAV_SCORING char buf[200]; - if (g.IO.KeyCtrl) // Hold CTRL to preview score in matching quadrant. CTRL+Arrow to rotate. + if (g.IO.KeyCtrl) // Hold Ctrl to preview score in matching quadrant. Ctrl+Arrow to rotate. { if (quadrant == move_dir) { @@ -12412,9 +13152,9 @@ static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result) result->Window = window; result->ID = g.LastItemData.ID; result->FocusScopeId = g.CurrentFocusScopeId; - result->InFlags = g.LastItemData.InFlags; + result->ItemFlags = g.LastItemData.ItemFlags; result->RectRel = WindowRectAbsToRel(window, g.LastItemData.NavRect); - if (result->InFlags & ImGuiItemFlags_HasSelectionUserData) + if (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); result->SelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData. @@ -12437,15 +13177,15 @@ static void ImGui::NavProcessItem() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; const ImGuiID id = g.LastItemData.ID; - const ImGuiItemFlags item_flags = g.LastItemData.InFlags; + const ImGuiItemFlags item_flags = g.LastItemData.ItemFlags; - // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221) + // When inside a container that isn't scrollable with Left<>Right, clip NavRect accordingly (#2221, #8816) + ImRect nav_bb = g.LastItemData.NavRect; if (window->DC.NavIsScrollPushableX == false) { - g.LastItemData.NavRect.Min.x = ImClamp(g.LastItemData.NavRect.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); - g.LastItemData.NavRect.Max.x = ImClamp(g.LastItemData.NavRect.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + nav_bb.Min.x = ImClamp(nav_bb.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x); + nav_bb.Max.x = ImClamp(nav_bb.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x); } - const ImRect nav_bb = g.LastItemData.NavRect; // Process Init Request if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0) @@ -12477,15 +13217,19 @@ static void ImGui::NavProcessItem() else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) { ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - if (NavScoreItem(result)) + if (NavScoreItem(result, nav_bb)) NavApplyItemToResult(result); // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. const float VISIBLE_RATIO = 0.70f; - if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) - if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) - if (NavScoreItem(&g.NavMoveResultLocalVisible)) - NavApplyItemToResult(&g.NavMoveResultLocalVisible); + if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) + { + const ImRect& r = window->InnerRect; // window->ClipRect + if (r.Overlaps(nav_bb)) + if (ImClamp(nav_bb.Max.y, r.Min.y, r.Max.y) - ImClamp(nav_bb.Min.y, r.Min.y, r.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) + if (NavScoreItem(&g.NavMoveResultLocalVisible, nav_bb)) + NavApplyItemToResult(&g.NavMoveResultLocalVisible); + } } } } @@ -12499,7 +13243,7 @@ static void ImGui::NavProcessItem() SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; - if (g.LastItemData.InFlags & ImGuiItemFlags_HasSelectionUserData) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_HasSelectionUserData) { IM_ASSERT(g.NextItemData.SelectionUserData != ImGuiSelectionUserData_Invalid); g.NavLastValidSelectionUserData = g.NextItemData.SelectionUserData; // INTENTIONAL: At this point this field is not cleared in NextItemData. Avoid unnecessary copy to LastItemData. @@ -12614,13 +13358,13 @@ void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result) NavUpdateAnyRequestFlag(); } -// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere -void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data) +// Called by TreePop() to implement ImGuiTreeNodeFlags_NavLeftJumpsToParent +void ImGui::NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, const ImGuiTreeNodeStackData* tree_node_data) { ImGuiContext& g = *GImGui; g.NavMoveScoringItems = false; g.LastItemData.ID = tree_node_data->ID; - g.LastItemData.InFlags = tree_node_data->InFlags & ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper). + g.LastItemData.ItemFlags = tree_node_data->ItemFlags & ~ImGuiItemFlags_HasSelectionUserData; // Losing SelectionUserData, recovered next-frame (cheaper). g.LastItemData.NavRect = tree_node_data->NavRect; NavApplyItemToResult(result); // Result this instead of implementing a NavApplyPastTreeNodeToResult() NavClearPreferredPosForAxis(ImGuiAxis_Y); @@ -12703,13 +13447,6 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) } } -void ImGui::NavRestoreHighlightAfterMove() -{ - ImGuiContext& g = *GImGui; - g.NavDisableHighlight = false; - g.NavDisableMouseHover = g.NavMousePosDirty = true; -} - static inline void ImGui::NavUpdateAnyRequestFlag() { ImGuiContext& g = *GImGui; @@ -12750,14 +13487,32 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) } } +static ImGuiInputSource ImGui::NavCalcPreferredRefPosSource() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.NavWindow; + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. + if ((!g.NavCursorVisible || !g.NavHighlightItemUnderNav || !window) && !activated_shortcut) + return ImGuiInputSource_Mouse; + else + return ImGuiInputSource_Keyboard; // or Nav in general +} + static ImVec2 ImGui::NavCalcPreferredRefPos() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.NavWindow; + ImGuiInputSource source = NavCalcPreferredRefPosSource(); + const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID; + if (source != ImGuiInputSource_Mouse && !activated_shortcut && window == NULL) + source = ImGuiInputSource_Mouse; + // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag. - if ((g.NavDisableHighlight || !g.NavDisableMouseHover || !window) && !activated_shortcut) + if (source == ImGuiInputSource_Mouse) { // Mouse (we need a fallback in case the mouse becomes invalid after being used) // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard. @@ -12771,11 +13526,11 @@ static ImVec2 ImGui::NavCalcPreferredRefPos() ImRect ref_rect; if (activated_shortcut) ref_rect = g.LastItemData.NavRect; - else + else if (window != NULL) ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]); // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?) - if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX)) + if (window != NULL && window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX)) { ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window); ref_rect.Translate(window->Scroll - next_scroll); @@ -12846,11 +13601,14 @@ static void ImGui::NavUpdate() NavMoveRequestApplyResult(); g.NavTabbingCounter = 0; g.NavMoveSubmitted = g.NavMoveScoringItems = false; + if (g.NavCursorHideFrames > 0) + if (--g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling) bool set_mouse_pos = false; if (g.NavMousePosDirty && g.NavIdIsAlive) - if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + if (g.NavCursorVisible && g.NavHighlightItemUnderNav && g.NavWindow) set_mouse_pos = true; g.NavMousePosDirty = false; IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu); @@ -12861,12 +13619,12 @@ static void ImGui::NavUpdate() if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main) g.NavWindow->NavLastChildNavWindow = NULL; - // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) + // Update Ctrl+Tab and Windowing features (hold Square to move/resize/etc.) NavUpdateWindowing(); // Set output flags for user application io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); - io.NavVisible = (io.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL); + io.NavVisible = (io.NavActive && g.NavId != 0 && g.NavCursorVisible) || (g.NavWindowingTarget != NULL); // Process NavCancel input (to close a popup, get back to parent, clear focus) NavUpdateCancelRequest(); @@ -12874,7 +13632,7 @@ static void ImGui::NavUpdate() // Process manual activation request g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = 0; g.NavActivateFlags = ImGuiActivateFlags_None; - if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + if (g.NavId != 0 && g.NavCursorVisible && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner)); const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner))); @@ -12899,7 +13657,9 @@ static void ImGui::NavUpdate() } } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - g.NavDisableHighlight = true; + g.NavCursorVisible = false; + else if (g.IO.ConfigNavCursorVisibleAlways && g.NavCursorHideFrames == 0) + g.NavCursorVisible = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); @@ -12930,7 +13690,7 @@ static void ImGui::NavUpdate() { // *Fallback* manual-scroll with Nav directional keys when window has no navigable item ImGuiWindow* window = g.NavWindow; - const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + const float scroll_speed = IM_ROUND(window->FontRefSize * 100 * io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. const ImGuiDir move_dir = g.NavMoveDir; if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY && move_dir != ImGuiDir_None) { @@ -12956,13 +13716,13 @@ static void ImGui::NavUpdate() // Always prioritize mouse highlight if navigation is disabled if (!nav_keyboard_active && !nav_gamepad_active) { - g.NavDisableHighlight = true; - g.NavDisableMouseHover = set_mouse_pos = false; + g.NavCursorVisible = false; + g.NavHighlightItemUnderNav = set_mouse_pos = false; } // Update mouse position if requested // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied) - if (set_mouse_pos && (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + if (set_mouse_pos && io.ConfigNavMoveSetMousePos && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) TeleportMousePos(NavCalcPreferredRefPos()); // [DEBUG] @@ -12992,7 +13752,7 @@ void ImGui::NavInitRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = 0; g.NavJustMovedToIsTabbing = false; - g.NavJustMovedToHasSelectionData = (result->InFlags & ImGuiItemFlags_HasSelectionUserData) != 0; + g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; } // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) @@ -13003,7 +13763,7 @@ void ImGui::NavInitRequestApplyResult() if (result->SelectionUserData != ImGuiSelectionUserData_Invalid) g.NavLastValidSelectionUserData = result->SelectionUserData; if (g.NavInitRequestFromMove) - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } // Bias scoring rect ahead of scoring + update preferred pos (if missing) using source position @@ -13067,16 +13827,11 @@ void ImGui::NavUpdateCreateMoveRequest() // Update PageUp/PageDown/Home/End scroll // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag? - float scoring_rect_offset_y = 0.0f; + float scoring_page_offset_y = 0.0f; if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active) - scoring_rect_offset_y = NavUpdatePageUpPageDown(); - if (scoring_rect_offset_y != 0.0f) - { - g.NavScoringNoClipRect = window->InnerRect; - g.NavScoringNoClipRect.TranslateY(scoring_rect_offset_y); - } + scoring_page_offset_y = NavUpdatePageUpPageDown(); - // [DEBUG] Always send a request when holding CTRL. Hold CTRL + Arrow change the direction. + // [DEBUG] Always send a request when holding Ctrl. Hold Ctrl + Arrow change the direction. #if IMGUI_DEBUG_NAV_SCORING //if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C)) // g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3); @@ -13100,7 +13855,8 @@ void ImGui::NavUpdateCreateMoveRequest() IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "", g.NavLayer); g.NavInitRequest = g.NavInitRequestFromMove = true; g.NavInitResult.ID = 0; - g.NavDisableHighlight = false; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = true; } // When using gamepad, we project the reference nav bounding box into window visible area. @@ -13119,8 +13875,8 @@ void ImGui::NavUpdateCreateMoveRequest() if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(window->NavRectRel[g.NavLayer])) { IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel for gamepad move\n"); - float pad_x = ImMin(inner_rect_rel.GetWidth(), window->CalcFontSize() * 0.5f); - float pad_y = ImMin(inner_rect_rel.GetHeight(), window->CalcFontSize() * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item + float pad_x = ImMin(inner_rect_rel.GetWidth(), window->FontRefSize * 0.5f); + float pad_y = ImMin(inner_rect_rel.GetHeight(), window->FontRefSize * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX; inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX; inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX; @@ -13130,21 +13886,33 @@ void ImGui::NavUpdateCreateMoveRequest() } } + // Prepare scoring rectangle. // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) ImRect scoring_rect; if (window != NULL) { ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0); scoring_rect = WindowRectRelToAbs(window, nav_rect_rel); - scoring_rect.TranslateY(scoring_rect_offset_y); + + if (g.NavMoveFlags & ImGuiNavMoveFlags_IsPageMove) + { + // When we start from a visible location, score visible items and prioritize this result. + if (window->InnerRect.Contains(scoring_rect)) + g.NavMoveFlags |= ImGuiNavMoveFlags_AlsoScoreVisibleSet; + g.NavScoringNoClipRect = scoring_rect; + scoring_rect.TranslateY(scoring_page_offset_y); + g.NavScoringNoClipRect.Add(scoring_rect); + } + + //GetForegroundDrawList()->AddRectFilled(scoring_rect.Min - ImVec2(1, 1), scoring_rect.Max + ImVec2(1, 1), IM_COL32(255, 100, 0, 80)); // [DEBUG] Pre-bias if (g.NavMoveSubmitted) NavBiasScoringRect(scoring_rect, window->RootWindowForNav->NavPreferredScoringPosRel[g.NavLayer], g.NavMoveDir, g.NavMoveFlags); IM_ASSERT(!scoring_rect.IsInverted()); // Ensure we have a non-inverted bounding box here will allow us to remove extraneous ImFabs() calls in NavScoreItem(). - //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG] - //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG] + //GetForegroundDrawList()->AddRectFilled(scoring_rect.Min - ImVec2(1, 1), scoring_rect.Max + ImVec2(1, 1), IM_COL32(255, 100, 0, 80)); // [DEBUG] Post-bias + //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRectFilled(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(100, 255, 0, 80)); } // [DEBUG] } g.NavScoringRect = scoring_rect; - g.NavScoringNoClipRect.Add(scoring_rect); + //g.NavScoringNoClipRect.Add(scoring_rect); } void ImGui::NavUpdateCreateTabbingRequest() @@ -13164,7 +13932,7 @@ void ImGui::NavUpdateCreateTabbingRequest() // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and without wrapping. const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; if (nav_keyboard_active) - g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavDisableHighlight == true && g.ActiveId == 0) ? 0 : +1; + g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.NavCursorVisible == false && g.ActiveId == 0) ? 0 : +1; else g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1; ImGuiNavMoveFlags move_flags = ImGuiNavMoveFlags_IsTabbing | ImGuiNavMoveFlags_Activate; @@ -13196,9 +13964,9 @@ void ImGui::NavMoveRequestApplyResult() if (result == NULL) { if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) - g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavHighlight; - if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) - NavRestoreHighlightAfterMove(); + g.NavMoveFlags |= ImGuiNavMoveFlags_NoSetNavCursorVisible; + if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0) + SetNavCursorVisibleAfterMove(); NavClearPreferredPosForAxis(axis); // On a failed move, clear preferred pos for this axis. IMGUI_DEBUG_LOG_NAV("[nav] NavMoveSubmitted but not led to a result!\n"); return; @@ -13251,7 +14019,7 @@ void ImGui::NavMoveRequestApplyResult() g.NavJustMovedToFocusScopeId = result->FocusScopeId; g.NavJustMovedToKeyMods = g.NavMoveKeyMods; g.NavJustMovedToIsTabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; - g.NavJustMovedToHasSelectionData = (result->InFlags & ImGuiItemFlags_HasSelectionUserData) != 0; + g.NavJustMovedToHasSelectionData = (result->ItemFlags & ImGuiItemFlags_HasSelectionUserData) != 0; //IMGUI_DEBUG_LOG_NAV("[nav] NavJustMovedFromFocusScopeId = 0x%08X, NavJustMovedToFocusScopeId = 0x%08X\n", g.NavJustMovedFromFocusScopeId, g.NavJustMovedToFocusScopeId); } @@ -13271,7 +14039,7 @@ void ImGui::NavMoveRequestApplyResult() } // Tabbing: Activates Inputable, otherwise only Focus - if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->InFlags & ImGuiItemFlags_Inputable) == 0) + if ((g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) && (result->ItemFlags & ImGuiItemFlags_Inputable) == 0) g.NavMoveFlags &= ~ImGuiNavMoveFlags_Activate; // Activate @@ -13279,16 +14047,18 @@ void ImGui::NavMoveRequestApplyResult() { g.NavNextActivateId = result->ID; g.NavNextActivateFlags = ImGuiActivateFlags_None; + if (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi) + g.NavNextActivateFlags |= ImGuiActivateFlags_FromFocusApi; if (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) g.NavNextActivateFlags |= ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState | ImGuiActivateFlags_FromTabbing; } - // Enable nav highlight - if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavHighlight) == 0) - NavRestoreHighlightAfterMove(); + // Make nav cursor visible + if ((g.NavMoveFlags & ImGuiNavMoveFlags_NoSetNavCursorVisible) == 0) + SetNavCursorVisibleAfterMove(); } -// Process NavCancel input (to close a popup, get back to parent, clear focus) +// Process Escape/NavCancel input (to close a popup, get back to parent, clear focus) // FIXME: In order to support e.g. Escape to clear a selection we'll need: // - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it. // - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit child window in EndChild()) or in EndFrame(), to allow an earlier intercept @@ -13309,7 +14079,7 @@ static void ImGui::NavUpdateCancelRequest() { // Leave the "menu" layer NavRestoreLayer(ImGuiNavLayer_Main); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow) { @@ -13319,7 +14089,7 @@ static void ImGui::NavUpdateCancelRequest() IM_ASSERT(child_window->ChildId != 0); FocusWindow(parent_window); SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect())); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) { @@ -13329,9 +14099,16 @@ static void ImGui::NavUpdateCancelRequest() else { // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were - if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) - g.NavWindow->NavLastIds[0] = 0; - g.NavId = 0; + // FIXME-NAV: This should happen on window appearing. + if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup)))// || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + + // Clear nav focus + if (g.IO.ConfigNavEscapeClearFocusItem || g.IO.ConfigNavEscapeClearFocusWindow) + g.NavId = 0; + if (g.IO.ConfigNavEscapeClearFocusWindow) + FocusWindow(NULL); } } @@ -13356,7 +14133,7 @@ static float ImGui::NavUpdatePageUpPageDown() if (g.NavLayer != ImGuiNavLayer_Main) NavRestoreLayer(ImGuiNavLayer_Main); - if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY) + if ((window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Main)) == 0 && window->DC.NavWindowHasScrollY) { // Fallback manual-scroll when window has no navigable item if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner)) @@ -13371,21 +14148,21 @@ static float ImGui::NavUpdatePageUpPageDown() else { ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; - const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->FontRefSize * 1.0f + nav_rect_rel.GetHeight()); float nav_scoring_rect_offset_y = 0.0f; if (IsKeyPressed(ImGuiKey_PageUp, true)) { nav_scoring_rect_offset_y = -page_offset_y; g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction (so we can always land on the last item) g.NavMoveClipDir = ImGuiDir_Up; - g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_IsPageMove; // ImGuiNavMoveFlags_AlsoScoreVisibleSet may be added later } else if (IsKeyPressed(ImGuiKey_PageDown, true)) { nav_scoring_rect_offset_y = +page_offset_y; g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so we can always land on the last item) g.NavMoveClipDir = ImGuiDir_Down; - g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet | ImGuiNavMoveFlags_IsPageMove; + g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_IsPageMove; // ImGuiNavMoveFlags_AlsoScoreVisibleSet may be added later } else if (home_pressed) { @@ -13417,7 +14194,7 @@ static void ImGui::NavEndFrame() { ImGuiContext& g = *GImGui; - // Show CTRL+TAB list window + // Show Ctrl+Tab list window if (g.NavWindowingTarget != NULL) NavUpdateWindowingOverlay(); @@ -13487,14 +14264,12 @@ static void ImGui::NavUpdateCreateWrappingRequest() NavMoveRequestForward(g.NavMoveDir, clip_dir, move_flags, g.NavMoveScrollFlags); } -static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) +// Can we focus this window with Ctrl+Tab (or PadMenu + PadFocusPrev/PadFocusNext) +// Note that NoNavFocus makes the window not reachable with Ctrl+Tab but it can still be focused with mouse or programmatically. +// If you want a window to never be focused, you may use the e.g. NoInputs flag. +bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) { - ImGuiContext& g = *GImGui; - IM_UNUSED(g); - int order = window->FocusOrder; - IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking) - IM_ASSERT(g.WindowsFocusOrder[order] == window); - return order; + return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); } static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) @@ -13506,7 +14281,7 @@ static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // return NULL; } -static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +static void NavUpdateWindowingTarget(int focus_change_dir) { ImGuiContext& g = *GImGui; IM_ASSERT(g.NavWindowingTarget); @@ -13525,8 +14300,36 @@ static void NavUpdateWindowingHighlightWindow(int focus_change_dir) g.NavWindowingToggleLayer = false; } +// Apply focus and close overlay +static void ImGui::NavUpdateWindowingApplyFocus(ImGuiWindow* apply_focus_window) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow) + { + ClearActiveID(); + SetNavCursorVisibleAfterMove(); + ClosePopupsOverWindow(apply_focus_window, false); + FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild); + IM_ASSERT(g.NavWindow != NULL); + apply_focus_window = g.NavWindow; + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window has ONLY a menu layer (no main layer), select it directly + // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame, + // so Ctrl+Tab where the keys are only held for 1 frame will be able to use correct layers mask since + // the target window as already been previewed once. + // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases, + // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask* + // won't be valid. + if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) + g.NavLayer = ImGuiNavLayer_Menu; + } + g.NavWindowingTarget = NULL; +} + // Windowing management mode -// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer) +// Keyboard: Ctrl+Tab (change focus/move/resize), Alt (toggle menu layer) // Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer) static void ImGui::NavUpdateWindowing() { @@ -13537,7 +14340,7 @@ static void ImGui::NavUpdateWindowing() bool apply_toggle_layer = false; ImGuiWindow* modal_window = GetTopMostPopupModal(); - bool allow_windowing = (modal_window == NULL); // FIXME: This prevent CTRL+TAB from being usable with windows that are inside the Begin-stack of that modal. + bool allow_windowing = (modal_window == NULL); // FIXME: This prevent Ctrl+Tab from being usable with windows that are inside the Begin-stack of that modal. if (!allow_windowing) g.NavWindowingTarget = NULL; @@ -13549,23 +14352,33 @@ static void ImGui::NavUpdateWindowing() g.NavWindowingTargetAnim = NULL; } - // Start CTRL+Tab or Square+L/R window selection + // Start Ctrl+Tab or Square+L/R window selection // (g.ConfigNavWindowingKeyNext/g.ConfigNavWindowingKeyPrev defaults are ImGuiMod_Ctrl|ImGuiKey_Tab and ImGuiMod_Ctrl|ImGuiMod_Shift|ImGuiKey_Tab) - const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing"); + const ImGuiID owner_id = ImHashStr("##NavUpdateWindowing"); const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id); - const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None); + const bool start_toggling_with_gamepad = nav_gamepad_active && !g.NavWindowingTarget && Shortcut(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_RouteAlways, owner_id); + const bool start_windowing_with_gamepad = allow_windowing && start_toggling_with_gamepad; const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard! + bool just_started_windowing_from_null_focus = false; + if (start_toggling_with_gamepad) + { + g.NavWindowingToggleLayer = true; // Gamepad starts toggling layer + g.NavWindowingToggleKey = ImGuiKey_NavGamepadMenu; + g.NavWindowingInputSource = g.NavInputSource = ImGuiInputSource_Gamepad; + } if (start_windowing_with_gamepad || start_windowing_with_keyboard) - if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) + if (ImGuiWindow* window = (g.NavWindow && IsWindowNavFocusable(g.NavWindow)) ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1)) { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; + if (start_windowing_with_keyboard || g.ConfigNavWindowingWithGamepad) + g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow; // Current location g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f); - g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer - g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; + g.NavWindowingInputSource = g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad; + if (g.NavWindow == NULL) + just_started_windowing_from_null_focus = true; // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects. if (keyboard_next_window || keyboard_prev_window) @@ -13573,18 +14386,22 @@ static void ImGui::NavUpdateWindowing() } // Gamepad update - g.NavWindowingTimer += io.DeltaTime; - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad) + if ((g.NavWindowingTarget || g.NavWindowingToggleLayer) && g.NavWindowingInputSource == ImGuiInputSource_Gamepad) { - // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); - - // Select window to focus - const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1); - if (focus_change_dir != 0) + if (g.NavWindowingTarget != NULL) { - NavUpdateWindowingHighlightWindow(focus_change_dir); - g.NavWindowingHighlightAlpha = 1.0f; + // Highlight only appears after a brief time holding the button, so that a fast tap on ImGuiKey_NavGamepadMenu (to toggle NavLayer) doesn't add visual noise + // However inputs are accepted immediately, so you press ImGuiKey_NavGamepadMenu + L1/R1 fast. + g.NavWindowingTimer += io.DeltaTime; + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsKeyPressed(ImGuiKey_GamepadL1) - (int)IsKeyPressed(ImGuiKey_GamepadR1); + if (focus_change_dir != 0 && !just_started_windowing_from_null_focus) + { + NavUpdateWindowingTarget(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } } // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most) @@ -13596,35 +14413,38 @@ static void ImGui::NavUpdateWindowing() else if (!g.NavWindowingToggleLayer) apply_focus_window = g.NavWindowingTarget; g.NavWindowingTarget = NULL; + g.NavWindowingToggleLayer = false; } } // Keyboard: Focus - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard) + if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_Keyboard) { - // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast Ctrl+Tab doesn't add visual noise ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_; IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows. + g.NavWindowingTimer += io.DeltaTime; g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (keyboard_next_window || keyboard_prev_window) - NavUpdateWindowingHighlightWindow(keyboard_next_window ? -1 : +1); + if ((keyboard_next_window || keyboard_prev_window) && !just_started_windowing_from_null_focus) + NavUpdateWindowingTarget(keyboard_next_window ? -1 : +1); else if ((io.KeyMods & shared_mods) != shared_mods) apply_focus_window = g.NavWindowingTarget; } - // Keyboard: Press and Release ALT to toggle menu layer + // Keyboard: Press and Release Alt to toggle menu layer const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; bool windowing_toggle_layer_start = false; - for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) - if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) - { - windowing_toggle_layer_start = true; - g.NavWindowingToggleLayer = true; - g.NavWindowingToggleKey = windowing_toggle_key; - g.NavInputSource = ImGuiInputSource_Keyboard; - break; - } - if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard) + if (g.NavWindow != NULL && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) + if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner)) + { + windowing_toggle_layer_start = true; + g.NavWindowingToggleLayer = true; + g.NavWindowingToggleKey = windowing_toggle_key; + g.NavWindowingInputSource = g.NavInputSource = ImGuiInputSource_Keyboard; + break; + } + if (g.NavWindowingToggleLayer && g.NavWindowingInputSource == ImGuiInputSource_Keyboard) { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) // We cancel toggling nav layer when other modifiers are pressed. (See #4439) @@ -13660,7 +14480,7 @@ static void ImGui::NavUpdateWindowing() const float NAV_MOVE_SPEED = 800.0f; const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); g.NavWindowingAccumDeltaPos += nav_move_dir * move_step; - g.NavDisableMouseHover = true; + g.NavHighlightItemUnderNav = true; ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos); if (accum_floored.x != 0.0f || accum_floored.y != 0.0f) { @@ -13672,28 +14492,8 @@ static void ImGui::NavUpdateWindowing() } // Apply final focus - if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) - { - ClearActiveID(); - NavRestoreHighlightAfterMove(); - ClosePopupsOverWindow(apply_focus_window, false); - FocusWindow(apply_focus_window, ImGuiFocusRequestFlags_RestoreFocusedChild); - apply_focus_window = g.NavWindow; - if (apply_focus_window->NavLastIds[0] == 0) - NavInitWindow(apply_focus_window, false); - - // If the window has ONLY a menu layer (no main layer), select it directly - // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame, - // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since - // the target window as already been previewed once. - // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases, - // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask* - // won't be valid. - if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu)) - g.NavLayer = ImGuiNavLayer_Menu; - } if (apply_focus_window) - g.NavWindowingTarget = NULL; + NavUpdateWindowingApplyFocus(apply_focus_window); // Apply menu/layer toggle if (apply_toggle_layer && g.NavWindow) @@ -13722,7 +14522,7 @@ static void ImGui::NavUpdateWindowing() if (new_nav_layer == ImGuiNavLayer_Menu) g.NavWindow->NavLastIds[new_nav_layer] = 0; NavRestoreLayer(new_nav_layer); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } } } @@ -13737,7 +14537,7 @@ static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled); } -// Overlay displayed when using CTRL+TAB. Called by EndFrame(). +// Overlay displayed when using Ctrl+Tab. Called by EndFrame(). void ImGui::NavUpdateWindowingOverlay() { ImGuiContext& g = *GImGui; @@ -13747,12 +14547,12 @@ void ImGui::NavUpdateWindowingOverlay() return; if (g.NavWindowingListWindow == NULL) - g.NavWindowingListWindow = FindWindowByName("###NavWindowingList"); + g.NavWindowingListWindow = FindWindowByName("##NavWindowingOverlay"); const ImGuiViewport* viewport = GetMainViewport(); SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); - Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + Begin("##NavWindowingOverlay", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); if (g.ContextName[0] != 0) SeparatorText(g.ContextName); for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) @@ -13770,7 +14570,6 @@ void ImGui::NavUpdateWindowingOverlay() PopStyleVar(); } - //----------------------------------------------------------------------------- // [SECTION] DRAG AND DROP //----------------------------------------------------------------------------- @@ -13788,7 +14587,7 @@ void ImGui::ClearDragDrop() IMGUI_DEBUG_LOG_ACTIVEID("[dragdrop] ClearDragDrop()\n"); g.DragDropActive = false; g.DragDropPayload.Clear(); - g.DragDropAcceptFlags = ImGuiDragDropFlags_None; + g.DragDropAcceptFlagsCurr = ImGuiDragDropFlags_None; g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; g.DragDropAcceptIdCurrRectSurface = FLT_MAX; g.DragDropAcceptFrameCount = -1; @@ -13861,7 +14660,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) // Rely on keeping other window->LastItemXXX fields intact. source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect); KeepAliveID(source_id); - bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.InFlags); + bool is_hovered = ItemHoverable(g.LastItemData.Rect, source_id, g.LastItemData.ItemFlags); if (is_hovered && g.IO.MouseClicked[mouse_button]) { SetActiveID(source_id, window); @@ -13917,11 +14716,11 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. bool ret; - if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlagsPrev & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) ret = BeginTooltipHidden(); else ret = BeginTooltip(); - IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddendAndSkipItemsForCurrentFrame(). + IM_ASSERT(ret); // FIXME-NEWBEGIN: If this ever becomes false, we need to Begin("##Hidden", NULL, ImGuiWindowFlags_NoSavedSettings) + SetWindowHiddenAndSkipItemsForCurrentFrame(). IM_UNUSED(ret); } @@ -13955,7 +14754,7 @@ bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_s cond = ImGuiCond_Always; IM_ASSERT(type != NULL); - IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); + IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() @@ -14011,6 +14810,31 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) g.DragDropTargetRect = bb; g.DragDropTargetClipRect = window->ClipRect; // May want to be overridden by user depending on use case? g.DragDropTargetId = id; + g.DragDropTargetFullViewport = 0; + g.DragDropWithinTarget = true; + return true; +} + +// Typical usage would be: +// if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) +// if (ImGui::BeginDragDropTargetViewport(ImGui::GetMainViewport(), NULL)) +// But we are leaving the hover test to the caller for maximum flexibility. +bool ImGui::BeginDragDropTargetViewport(ImGuiViewport* viewport, const ImRect* p_bb) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImRect bb = p_bb ? *p_bb : ((ImGuiViewportP*)viewport)->GetWorkRect(); + ImGuiID id = viewport->ID; + if (!IsMouseHoveringRect(bb.Min, bb.Max, false) || (id == g.DragDropPayload.SourceId)) + return false; + + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() + g.DragDropTargetRect = bb; + g.DragDropTargetClipRect = bb; + g.DragDropTargetId = id; + g.DragDropTargetFullViewport = id; g.DragDropWithinTarget = true; return true; } @@ -14073,7 +14897,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop if (r_surface > g.DragDropAcceptIdCurrRectSurface) return NULL; - g.DragDropAcceptFlags = flags; + g.DragDropAcceptFlagsCurr = flags; g.DragDropAcceptIdCurr = g.DragDropTargetId; g.DragDropAcceptIdCurrRectSurface = r_surface; //IMGUI_DEBUG_LOG("AcceptDragDropPayload(): %08X: accept\n", g.DragDropTargetId); @@ -14081,8 +14905,17 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop // Render default drop visuals payload.Preview = was_accepted_previously; flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame) - if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - RenderDragDropTargetRect(r, g.DragDropTargetClipRect); + const bool draw_target_rect = payload.Preview && !(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); + if (draw_target_rect && g.DragDropTargetFullViewport != 0) + { + ImRect bb = g.DragDropTargetRect; + bb.Expand(-3.5f); + RenderDragDropTargetRectEx(GetForegroundDrawList(), bb); + } + else if (draw_target_rect) + { + RenderDragDropTargetRectForItem(r); + } g.DragDropAcceptFrameCount = g.FrameCount; if ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) && g.DragDropMouseButton == -1) @@ -14098,21 +14931,28 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDrop } // FIXME-STYLE FIXME-DRAGDROP: Settle on a proper default visuals for drop target. -void ImGui::RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect) +void ImGui::RenderDragDropTargetRectForItem(const ImRect& bb) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImRect bb_display = bb; - bb_display.ClipWith(item_clip_rect); // Clip THEN expand so we have a way to visualize that target is not entirely visible. - bb_display.Expand(3.5f); + bb_display.ClipWith(g.DragDropTargetClipRect); // Clip THEN expand so we have a way to visualize that target is not entirely visible. + bb_display.Expand(g.Style.DragDropTargetPadding); bool push_clip_rect = !window->ClipRect.Contains(bb_display); if (push_clip_rect) window->DrawList->PushClipRectFullScreen(); - window->DrawList->AddRect(bb_display.Min, bb_display.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f); + RenderDragDropTargetRectEx(window->DrawList, bb_display); if (push_clip_rect) window->DrawList->PopClipRect(); } +void ImGui::RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb) +{ + ImGuiContext& g = *GImGui; + draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTargetBg), g.Style.DragDropTargetRounding, 0); + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_DragDropTarget), g.Style.DragDropTargetRounding, 0, g.Style.DragDropTargetBorderSize); +} + const ImGuiPayload* ImGui::GetDragDropPayload() { ImGuiContext& g = *GImGui; @@ -14199,7 +15039,7 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* } if (prefix) - LogRenderedText(ref_pos, prefix, prefix + strlen(prefix)); // Calculate end ourself to ensure "##" are included here. + LogRenderedText(ref_pos, prefix, prefix + ImStrlen(prefix)); // Calculate end ourself to ensure "##" are included here. // Re-adjust padding if we have popped out of our starting depth if (g.LogDepthRef > window->DC.TreeDepth) @@ -14232,19 +15072,21 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* } if (suffix) - LogRenderedText(ref_pos, suffix, suffix + strlen(suffix)); + LogRenderedText(ref_pos, suffix, suffix + ImStrlen(suffix)); } // Start logging/capturing text output -void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth) +void ImGui::LogBegin(ImGuiLogFlags flags, int auto_open_depth) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(g.LogEnabled == false); - IM_ASSERT(g.LogFile == NULL); - IM_ASSERT(g.LogBuffer.empty()); + IM_ASSERT(g.LogFile == NULL && g.LogBuffer.empty()); + IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiLogFlags_OutputMask_)); // Check that only 1 type flag is used + g.LogEnabled = g.ItemUnclipByLog = true; - g.LogType = type; + g.LogFlags = flags; + g.LogWindow = window; g.LogNextPrefix = g.LogNextSuffix = NULL; g.LogDepthRef = window->DC.TreeDepth; g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault); @@ -14267,7 +15109,7 @@ void ImGui::LogToTTY(int auto_open_depth) return; IM_UNUSED(auto_open_depth); #ifndef IMGUI_DISABLE_TTY_FUNCTIONS - LogBegin(ImGuiLogType_TTY, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputTTY, auto_open_depth); g.LogFile = stdout; #endif } @@ -14293,7 +15135,7 @@ void ImGui::LogToFile(int auto_open_depth, const char* filename) return; } - LogBegin(ImGuiLogType_File, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputFile, auto_open_depth); g.LogFile = f; } @@ -14303,7 +15145,7 @@ void ImGui::LogToClipboard(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Clipboard, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputClipboard, auto_open_depth); } void ImGui::LogToBuffer(int auto_open_depth) @@ -14311,7 +15153,7 @@ void ImGui::LogToBuffer(int auto_open_depth) ImGuiContext& g = *GImGui; if (g.LogEnabled) return; - LogBegin(ImGuiLogType_Buffer, auto_open_depth); + LogBegin(ImGuiLogFlags_OutputBuffer, auto_open_depth); } void ImGui::LogFinish() @@ -14321,29 +15163,29 @@ void ImGui::LogFinish() return; LogText(IM_NEWLINE); - switch (g.LogType) + switch (g.LogFlags & ImGuiLogFlags_OutputMask_) { - case ImGuiLogType_TTY: + case ImGuiLogFlags_OutputTTY: #ifndef IMGUI_DISABLE_TTY_FUNCTIONS fflush(g.LogFile); #endif break; - case ImGuiLogType_File: + case ImGuiLogFlags_OutputFile: ImFileClose(g.LogFile); break; - case ImGuiLogType_Buffer: + case ImGuiLogFlags_OutputBuffer: break; - case ImGuiLogType_Clipboard: + case ImGuiLogFlags_OutputClipboard: if (!g.LogBuffer.empty()) SetClipboardText(g.LogBuffer.begin()); break; - case ImGuiLogType_None: + default: IM_ASSERT(0); break; } g.LogEnabled = g.ItemUnclipByLog = false; - g.LogType = ImGuiLogType_None; + g.LogFlags = ImGuiLogFlags_None; g.LogFile = NULL; g.LogBuffer.clear(); } @@ -14377,7 +15219,6 @@ void ImGui::LogButtons() LogToClipboard(); } - //----------------------------------------------------------------------------- // [SECTION] SETTINGS //----------------------------------------------------------------------------- @@ -14497,7 +15338,7 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter). // For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy.. if (ini_size == 0) - ini_size = strlen(ini_data); + ini_size = ImStrlen(ini_data); g.SettingsIniData.Buf.resize((int)ini_size + 1); char* const buf = g.SettingsIniData.Buf.Data; char* const buf_end = buf + ini_size; @@ -14591,14 +15432,10 @@ ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) { ImGuiContext& g = *GImGui; + // Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier. if (g.IO.ConfigDebugIniSettings == false) - { - // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() - // Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier. - if (const char* p = strstr(name, "###")) - name = p; - } - const size_t name_len = strlen(name); + name = ImHashSkipUncontributingPrefix(name); + const size_t name_len = ImStrlen(name); // Allocate chunk const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1; @@ -14737,7 +15574,6 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl } } - //----------------------------------------------------------------------------- // [SECTION] LOCALIZATION //----------------------------------------------------------------------------- @@ -14749,16 +15585,33 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count) g.LocalizationTable[entries[n].Key] = entries[n].Text; } - //----------------------------------------------------------------------------- // [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- // - GetMainViewport() // - SetWindowViewport() [Internal] +// - ScaleWindowsInViewport() [Internal] // - UpdateViewportsNewFrame() [Internal] // (this section is more complete in the 'docking' branch) //----------------------------------------------------------------------------- +void ImGuiPlatformIO::ClearPlatformHandlers() +{ + Platform_GetClipboardTextFn = NULL; + Platform_SetClipboardTextFn = NULL; + Platform_ClipboardUserData = NULL; + Platform_OpenInShellFn = NULL; + Platform_OpenInShellUserData = NULL; + Platform_SetImeDataFn = NULL; + Platform_ImeUserData = NULL; +} + +void ImGuiPlatformIO::ClearRendererHandlers() +{ + Renderer_TextureMaxWidth = Renderer_TextureMaxHeight = 0; + Renderer_RenderState = NULL; +} + ImGuiViewport* ImGui::GetMainViewport() { ImGuiContext& g = *GImGui; @@ -14770,6 +15623,24 @@ void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport) window->Viewport = viewport; } +static void ScaleWindow(ImGuiWindow* window, float scale) +{ + ImVec2 origin = window->Viewport->Pos; + window->Pos = ImFloor((window->Pos - origin) * scale + origin); + window->Size = ImTrunc(window->Size * scale); + window->SizeFull = ImTrunc(window->SizeFull * scale); + window->ContentSize = ImTrunc(window->ContentSize * scale); +} + +// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) +void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) +{ + ImGuiContext& g = *GImGui; + for (ImGuiWindow* window : g.Windows) + if (window->Viewport == viewport) + ScaleWindow(window, scale); +} + // Update viewports and monitor infos static void ImGui::UpdateViewportsNewFrame() { @@ -14782,6 +15653,8 @@ static void ImGui::UpdateViewportsNewFrame() main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp; main_viewport->Pos = ImVec2(0.0f, 0.0f); main_viewport->Size = g.IO.DisplaySize; + main_viewport->FramebufferScale = g.IO.DisplayFramebufferScale; + IM_ASSERT(main_viewport->FramebufferScale.x > 0.0f && main_viewport->FramebufferScale.y > 0.0f); for (ImGuiViewportP* viewport : g.Viewports) { @@ -14873,7 +15746,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext*, const char* t if (!main_clipboard) PasteboardCreate(kPasteboardClipboard, &main_clipboard); PasteboardClear(main_clipboard); - CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text)); + CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, ImStrlen(text)); if (cf_data) { PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0); @@ -14927,7 +15800,7 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha { ImGuiContext& g = *ctx; g.ClipboardHandlerData.clear(); - const char* text_end = text + strlen(text); + const char* text_end = text + ImStrlen(text); g.ClipboardHandlerData.resize((int)(text_end - text) + 1); memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text)); g.ClipboardHandlerData[(int)(text_end - text)] = 0; @@ -14941,11 +15814,13 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha #if defined(__APPLE__) && TARGET_OS_IPHONE #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif - -#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#if defined(__3DS__) #define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif +#if defined(_WIN32) && defined(IMGUI_DISABLE_WIN32_FUNCTIONS) +#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #endif +#endif // #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #ifndef IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS #ifdef _WIN32 @@ -14955,7 +15830,11 @@ static void Platform_SetClipboardTextFn_DefaultImpl(ImGuiContext* ctx, const cha #endif static bool Platform_OpenInShellFn_DefaultImpl(ImGuiContext*, const char* path) { - return (INT_PTR)::ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT) > 32; + const int path_wsize = ::MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0); + ImVector path_wbuf; + path_wbuf.resize(path_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, path, -1, path_wbuf.Data, path_wsize); + return (INT_PTR)::ShellExecuteW(NULL, L"open", path_wbuf.Data, NULL, NULL, SW_SHOWDEFAULT) > 32; } #else #include @@ -15030,11 +15909,16 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- +// - MetricsHelpMarker() [Internal] // - DebugRenderViewportThumbnail() [Internal] // - RenderViewportsThumbnails() [Internal] +// - DebugRenderKeyboardPreview() [Internal] // - DebugTextEncoding() -// - MetricsHelpMarker() [Internal] -// - ShowFontAtlas() [Internal] +// - DebugFlashStyleColorStop() [Internal] +// - DebugFlashStyleColor() +// - UpdateDebugToolFlashStyleColor() [Internal] +// - ShowFontAtlas() [Internal but called by Demo!] +// - DebugNodeTexture() [Internal] // - ShowMetricsWindow() // - DebugNodeColumns() [Internal] // - DebugNodeDrawList() [Internal] @@ -15048,8 +15932,24 @@ static void Platform_SetImeDataFn_DefaultImpl(ImGuiContext*, ImGuiViewport*, ImG // - DebugNodeWindowSettings() [Internal] // - DebugNodeWindowsList() [Internal] // - DebugNodeWindowsListByBeginStackParent() [Internal] +// - ShowFontSelector() //----------------------------------------------------------------------------- +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. +static void MetricsHelpMarker(const char* desc) +{ + ImGui::TextDisabled("(?)"); + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} +#endif + #ifndef IMGUI_DISABLE_DEBUG_TOOLS void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb) @@ -15163,10 +16063,11 @@ void ImGui::DebugTextEncoding(const char* str) TableSetupColumn("Glyph"); TableSetupColumn("Codepoint"); TableHeadersRow(); + const char* str_end = str + strlen(str); // As we may receive malformed UTF-8, pass an explicit end instead of relying on ImTextCharFromUtf8() assuming enough space. for (const char* p = str; *p != 0; ) { unsigned int c; - const int c_utf8_len = ImTextCharFromUtf8(&c, p, NULL); + const int c_utf8_len = ImTextCharFromUtf8(&c, p, str_end); TableNextColumn(); Text("%d", (int)(p - str)); TableNextColumn(); @@ -15177,10 +16078,12 @@ void ImGui::DebugTextEncoding(const char* str) Text("0x%02X", (int)(unsigned char)p[byte_index]); } TableNextColumn(); - if (GetFont()->FindGlyphNoFallback((ImWchar)c)) - TextUnformatted(p, p + c_utf8_len); - else - TextUnformatted((c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]"); + TextUnformatted(p, p + c_utf8_len); + if (!GetFont()->IsGlyphInFont((ImWchar)c)) + { + SameLine(); + TextUnformatted("[missing]"); + } TableNextColumn(); Text("U+%04X", (int)c); p += c_utf8_len; @@ -15217,38 +16120,214 @@ void ImGui::UpdateDebugToolFlashStyleColor() DebugFlashStyleColorStop(); } -// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds. -static void MetricsHelpMarker(const char* desc) +static const char* FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) +{ + union { void* ptr; int integer; } tex_id_opaque; + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); + if (sizeof(tex_id) >= sizeof(void*)) + ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); + else + ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); + return buf; +} + +static const char* FormatTextureRefForDebugDisplay(char* buf, int buf_size, ImTextureRef tex_ref) { - ImGui::TextDisabled("(?)"); - if (ImGui::BeginItemTooltip()) - { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted(desc); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } + char* buf_end = buf + buf_size; + if (tex_ref._TexData != NULL) + buf += ImFormatString(buf, buf_end - buf, "#%03d: ", tex_ref._TexData->UniqueID); + return FormatTextureIDForDebugDisplay(buf, (int)(buf_end - buf), tex_ref.GetTexID()); // Calling TexRef::GetTexID() to avoid assert of cmd->GetTexID() } +#ifdef IMGUI_ENABLE_FREETYPE +namespace ImGuiFreeType { IMGUI_API const ImFontLoader* GetFontLoader(); IMGUI_API bool DebugEditFontLoaderFlags(unsigned int* p_font_builder_flags); } +#endif + // [DEBUG] List fonts in a font atlas and display its texture void ImGui::ShowFontAtlas(ImFontAtlas* atlas) { + ImGuiContext& g = *GImGui; + ImGuiIO& io = g.IO; + ImGuiStyle& style = g.Style; + + BeginDisabled(); + CheckboxFlags("io.BackendFlags: RendererHasTextures", &io.BackendFlags, ImGuiBackendFlags_RendererHasTextures); + EndDisabled(); + ShowFontSelector("Font"); + //BeginDisabled((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0); + if (DragFloat("FontSizeBase", &style.FontSizeBase, 0.20f, 5.0f, 100.0f, "%.0f")) + style._NextFrameFontSizeBase = style.FontSizeBase; // FIXME: Temporary hack until we finish remaining work. + SameLine(0.0f, 0.0f); Text(" (out %.2f)", GetFontSize()); + SameLine(); MetricsHelpMarker("- This is scaling font only. General scaling will come later."); + DragFloat("FontScaleMain", &style.FontScaleMain, 0.02f, 0.5f, 4.0f); + //BeginDisabled(io.ConfigDpiScaleFonts); + DragFloat("FontScaleDpi", &style.FontScaleDpi, 0.02f, 0.5f, 4.0f); + //SetItemTooltip("When io.ConfigDpiScaleFonts is set, this value is automatically overwritten."); + //EndDisabled(); + if ((io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + { + BulletText("Warning: Font scaling will NOT be smooth, because\nImGuiBackendFlags_RendererHasTextures is not set!"); + BulletText("For instructions, see:"); + SameLine(); + TextLinkOpenURL("docs/BACKENDS.md", "https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md"); + } + BulletText("Load a nice font for better results!"); + BulletText("Please submit feedback:"); + SameLine(); TextLinkOpenURL("#8465", "https://github.com/ocornut/imgui/issues/8465"); + BulletText("Read FAQ for more details:"); + SameLine(); TextLinkOpenURL("dearimgui.com/faq", "https://www.dearimgui.com/faq/"); + //EndDisabled(); + + SeparatorText("Font List"); + + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + Checkbox("Show font preview", &cfg->ShowFontPreview); + + // Font loaders + if (TreeNode("Loader", "Loader: \'%s\'", atlas->FontLoaderName ? atlas->FontLoaderName : "NULL")) + { + const ImFontLoader* loader_current = atlas->FontLoader; + BeginDisabled(!atlas->RendererHasTextures); +#ifdef IMGUI_ENABLE_STB_TRUETYPE + const ImFontLoader* loader_stbtruetype = ImFontAtlasGetFontLoaderForStbTruetype(); + if (RadioButton("stb_truetype", loader_current == loader_stbtruetype)) + atlas->SetFontLoader(loader_stbtruetype); +#else + BeginDisabled(); + RadioButton("stb_truetype", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_STB_TRUETYPE"); + EndDisabled(); +#endif + SameLine(); +#ifdef IMGUI_ENABLE_FREETYPE + const ImFontLoader* loader_freetype = ImGuiFreeType::GetFontLoader(); + if (RadioButton("FreeType", loader_current == loader_freetype)) + atlas->SetFontLoader(loader_freetype); + if (loader_current == loader_freetype) + { + unsigned int loader_flags = atlas->FontLoaderFlags; + Text("Shared FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) + { + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + atlas->FontLoaderFlags = loader_flags; + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + } + } +#else + BeginDisabled(); + RadioButton("FreeType", false); + SetItemTooltip("Requires #define IMGUI_ENABLE_FREETYPE + imgui_freetype.cpp."); + EndDisabled(); +#endif + EndDisabled(); + TreePop(); + } + + // Font list for (ImFont* font : atlas->Fonts) { PushID(font); DebugNodeFont(font); PopID(); } - if (TreeNode("Font Atlas", "Font Atlas (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) + + SeparatorText("Font Atlas"); + if (Button("Compact")) + atlas->CompactCache(); + SameLine(); + if (Button("Grow")) + ImFontAtlasTextureGrow(atlas); + SameLine(); + if (Button("Clear All")) + ImFontAtlasBuildClear(atlas); + SetItemTooltip("Destroy cache and custom rectangles."); + + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + if (tex_n > 0) + SameLine(); + Text("Tex: %dx%d", tex->Width, tex->Height); + } + const int packed_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsPackedSurface); + const int discarded_surface_sqrt = (int)sqrtf((float)atlas->Builder->RectsDiscardedSurface); + Text("Packed rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsPackedCount, atlas->Builder->RectsPackedSurface, packed_surface_sqrt, packed_surface_sqrt); + Text("incl. Discarded rects: %d, area: about %d px ~%dx%d px", atlas->Builder->RectsDiscardedCount, atlas->Builder->RectsDiscardedSurface, discarded_surface_sqrt, discarded_surface_sqrt); + + ImFontAtlasRectId highlight_r_id = ImFontAtlasRectId_Invalid; + if (TreeNode("Rects Index", "Rects Index (%d)", atlas->Builder->RectsPackedCount)) // <-- Use count of used rectangles + { + PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0f); + if (BeginTable("##table", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY, ImVec2(0.0f, GetTextLineHeightWithSpacing() * 12))) + { + for (const ImFontAtlasRectEntry& entry : atlas->Builder->RectsIndex) + if (entry.IsUsed) + { + ImFontAtlasRectId id = ImFontAtlasRectId_Make(atlas->Builder->RectsIndex.index_from_ptr(&entry), entry.Generation); + ImFontAtlasRect r = {}; + atlas->GetCustomRect(id, &r); + const char* buf; + ImFormatStringToTempBuffer(&buf, NULL, "ID:%08X, used:%d, { w:%3d, h:%3d } { x:%4d, y:%4d }", id, entry.IsUsed, r.w, r.h, r.x, r.y); + TableNextColumn(); + Selectable(buf); + if (IsItemHovered()) + highlight_r_id = id; + TableNextColumn(); + Image(atlas->TexRef, ImVec2(r.w, r.h), r.uv0, r.uv1); + } + EndTable(); + } + PopStyleVar(); + TreePop(); + } + + // Texture list + // (ensure the last texture always use the same ID, so we can keep it open neatly) + ImFontAtlasRect highlight_r; + if (highlight_r_id != ImFontAtlasRectId_Invalid) + atlas->GetCustomRect(highlight_r_id, &highlight_r); + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + if (tex_n == atlas->TexList.Size - 1) + SetNextItemOpen(true, ImGuiCond_Once); + DebugNodeTexture(atlas->TexList[tex_n], atlas->TexList.Size - 1 - tex_n, (highlight_r_id != ImFontAtlasRectId_Invalid) ? &highlight_r : NULL); + } +} + +void ImGui::DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect) +{ + ImGuiContext& g = *GImGui; + PushID(int_id); + if (TreeNode("", "Texture #%03d (%dx%d pixels)", tex->UniqueID, tex->Width, tex->Height)) { - ImGuiContext& g = *GImGui; ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; - Checkbox("Tint with Text Color", &cfg->ShowAtlasTintedWithTextColor); // Using text color ensure visibility of core atlas data, but will alter custom colored icons - ImVec4 tint_col = cfg->ShowAtlasTintedWithTextColor ? GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); - ImVec4 border_col = GetStyleColorVec4(ImGuiCol_Border); - Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), tint_col, border_col); + Checkbox("Show used rect", &cfg->ShowTextureUsedRect); + PushStyleVar(ImGuiStyleVar_ImageBorderSize, ImMax(1.0f, g.Style.ImageBorderSize)); + ImVec2 p = GetCursorScreenPos(); + if (tex->WantDestroyNextFrame) + Dummy(ImVec2((float)tex->Width, (float)tex->Height)); + else + ImageWithBg(tex->GetTexRef(), ImVec2((float)tex->Width, (float)tex->Height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); + if (cfg->ShowTextureUsedRect) + GetWindowDrawList()->AddRect(ImVec2(p.x + tex->UsedRect.x, p.y + tex->UsedRect.y), ImVec2(p.x + tex->UsedRect.x + tex->UsedRect.w, p.y + tex->UsedRect.y + tex->UsedRect.h), IM_COL32(255, 0, 255, 255)); + if (highlight_rect != NULL) + { + ImRect r_outer(p.x, p.y, p.x + tex->Width, p.y + tex->Height); + ImRect r_inner(p.x + highlight_rect->x, p.y + highlight_rect->y, p.x + highlight_rect->x + highlight_rect->w, p.y + highlight_rect->y + highlight_rect->h); + RenderRectFilledWithHole(GetWindowDrawList(), r_outer, r_inner, IM_COL32(0, 0, 0, 100), 0.0f); + GetWindowDrawList()->AddRect(r_inner.Min - ImVec2(1, 1), r_inner.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255)); + } + PopStyleVar(); + + char texid_desc[30]; + Text("Status = %s (%d), Format = %s (%d), UseColors = %d", ImTextureDataGetStatusName(tex->Status), tex->Status, ImTextureDataGetFormatName(tex->Format), tex->Format, tex->UseColors); + Text("TexID = %s, BackendUserData = %p", FormatTextureRefForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), tex->GetTexRef()), tex->BackendUserData); TreePop(); } + PopID(); } void ImGui::ShowMetricsWindow(bool* p_open) @@ -15331,6 +16410,10 @@ void ImGui::ShowMetricsWindow(bool* p_open) } }; +#ifdef IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS + TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS is enabled.\nMust disable after use! Otherwise Dear ImGui will run slower.\n"); +#endif + // Tools if (TreeNode("Tools")) { @@ -15384,7 +16467,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min - ImVec2(1, 1), table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); Indent(); char buf[128]; for (int rect_n = 0; rect_n < TRT_Count; rect_n++) @@ -15399,7 +16482,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n, trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); } } else @@ -15408,7 +16491,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), trt_rects_names[rect_n]); Selectable(buf); if (IsItemHovered()) - GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f); } } Unindent(); @@ -15490,6 +16573,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for Fonts + for (ImFontAtlas* atlas : g.FontAtlases) + if (TreeNode((void*)atlas, "Fonts (%d), Textures (%d)", atlas->Fonts.Size, atlas->TexList.Size)) + { + ShowFontAtlas(atlas); + TreePop(); + } + // Details for Popups if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) { @@ -15526,14 +16617,6 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } - // Details for Fonts - ImFontAtlas* atlas = g.IO.Fonts; - if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size)) - { - ShowFontAtlas(atlas); - TreePop(); - } - // Details for InputText if (TreeNode("InputText")) { @@ -15641,19 +16724,12 @@ void ImGui::ShowMetricsWindow(bool* p_open) { Text("KEYBOARD/GAMEPAD/MOUSE KEYS"); { - // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends. // User code should never have to go through such hoops! You can generally iterate between ImGuiKey_NamedKey_BEGIN and ImGuiKey_NamedKey_END. Indent(); -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } }; -#else - struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key >= 0 && key < 512 && GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array - //Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); Text("\"%s\" %d", GetKeyName(key), key); } } -#endif - Text("Keys down:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } - Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } - Text("Keys released:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } - Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : ""); + Text("Keys down:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyDown(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text("(%.02f)", GetKeyData(key)->DownDuration); } + Text("Keys pressed:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyPressed(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys released:"); for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) { if (!IsKeyReleased(key)) continue; SameLine(); Text(IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); } + Text("Keys mods: %s%s%s%s", io.KeyCtrl ? "Ctrl " : "", io.KeyShift ? "Shift " : "", io.KeyAlt ? "Alt " : "", io.KeySuper ? "Super " : ""); Text("Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text("\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public. DebugRenderKeyboardPreview(GetWindowDrawList()); Unindent(); @@ -15766,7 +16842,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible); Text("NavActivateId/DownId/PressedId: %08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId); Text("NavActivateFlags: %04X", g.NavActivateFlags); - Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); + Text("NavCursorVisible: %d, NavHighlightItemUnderNav: %d", g.NavCursorVisible, g.NavHighlightItemUnderNav); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); Text("NavFocusRoute[] = "); for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--) @@ -15893,7 +16969,7 @@ bool ImGui::DebugBreakButton(const char* label, const char* description_of_locat ColorConvertRGBtoHSV(col4f.x, col4f.y, col4f.z, hsv.x, hsv.y, hsv.z); ColorConvertHSVtoRGB(hsv.x + 0.20f, hsv.y, hsv.z, col4f.x, col4f.y, col4f.z); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, GetColorU32(col4f), true, g.Style.FrameRounding); RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, g.Style.ButtonTextAlign, &bb); @@ -15912,16 +16988,6 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) TreePop(); } -static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) -{ - union { void* ptr; int integer; } tex_id_opaque; - memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); - if (sizeof(tex_id) >= sizeof(void*)) - ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); - else - ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); -} - // [DEBUG] Display contents of ImDrawList void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label) { @@ -15958,8 +17024,8 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con continue; } - char texid_desc[20]; - FormatTextureIDForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TextureId); + char texid_desc[30]; + FormatTextureRefForDebugDisplay(texid_desc, IM_ARRAYSIZE(texid_desc), pcmd->TexRef); char buf[300]; ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex %s, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount / 3, texid_desc, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); @@ -16048,98 +17114,223 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->Flags = backup_flags; } +// [DEBUG] Compute mask of inputs with the same codepoint. +static int CalcFontGlyphSrcOverlapMask(ImFontAtlas* atlas, ImFont* font, unsigned int codepoint) +{ + int mask = 0, count = 0; + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (!(src->FontLoader ? src->FontLoader : atlas->FontLoader)->FontSrcContainsGlyph(atlas, src, (ImWchar)codepoint)) + continue; + mask |= (1 << src_n); + count++; + } + return count > 1 ? mask : 0; +} + // [DEBUG] Display details for a single font, called by ShowStyleEditor(). void ImGui::DebugNodeFont(ImFont* font) { - bool opened = TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", - font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); - SameLine(); - if (SmallButton("Set as default")) - GetIO().FontDefault = font; - if (!opened) - return; + ImGuiContext& g = *GImGui; + ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig; + ImFontAtlas* atlas = font->OwnerAtlas; + bool opened = TreeNode(font, "Font: \"%s\": %d sources(s)", font->GetDebugName(), font->Sources.Size); // Display preview text - PushFont(font); - Text("The quick brown fox jumps over the lazy dog"); - PopFont(); + if (!opened) + Indent(); + Indent(); + if (cfg->ShowFontPreview) + { + PushFont(font, 0.0f); + Text("The quick brown fox jumps over the lazy dog"); + PopFont(); + } + if (!opened) + { + Unindent(); + Unindent(); + return; + } + if (SmallButton("Set as default")) + GetIO().FontDefault = font; + SameLine(); + BeginDisabled(atlas->Fonts.Size <= 1 || atlas->Locked); + if (SmallButton("Remove")) + atlas->RemoveFont(font); + EndDisabled(); + SameLine(); + if (SmallButton("Clear bakes")) + ImFontAtlasFontDiscardBakes(atlas, font, 0); + SameLine(); + if (SmallButton("Clear unused")) + ImFontAtlasFontDiscardBakes(atlas, font, 2); // Display details +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS SetNextItemWidth(GetFontSize() * 8); DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); - SameLine(); MetricsHelpMarker( + /*SameLine(); MetricsHelpMarker( "Note that the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" - "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); - Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); + "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");*/ +#endif + char c_str[5]; - Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar); - Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar); - const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface); - Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); - for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) - if (font->ConfigData) - if (const ImFontConfig* cfg = &font->ConfigData[config_i]) - BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", - config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); + ImTextCharToUtf8(c_str, font->FallbackChar); + Text("Fallback character: '%s' (U+%04X)", c_str, font->FallbackChar); + ImTextCharToUtf8(c_str, font->EllipsisChar); + Text("Ellipsis character: '%s' (U+%04X)", c_str, font->EllipsisChar); + + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + { + ImFontConfig* src = font->Sources[src_n]; + if (TreeNode(src, "Input %d: \'%s\' [%d], Oversample: %d,%d, PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->FontNo, src->OversampleH, src->OversampleV, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y)) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + Text("Loader: '%s'", loader->Name ? loader->Name : "N/A"); +#ifdef IMGUI_ENABLE_FREETYPE + if (loader->Name != NULL && strcmp(loader->Name, "FreeType") == 0) + { + unsigned int loader_flags = src->FontLoaderFlags; + Text("FreeType Loader Flags: 0x%08X", loader_flags); + if (ImGuiFreeType::DebugEditFontLoaderFlags(&loader_flags)) + { + ImFontAtlasFontDestroyOutput(atlas, font); + src->FontLoaderFlags = loader_flags; + ImFontAtlasFontInitOutput(atlas, font); + } + } +#endif + TreePop(); + } + } + if (font->Sources.Size > 1 && TreeNode("Input Glyphs Overlap Detection Tool")) + { + TextWrapped("- First Input that contains the glyph is used.\n" + "- Use ImFontConfig::GlyphExcludeRanges[] to specify ranges to ignore glyph in given Input.\n- Prefer using a small number of ranges as the list is scanned every time a new glyph is loaded,\n - e.g. GlyphExcludeRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };\n- This tool doesn't cache results and is slow, don't keep it open!"); + if (BeginTable("table", 2)) + { + for (unsigned int c = 0; c < 0x10000; c++) + if (int overlap_mask = CalcFontGlyphSrcOverlapMask(atlas, font, c)) + { + unsigned int c_end = c + 1; + while (c_end < 0x10000 && CalcFontGlyphSrcOverlapMask(atlas, font, c_end) == overlap_mask) + c_end++; + if (TableNextColumn() && TreeNode((void*)(intptr_t)c, "U+%04X-U+%04X: %d codepoints in %d inputs", c, c_end - 1, c_end - c, ImCountSetBits(overlap_mask))) + { + char utf8_buf[5]; + for (unsigned int n = c; n < c_end; n++) + { + ImTextCharToUtf8(utf8_buf, n); + BulletText("Codepoint U+%04X (%s)", n, utf8_buf); + } + TreePop(); + } + TableNextColumn(); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) + if (overlap_mask & (1 << src_n)) + { + Text("%d ", src_n); + SameLine(); + } + c = c_end - 1; + } + EndTable(); + } + TreePop(); + } // Display all glyphs of the fonts in separate pages of 256 characters - if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) + for (int baked_n = 0; baked_n < atlas->Builder->BakedPool.Size; baked_n++) { - ImDrawList* draw_list = GetWindowDrawList(); - const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); - const float cell_size = font->FontSize * 1; - const float cell_spacing = GetStyle().ItemSpacing.y; - for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + ImFontBaked* baked = &atlas->Builder->BakedPool[baked_n]; + if (baked->OwnerFont != font) + continue; + PushID(baked_n); + if (TreeNode("Glyphs", "Baked at { %.2fpx, d.%.2f }: %d glyphs%s", baked->Size, baked->RasterizerDensity, baked->Glyphs.Size, (baked->LastUsedFrame < atlas->Builder->FrameCount - 1) ? " *Unused*" : "")) { - // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) - // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT - // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) - if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) + if (SmallButton("Load all")) + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base++) + baked->FindGlyph((ImWchar)base); + + const int surface_sqrt = (int)ImSqrt((float)baked->MetricsTotalSurface); + Text("Ascent: %f, Descent: %f, Ascent-Descent: %f", baked->Ascent, baked->Descent, baked->Ascent - baked->Descent); + Text("Texture Area: about %d px ~%dx%d px", baked->MetricsTotalSurface, surface_sqrt, surface_sqrt); + for (int src_n = 0; src_n < font->Sources.Size; src_n++) { - base += 4096 - 256; - continue; + ImFontConfig* src = font->Sources[src_n]; + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + BulletText("Input %d: \'%s\', Oversample: (%d=>%d,%d=>%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", + src_n, src->Name, src->OversampleH, oversample_h, src->OversampleV, oversample_v, src->PixelSnapH, src->GlyphOffset.x, src->GlyphOffset.y); } - int count = 0; - for (unsigned int n = 0; n < 256; n++) - if (font->FindGlyphNoFallback((ImWchar)(base + n))) + DebugNodeFontGlyphesForSrcMask(font, baked, ~0); + TreePop(); + } + PopID(); + } + TreePop(); + Unindent(); +} + +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask) +{ + ImDrawList* draw_list = GetWindowDrawList(); + const ImU32 glyph_col = GetColorU32(ImGuiCol_Text); + const float cell_size = baked->Size * 1; + const float cell_spacing = GetStyle().ItemSpacing.y; + for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) + { + // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) + // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT + // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) + if (!(base & 8191) && font->IsGlyphRangeUnused(base, base + 8191)) + { + base += 8192 - 256; + continue; + } + + int count = 0; + for (unsigned int n = 0; n < 256; n++) + if (const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL) + if (src_mask & (1 << glyph->SourceIdx)) count++; - if (count <= 0) - continue; - if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) - continue; + if (count <= 0) + continue; + if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) + continue; - // Draw a 16x16 grid of glyphs - ImVec2 base_pos = GetCursorScreenPos(); - for (unsigned int n = 0; n < 256; n++) + // Draw a 16x16 grid of glyphs + ImVec2 base_pos = GetCursorScreenPos(); + for (unsigned int n = 0; n < 256; n++) + { + // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions + // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. + ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); + ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); + const ImFontGlyph* glyph = baked->IsGlyphLoaded((ImWchar)(base + n)) ? baked->FindGlyph((ImWchar)(base + n)) : NULL; + draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); + if (!glyph || (src_mask & (1 << glyph->SourceIdx)) == 0) + continue; + font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); + if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) { - // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions - // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. - ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); - ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); - const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); - draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); - if (!glyph) - continue; - font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); - if (IsMouseHoveringRect(cell_p1, cell_p2) && BeginTooltip()) - { - DebugNodeFontGlyph(font, glyph); - EndTooltip(); - } + DebugNodeFontGlyph(font, glyph); + EndTooltip(); } - Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); - TreePop(); } + Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); TreePop(); } - TreePop(); } -void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) +void ImGui::DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph) { Text("Codepoint: U+%04X", glyph->Codepoint); Separator(); @@ -16147,6 +17338,12 @@ void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph) Text("AdvanceX: %.1f", glyph->AdvanceX); Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); + if (glyph->PackId >= 0) + { + ImTextureRect* r = ImFontAtlasPackGetRect(font->OwnerAtlas, glyph->PackId); + Text("PackId: 0x%X (%dx%d rect at %d,%d)", glyph->PackId, r->w, r->h, r->x, r->y); + } + Text("SourceIdx: %d", glyph->SourceIdx); } // [DEBUG] Display contents of ImGuiStorage @@ -16182,7 +17379,7 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) if (!is_active) { PopStyleColor(); } if (is_active && IsItemHovered()) { - ImDrawList* draw_list = GetForegroundDrawList(); + ImDrawList* draw_list = GetForegroundDrawList(tab_bar->Window); draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255)); draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255)); @@ -16270,7 +17467,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) { ImRect r = window->NavRectRel[layer]; - if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y) + if (r.Min.x >= r.Max.x && r.Min.y >= r.Max.y) BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]); else BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x, r.Min.y, r.Max.x, r.Max.y); @@ -16278,7 +17475,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) } const ImVec2* pr = window->NavPreferredScoringPosRel; for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) - BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater. + BulletText("NavPreferredScoringPosRel[%d] = (%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater. BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } @@ -16329,9 +17526,9 @@ void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int wi ImFormatString(buf, IM_ARRAYSIZE(buf), "[%04d] Window", window->BeginOrderWithinContext); //BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name); DebugNodeWindow(window, buf); - Indent(); + TreePush(buf); DebugNodeWindowsListByBeginStackParent(windows + i + 1, windows_size - i - 1, window); - Unindent(); + TreePop(); } } @@ -16399,14 +17596,14 @@ static void ShowDebugLogFlag(const char* name, ImGuiDebugLogFlags flags) } else { - ImGui::SetItemTooltip("Hold SHIFT when clicking to enable for 2 frames only (useful for spammy log entries)"); + ImGui::SetItemTooltip("Hold Shift when clicking to enable for 2 frames only (useful for spammy log entries)"); } } void ImGui::ShowDebugLogWindow(bool* p_open) { ImGuiContext& g = *GImGui; - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0) SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 12.0f), ImGuiCond_FirstUseEver); if (!Begin("Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1) { @@ -16423,6 +17620,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO); + ShowDebugLogFlag("Font", ImGuiDebugLogFlags_EventFont); ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); @@ -16475,7 +17673,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) void ImGui::DebugTextUnformattedWithLocateItem(const char* line_begin, const char* line_end) { TextUnformatted(line_begin, line_end); - if (!IsItemHovered()) + if (!IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) return; ImGuiContext& g = *GImGui; ImRect text_rect = g.LastItemData.Rect; @@ -16616,116 +17814,162 @@ void ImGui::UpdateDebugToolItemPicker() EndTooltip(); } -// [DEBUG] ID Stack Tool: update queries. Called by NewFrame() -void ImGui::UpdateDebugToolStackQueries() +// Update queries. The steps are: -1: query Stack, >= 0: query each stack item +// We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and constant-time +static ImGuiID DebugItemPathQuery_UpdateAndGetHookId(ImGuiDebugItemPathQuery* query, ImGuiID id) { - ImGuiContext& g = *GImGui; - ImGuiIDStackTool* tool = &g.DebugIDStackTool; - - // Clear hook when id stack tool is not visible - g.DebugHookIdInfo = 0; - if (g.FrameCount != tool->LastActiveFrame + 1) - return; - - // Update queries. The steps are: -1: query Stack, >= 0: query each stack item - // We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and constant-time - const ImGuiID query_id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId; - if (tool->QueryId != query_id) + // Update query. Clear hook when no active query + if (query->MainID != id) { - tool->QueryId = query_id; - tool->StackLevel = -1; - tool->Results.resize(0); + query->MainID = id; + query->Step = -1; + query->Complete = false; + query->Results.resize(0); + query->ResultsDescBuf.resize(0); } - if (query_id == 0) - return; + query->Active = false; + if (id == 0) + return 0; // Advance to next stack level when we got our result, or after 2 frames (in case we never get a result) - int stack_level = tool->StackLevel; - if (stack_level >= 0 && stack_level < tool->Results.Size) - if (tool->Results[stack_level].QuerySuccess || tool->Results[stack_level].QueryFrameCount > 2) - tool->StackLevel++; + if (query->Step >= 0 && query->Step < query->Results.Size) + if (query->Results[query->Step].QuerySuccess || query->Results[query->Step].QueryFrameCount > 2) + query->Step++; - // Update hook - stack_level = tool->StackLevel; - if (stack_level == -1) - g.DebugHookIdInfo = query_id; - if (stack_level >= 0 && stack_level < tool->Results.Size) + // Update status and hook + query->Complete = (query->Step == query->Results.Size); + if (query->Step == -1) + { + query->Active = true; + return id; + } + else if (query->Step >= 0 && query->Step < query->Results.Size) { - g.DebugHookIdInfo = tool->Results[stack_level].ID; - tool->Results[stack_level].QueryFrameCount++; + query->Results[query->Step].QueryFrameCount++; + query->Active = true; + return query->Results[query->Step].ID; } + return 0; +} + +// [DEBUG] ID Stack Tool: update query. Called by NewFrame() +void ImGui::UpdateDebugToolItemPathQuery() +{ + ImGuiContext& g = *GImGui; + ImGuiID id = 0; + if (g.DebugIDStackTool.LastActiveFrame + 1 == g.FrameCount) + id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId; + g.DebugHookIdInfoId = DebugItemPathQuery_UpdateAndGetHookId(&g.DebugItemPathQuery, id); } // [DEBUG] ID Stack tool: hooks called by GetID() family functions void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end) { ImGuiContext& g = *GImGui; + ImGuiDebugItemPathQuery* query = &g.DebugItemPathQuery; + if (query->Active == false) + { + IM_ASSERT(id == 0); + return; + } ImGuiWindow* window = g.CurrentWindow; - ImGuiIDStackTool* tool = &g.DebugIDStackTool; - // Step 0: stack query + // Step -1: stack query // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget. - if (tool->StackLevel == -1) + if (query->Step == -1) { - tool->StackLevel++; - tool->Results.resize(window->IDStack.Size + 1, ImGuiStackLevelInfo()); + IM_ASSERT(query->Results.Size == 0); + query->Step++; + query->Results.resize(window->IDStack.Size + 1, ImGuiStackLevelInfo()); for (int n = 0; n < window->IDStack.Size + 1; n++) - tool->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id; + query->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id; return; } - // Step 1+: query for individual level - IM_ASSERT(tool->StackLevel >= 0); - if (tool->StackLevel != window->IDStack.Size) + // Step 0+: query for individual level + IM_ASSERT(query->Step >= 0); + if (query->Step != window->IDStack.Size) return; - ImGuiStackLevelInfo* info = &tool->Results[tool->StackLevel]; + ImGuiStackLevelInfo* info = &query->Results[query->Step]; IM_ASSERT(info->ID == id && info->QueryFrameCount > 0); - switch (data_type) + if (info->DescOffset == -1) { - case ImGuiDataType_S32: - ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%d", (int)(intptr_t)data_id); - break; - case ImGuiDataType_String: - ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "%.*s", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)strlen((const char*)data_id), (const char*)data_id); - break; - case ImGuiDataType_Pointer: - ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "(void*)0x%p", data_id); - break; - case ImGuiDataType_ID: - if (info->Desc[0] != 0) // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to DebugHookIdInfo(). We prioritize the first one. - return; - ImFormatString(info->Desc, IM_ARRAYSIZE(info->Desc), "0x%08X [override]", id); - break; - default: - IM_ASSERT(0); + const char* result = NULL; + const char* result_end = NULL; + switch (data_type) + { + case ImGuiDataType_S32: + ImFormatStringToTempBuffer(&result, &result_end, "%d", (int)(intptr_t)data_id); + break; + case ImGuiDataType_String: + ImFormatStringToTempBuffer(&result, &result_end, "%.*s", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)ImStrlen((const char*)data_id), (const char*)data_id); + break; + case ImGuiDataType_Pointer: + ImFormatStringToTempBuffer(&result, &result_end, "(void*)0x%p", data_id); + break; + case ImGuiDataType_ID: + // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to DebugHookIdInfo(). We prioritize the first one. + ImFormatStringToTempBuffer(&result, &result_end, "0x%08X [override]", id); + break; + default: + IM_ASSERT(0); + } + info->DescOffset = query->ResultsDescBuf.size(); + query->ResultsDescBuf.append(result, result_end + 1); // Include zero terminator } info->QuerySuccess = true; - info->DataType = data_type; + if (info->DataType == -1) + info->DataType = (ImS8)data_type; } -static int StackToolFormatLevelInfo(ImGuiIDStackTool* tool, int n, bool format_for_ui, char* buf, size_t buf_size) +static int DebugItemPathQuery_FormatLevelInfo(ImGuiDebugItemPathQuery* query, int n, bool format_for_ui, char* buf, size_t buf_size) { - ImGuiStackLevelInfo* info = &tool->Results[n]; - ImGuiWindow* window = (info->Desc[0] == 0 && n == 0) ? ImGui::FindWindowByID(info->ID) : NULL; - if (window) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked) - return ImFormatString(buf, buf_size, format_for_ui ? "\"%s\" [window]" : "%s", window->Name); - if (info->QuerySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they both have same id) - return ImFormatString(buf, buf_size, (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", info->Desc); - if (tool->StackLevel < tool->Results.Size) // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers. + ImGuiStackLevelInfo* info = &query->Results[n]; + ImGuiWindow* window = (info->DescOffset == -1 && n == 0) ? ImGui::FindWindowByID(info->ID) : NULL; + if (window) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked) + return ImFormatString(buf, buf_size, format_for_ui ? "\"%s\" [window]" : "%s", ImHashSkipUncontributingPrefix(window->Name)); + if (info->QuerySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they both have same id) + return ImFormatString(buf, buf_size, (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", ImHashSkipUncontributingPrefix(&query->ResultsDescBuf.Buf[info->DescOffset])); + if (query->Step < query->Results.Size) // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers. return (*buf = 0); #ifdef IMGUI_ENABLE_TEST_ENGINE - if (const char* label = ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID)) // Source: ImGuiTestEngine's ItemInfo() - return ImFormatString(buf, buf_size, format_for_ui ? "??? \"%s\"" : "%s", label); + if (const char* label = ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID)) // Source: ImGuiTestEngine's ItemInfo() + return ImFormatString(buf, buf_size, format_for_ui ? "??? \"%s\"" : "%s", ImHashSkipUncontributingPrefix(label)); #endif return ImFormatString(buf, buf_size, "???"); } +static const char* DebugItemPathQuery_GetResultAsPath(ImGuiDebugItemPathQuery* query, bool hex_encode_non_ascii_chars) +{ + ImGuiTextBuffer* buf = &query->ResultPathBuf; + buf->resize(0); + for (int stack_n = 0; stack_n < query->Results.Size; stack_n++) + { + char level_desc[256]; + DebugItemPathQuery_FormatLevelInfo(query, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); + buf->append(stack_n == 0 ? "//" : "/"); + for (const char* p = level_desc; *p != 0; ) + { + unsigned int c; + const char* p_next = p + ImTextCharFromUtf8(&c, p, NULL); + if (c == '/') + buf->append("\\"); + if (c < 256 || !hex_encode_non_ascii_chars) + buf->append(p, p_next); + else for (; p < p_next; p++) + buf->appendf("\\x%02x", (unsigned char)*p); + p = p_next; + } + } + return buf->c_str(); +} + // ID Stack Tool: Display UI void ImGui::ShowIDStackToolWindow(bool* p_open) { ImGuiContext& g = *GImGui; - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0) SetNextWindowSize(ImVec2(0.0f, GetFontSize() * 8.0f), ImGuiCond_FirstUseEver); if (!Begin("Dear ImGui ID Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1) { @@ -16733,64 +17977,54 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) return; } - // Display hovered/active status + ImGuiDebugItemPathQuery* query = &g.DebugItemPathQuery; ImGuiIDStackTool* tool = &g.DebugIDStackTool; - const ImGuiID hovered_id = g.HoveredIdPreviousFrame; - const ImGuiID active_id = g.ActiveId; -#ifdef IMGUI_ENABLE_TEST_ENGINE - Text("HoveredId: 0x%08X (\"%s\"), ActiveId: 0x%08X (\"%s\")", hovered_id, hovered_id ? ImGuiTestEngine_FindItemDebugLabel(&g, hovered_id) : "", active_id, active_id ? ImGuiTestEngine_FindItemDebugLabel(&g, active_id) : ""); -#else - Text("HoveredId: 0x%08X, ActiveId: 0x%08X", hovered_id, active_id); -#endif + tool->LastActiveFrame = g.FrameCount; + const char* result_path = DebugItemPathQuery_GetResultAsPath(query, tool->OptHexEncodeNonAsciiChars); + Text("0x%08X", query->MainID); SameLine(); MetricsHelpMarker("Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details."); - // CTRL+C to copy path + // Ctrl+C to copy path const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime; - Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC); + PushStyleVarY(ImGuiStyleVar_FramePadding, 0.0f); + Checkbox("Hex-encode non-ASCII", &tool->OptHexEncodeNonAsciiChars); + SameLine(); + Checkbox("Ctrl+C: copy path", &tool->OptCopyToClipboardOnCtrlC); + PopStyleVar(); SameLine(); TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*"); - if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused)) + if (tool->OptCopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused)) { tool->CopyToClipboardLastTime = (float)g.Time; - char* p = g.TempBuffer.Data; - char* p_end = p + g.TempBuffer.Size; - for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++) - { - *p++ = '/'; - char level_desc[256]; - StackToolFormatLevelInfo(tool, stack_n, false, level_desc, IM_ARRAYSIZE(level_desc)); - for (int n = 0; level_desc[n] && p + 2 < p_end; n++) - { - if (level_desc[n] == '/') - *p++ = '\\'; - *p++ = level_desc[n]; - } - } - *p = '\0'; - SetClipboardText(g.TempBuffer.Data); + SetClipboardText(result_path); } + Text("- Path \"%s\"", query->Complete ? result_path : ""); +#ifdef IMGUI_ENABLE_TEST_ENGINE + Text("- Label \"%s\"", query->MainID ? ImGuiTestEngine_FindItemDebugLabel(&g, query->MainID) : ""); +#endif + Separator(); + // Display decorated stack - tool->LastActiveFrame = g.FrameCount; - if (tool->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders)) + if (query->Results.Size > 0 && BeginTable("##table", 3, ImGuiTableFlags_Borders)) { const float id_width = CalcTextSize("0xDDDDDDDD").x; TableSetupColumn("Seed", ImGuiTableColumnFlags_WidthFixed, id_width); TableSetupColumn("PushID", ImGuiTableColumnFlags_WidthStretch); TableSetupColumn("Result", ImGuiTableColumnFlags_WidthFixed, id_width); TableHeadersRow(); - for (int n = 0; n < tool->Results.Size; n++) + for (int n = 0; n < query->Results.Size; n++) { - ImGuiStackLevelInfo* info = &tool->Results[n]; + ImGuiStackLevelInfo* info = &query->Results[n]; TableNextColumn(); - Text("0x%08X", (n > 0) ? tool->Results[n - 1].ID : 0); + Text("0x%08X", (n > 0) ? query->Results[n - 1].ID : 0); TableNextColumn(); - StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size); + DebugItemPathQuery_FormatLevelInfo(query, n, true, g.TempBuffer.Data, g.TempBuffer.Size); TextUnformatted(g.TempBuffer.Data); TableNextColumn(); Text("0x%08X", info->ID); - if (n == tool->Results.Size - 1) + if (n == query->Results.Size - 1) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_Header)); } EndTable(); @@ -16806,6 +18040,7 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns*) {} void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {} void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {} void ImGui::DebugNodeFont(ImFont*) {} +void ImGui::DebugNodeFontGlyphesForSrcMask(ImFont*, ImFontBaked*, int) {} void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {} void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {} void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {} @@ -16820,6 +18055,40 @@ void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS +#if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) +// Demo helper function to select among loaded fonts. +// Here we use the regular BeginCombo()/EndCombo() api which is the more flexible one. +void ImGui::ShowFontSelector(const char* label) +{ + ImGuiIO& io = GetIO(); + ImFont* font_current = GetFont(); + if (BeginCombo(label, font_current->GetDebugName())) + { + for (ImFont* font : io.Fonts->Fonts) + { + PushID((void*)font); + if (Selectable(font->GetDebugName(), font == font_current, ImGuiSelectableFlags_SelectOnNav)) + io.FontDefault = font; + if (font == font_current) + SetItemDefaultFocus(); + PopID(); + } + EndCombo(); + } + SameLine(); + if (io.BackendFlags & ImGuiBackendFlags_RendererHasTextures) + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- Read FAQ and docs/FONTS.md for more details."); + else + MetricsHelpMarker( + "- Load additional fonts with io.Fonts->AddFontXXX() functions.\n" + "- The font atlas is built when calling io.Fonts->GetTexDataAsXXXX() or io.Fonts->Build().\n" + "- Read FAQ and docs/FONTS.md for more details.\n" + "- If you need to add/remove fonts at runtime (e.g. for DPI change), do it before calling NewFrame()."); +} +#endif // #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) || !defined(IMGUI_DISABLE_DEBUG_TOOLS) + //----------------------------------------------------------------------------- // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.h" index 77ca8e04..be945c61 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui.h" @@ -1,42 +1,45 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (headers) // Help: -// - See links below. // - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that. // - Read top of imgui.cpp for more details, links and comments. -// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. +// - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including imgui.h (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. // Resources: // - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) // - Homepage ................... https://github.com/ocornut/imgui -// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Releases & Changelog ....... https://github.com/ocornut/imgui/releases // - Gallery .................... https://github.com/ocornut/imgui/issues?q=label%3Agallery (please post your screenshots/video there!) // - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) // - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) // - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) -// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings + backends for various tech/engines) // - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary // - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui // - Issues & support ........... https://github.com/ocornut/imgui/issues // - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) +// - Web version of the Demo .... https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html (w/ source code browser) -// For first-time users having issues compiling/linking/running/loading fonts: +// For FIRST-TIME users having issues compiling/linking/running: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. -// Everything else should be asked in 'Issues'! We are building a database of cross-linked knowledge there. +// EVERYTHING ELSE should be asked in 'Issues'! We are building a database of cross-linked knowledge there. +// Since 1.92, we encourage font loading questions to also be posted in 'Issues'. // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.91.3" -#define IMGUI_VERSION_NUM 19130 -#define IMGUI_HAS_TABLE +#define IMGUI_VERSION "1.92.5" +#define IMGUI_VERSION_NUM 19250 +#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 +#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 /* Index of this file: // [SECTION] Header mess // [SECTION] Forward declarations and basic types +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) // [SECTION] Dear ImGui end-user API functions // [SECTION] Flags & Enumerations // [SECTION] Tables API flags and structures (ImGuiTableFlags, ImGuiTableColumnFlags, ImGuiTableRowFlags, ImGuiTableBgTarget, ImGuiTableSortSpecs, ImGuiTableColumnSortSpecs) @@ -47,7 +50,8 @@ Index of this file: // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) // [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) -// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont) +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) // [SECTION] ImGuiPlatformIO + other Platform Dependent Interfaces (ImGuiPlatformImeData) // [SECTION] Obsolete functions and types @@ -86,19 +90,24 @@ Index of this file: #endif // Helper Macros +// (note: compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.) #ifndef IM_ASSERT #include #define IM_ASSERT(_EXPR) assert(_EXPR) // You can override the default assert handler by editing imconfig.h #endif #define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers! #define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds. +#define IM_STRINGIFY_HELPER(_EXPR) #_EXPR +#define IM_STRINGIFY(_EXPR) IM_STRINGIFY_HELPER(_EXPR) // Preprocessor idiom to stringify e.g. an integer or a macro. // Check that version and structures layouts are matching between compiled imgui code and caller. Read comments above DebugCheckVersionAndDataLayout() for details. #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. -// (MSVC provides an equivalent mechanism via SAL Annotations but it would require the macros in a different -// location. e.g. #include + void myprintf(_Printf_format_string_ const char* format, ...)) +// (MSVC provides an equivalent mechanism via SAL Annotations but it requires the macros in a different +// location. e.g. #include + void myprintf(_Printf_format_string_ const char* format, ...), +// and only works when using Code Analysis, rather than just normal compiling). +// (see https://github.com/ocornut/imgui/issues/8871 for a patch to enable this for MSVC's Code Analysis) #if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__) #define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) #define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) @@ -130,15 +139,17 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' -#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif //----------------------------------------------------------------------------- @@ -156,7 +167,7 @@ typedef unsigned int ImU32; // 32-bit unsigned integer (often used to st typedef signed long long ImS64; // 64-bit signed integer typedef unsigned long long ImU64; // 64-bit unsigned integer -// Forward declarations +// Forward declarations: ImDrawList, ImFontAtlas layer struct ImDrawChannel; // Temporary storage to output draw commands out of order, used by ImDrawListSplitter and ImDrawList::ChannelsSplit() struct ImDrawCmd; // A single draw command within a parent ImDrawList (generally maps to 1 GPU draw call, unless it is a callback) struct ImDrawData; // All draw command lists required to render the frame + pos/size coordinates to use for the projection matrix. @@ -166,11 +177,18 @@ struct ImDrawListSplitter; // Helper to split a draw list into differen struct ImDrawVert; // A single vertex (pos + uv + col = 20 bytes by default. Override layout with IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT) struct ImFont; // Runtime data for a single font within a parent ImFontAtlas struct ImFontAtlas; // Runtime data for multiple fonts, bake multiple fonts into a single texture, TTF/OTF font loader -struct ImFontBuilderIO; // Opaque interface to a font builder (stb_truetype or FreeType). +struct ImFontAtlasBuilder; // Opaque storage for building a ImFontAtlas +struct ImFontAtlasRect; // Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +struct ImFontBaked; // Baked data for a ImFont at a given size. struct ImFontConfig; // Configuration data when adding a font or merging fonts struct ImFontGlyph; // A single font glyph (code point + coordinates within in ImFontAtlas + offset) struct ImFontGlyphRangesBuilder; // Helper to build glyph ranges from text/string data +struct ImFontLoader; // Opaque interface to a font loading backend (stb_truetype, FreeType etc.). +struct ImTextureData; // Specs and pixel storage for a texture used by Dear ImGui. +struct ImTextureRect; // Coordinates of a rectangle within a texture. struct ImColor; // Helper functions to create a color that can be converted to either u32 or float4 (*OBSOLETE* please avoid using) + +// Forward declarations: ImGui layer struct ImGuiContext; // Dear ImGui context (opaque structure, unless including imgui_internal.h) struct ImGuiIO; // Main configuration and I/O between your application and ImGui (also see: ImGuiPlatformIO) struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) @@ -197,9 +215,9 @@ struct ImGuiViewport; // A Platform Window (always only one in 'ma // Enumerations // - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. -// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - In Visual Studio: Ctrl+Comma ("Edit.GoToAll") can follow symbols inside comments, whereas Ctrl+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. enum ImGuiDir : int; // -> enum ImGuiDir // Enum: A cardinal direction (Left, Right, Up, Down) enum ImGuiKey : int; // -> enum ImGuiKey // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value) enum ImGuiMouseSource : int; // -> enum ImGuiMouseSource // Enum; A mouse input source identifier (Mouse, TouchScreen, Pen) @@ -214,12 +232,14 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // Flags (declared as int to allow using as flags without overhead, and to not pollute the top of this file) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. -// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - In Visual Studio: Ctrl+Comma ("Edit.GoToAll") can follow symbols inside comments, whereas Ctrl+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance -typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build +typedef int ImDrawTextFlags; // -> enum ImDrawTextFlags_ // Internal, do not use! +typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont +typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas typedef int ImGuiBackendFlags; // -> enum ImGuiBackendFlags_ // Flags: for io.BackendFlags typedef int ImGuiButtonFlags; // -> enum ImGuiButtonFlags_ // Flags: for InvisibleButton() typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: for BeginChild() @@ -233,6 +253,7 @@ typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: f typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline() typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), shared by all items typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values. +typedef int ImGuiListClipperFlags; // -> enum ImGuiListClipperFlags_// Flags: for ImGuiListClipper typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() typedef int ImGuiMultiSelectFlags; // -> enum ImGuiMultiSelectFlags_// Flags: for BeginMultiSelect() typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() @@ -246,20 +267,6 @@ typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: f typedef int ImGuiViewportFlags; // -> enum ImGuiViewportFlags_ // Flags: for ImGuiViewport typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin(), BeginChild() -// ImTexture: user data for renderer backend to identify a texture [Compile-time configurable type] -// - To use something else than an opaque void* pointer: override with e.g. '#define ImTextureID MyTextureType*' in your imconfig.h file. -// - This can be whatever to you want it to be! read the FAQ about ImTextureID for details. -#ifndef ImTextureID -typedef void* ImTextureID; // Default: store a pointer or an integer fitting in a pointer (most renderer backends are ok with that) -#endif - -// ImDrawIdx: vertex index. [Compile-time configurable type] -// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). -// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. -#ifndef ImDrawIdx -typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibility with renderer backends) -#endif - // Character types // (we generally use UTF-8 encoded string in the API. This is storage specifically for a decoded character used for keyboard input and display) typedef unsigned int ImWchar32; // A single decoded U32 character/code point. We encode them as multi bytes UTF-8 when used in strings. @@ -309,6 +316,68 @@ struct ImVec4 }; IM_MSVC_RUNTIME_CHECKS_RESTORE +//----------------------------------------------------------------------------- +// [SECTION] Texture identifiers (ImTextureID, ImTextureRef) +//----------------------------------------------------------------------------- + +// ImTextureID = backend specific, low-level identifier for a texture uploaded in GPU/graphics system. +// [Compile-time configurable type] +// - When a Rendered Backend creates a texture, it store its native identifier into a ImTextureID value. +// (e.g. Used by DX11 backend to a `ID3D11ShaderResourceView*`; Used by OpenGL backends to store `GLuint`; +// Used by SDLGPU backend to store a `SDL_GPUTextureSamplerBinding*`, etc.). +// - User may submit their own textures to e.g. ImGui::Image() function by passing this value. +// - During the rendering loop, the Renderer Backend retrieve the ImTextureID, which stored inside a +// ImTextureRef, which is stored inside a ImDrawCmd. +// - Compile-time type configuration: +// - To use something other than a 64-bit value: add '#define ImTextureID MyTextureType*' in your imconfig.h file. +// - This can be whatever to you want it to be! read the FAQ entry about textures for details. +// - You may decide to store a higher-level structure containing texture, sampler, shader etc. with various +// constructors if you like. You will need to implement ==/!= operators. +// History: +// - In v1.91.4 (2024/10/08): the default type for ImTextureID was changed from 'void*' to 'ImU64'. This allowed backends requiring 64-bit worth of data to build on 32-bit architectures. Use intermediary intptr_t cast and read FAQ if you have casting warnings. +// - In v1.92.0 (2025/06/11): added ImTextureRef which carry either a ImTextureID either a pointer to internal texture atlas. All user facing functions taking ImTextureID changed to ImTextureRef +#ifndef ImTextureID +typedef ImU64 ImTextureID; // Default: store up to 64-bits (any pointer or integer). A majority of backends are ok with that. +#endif + +// Define this if you need 0 to be a valid ImTextureID for your backend. +#ifndef ImTextureID_Invalid +#define ImTextureID_Invalid ((ImTextureID)0) +#endif + +// ImTextureRef = higher-level identifier for a texture. Store a ImTextureID _or_ a ImTextureData*. +// The identifier is valid even before the texture has been uploaded to the GPU/graphics system. +// This is what gets passed to functions such as `ImGui::Image()`, `ImDrawList::AddImage()`. +// This is what gets stored in draw commands (`ImDrawCmd`) to identify a texture during rendering. +// - When a texture is created by user code (e.g. custom images), we directly stores the low-level ImTextureID. +// Because of this, when displaying your own texture you are likely to ever only manage ImTextureID values on your side. +// - When a texture is created by the backend, we stores a ImTextureData* which becomes an indirection +// to extract the ImTextureID value during rendering, after texture upload has happened. +// - To create a ImTextureRef from a ImTextureData you can use ImTextureData::GetTexRef(). +// We intentionally do not provide an ImTextureRef constructor for this: we don't expect this +// to be frequently useful to the end-user, and it would be erroneously called by many legacy code. +// - If you want to bind the current atlas when using custom rectangle, you can use io.Fonts->TexRef. +// - Binding generators for languages such as C (which don't have constructors), should provide a helper, e.g. +// inline ImTextureRef ImTextureRefFromID(ImTextureID tex_id) { ImTextureRef tex_ref = { ._TexData = NULL, .TexID = tex_id }; return tex_ref; } +// In 1.92 we changed most drawing functions using ImTextureID to use ImTextureRef. +// We intentionally do not provide an implicit ImTextureRef -> ImTextureID cast operator because it is technically lossy to convert ImTextureRef to ImTextureID before rendering. +IM_MSVC_RUNTIME_CHECKS_OFF +struct ImTextureRef +{ + ImTextureRef() { _TexData = NULL; _TexID = ImTextureID_Invalid; } + ImTextureRef(ImTextureID tex_id) { _TexData = NULL; _TexID = tex_id; } +#if !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(ImTextureID) + ImTextureRef(void* tex_id) { _TexData = NULL; _TexID = (ImTextureID)(size_t)tex_id; } // For legacy backends casting to ImTextureID +#endif + + inline ImTextureID GetTexID() const; // == (_TexData ? _TexData->TexID : _TexID) // Implemented below in the file. + + // Members (either are set, never both!) + ImTextureData* _TexData; // A texture, generally owned by a ImFontAtlas. Will convert to ImTextureID during render loop, after texture has been uploaded. + ImTextureID _TexID; // _OR_ Low-level backend texture identifier, if already uploaded or created by user/app. Generally provided to e.g. ImGui::Image() calls. +}; +IM_MSVC_RUNTIME_CHECKS_RESTORE + //----------------------------------------------------------------------------- // [SECTION] Dear ImGui end-user API functions // (Note that ImGui:: being a namespace, you can add extra ImGui:: functions in your own separate file. Please don't modify imgui source files!) @@ -332,7 +401,7 @@ namespace ImGui IMGUI_API void NewFrame(); // start a new Dear ImGui frame, you can submit any command from this point until Render()/EndFrame(). IMGUI_API void EndFrame(); // ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all! IMGUI_API void Render(); // ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData(). - IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). this is what you have to render. + IMGUI_API ImDrawData* GetDrawData(); // valid after Render() and until the next call to NewFrame(). Call ImGui_ImplXXXX_RenderDrawData() function in your Renderer Backend to render. // Demo, Debug, Information IMGUI_API void ShowDemoWindow(bool* p_open = NULL); // create Demo window. demonstrate most ImGui features. call this to learn about the library! try to make it always available in your application! @@ -414,7 +483,6 @@ namespace ImGui IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). IMGUI_API void SetWindowFocus(); // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). - IMGUI_API void SetWindowFontScale(float scale); // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). IMGUI_API void SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond = 0); // set named window position. IMGUI_API void SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond = 0); // set named window size. set axis to 0.0f to force an auto-fit on this axis. IMGUI_API void SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond = 0); // set named window collapsed state @@ -434,9 +502,29 @@ namespace ImGui IMGUI_API void SetScrollFromPosX(float local_x, float center_x_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. IMGUI_API void SetScrollFromPosY(float local_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. - // Parameters stacks (shared) - IMGUI_API void PushFont(ImFont* font); // use NULL as a shortcut to push default font + // Parameters stacks (font) + // - PushFont(font, 0.0f) // Change font and keep current size + // - PushFont(NULL, 20.0f) // Keep font and change current size + // - PushFont(font, 20.0f) // Change font and set size to 20.0f + // - PushFont(font, style.FontSizeBase * 2.0f) // Change font and set size to be twice bigger than current size. + // - PushFont(font, font->LegacySize) // Change font and set size to size passed to AddFontXXX() function. Same as pre-1.92 behavior. + // *IMPORTANT* before 1.92, fonts had a single size. They can now be dynamically be adjusted. + // - In 1.92 we have REMOVED the single parameter version of PushFont() because it seems like the easiest way to provide an error-proof transition. + // - PushFont(font) before 1.92 = PushFont(font, font->LegacySize) after 1.92 // Use default font size as passed to AddFontXXX() function. + // *IMPORTANT* global scale factors are applied over the provided size. + // - Global scale factors are: 'style.FontScaleMain', 'style.FontScaleDpi' and maybe more. + // - If you want to apply a factor to the _current_ font size: + // - CORRECT: PushFont(NULL, style.FontSizeBase) // use current unscaled size == does nothing + // - CORRECT: PushFont(NULL, style.FontSizeBase * 2.0f) // use current unscaled size x2 == make text twice bigger + // - INCORRECT: PushFont(NULL, GetFontSize()) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + // - INCORRECT: PushFont(NULL, GetFontSize() * 2.0f) // INCORRECT! using size after global factors already applied == GLOBAL SCALING FACTORS WILL APPLY TWICE! + IMGUI_API void PushFont(ImFont* font, float font_size_base_unscaled); // Use NULL as a shortcut to keep current font. Use 0.0f to keep current size. IMGUI_API void PopFont(); + IMGUI_API ImFont* GetFont(); // get current font + IMGUI_API float GetFontSize(); // get current scaled font size (= height in pixels). AFTER global scale factors applied. *IMPORTANT* DO NOT PASS THIS VALUE TO PushFont()! Use ImGui::GetStyle().FontSizeBase to get value before global scale factors. + IMGUI_API ImFontBaked* GetFontBaked(); // get current font bound at current size // == GetFont()->GetFontBaked(GetFontSize()) + + // Parameters stacks (shared) IMGUI_API void PushStyleColor(ImGuiCol idx, ImU32 col); // modify a style color. always use this if you modify the style after NewFrame(). IMGUI_API void PushStyleColor(ImGuiCol idx, const ImVec4& col); IMGUI_API void PopStyleColor(int count = 1); @@ -458,8 +546,6 @@ namespace ImGui // Style read access // - Use the ShowStyleEditor() function to interactively see/edit the colors. - IMGUI_API ImFont* GetFont(); // get current font - IMGUI_API float GetFontSize(); // get current font size (= height in pixels) of current font with current scale applied IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a white pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList @@ -538,7 +624,7 @@ namespace ImGui IMGUI_API void LabelTextV(const char* label, const char* fmt, va_list args) IM_FMTLIST(2); IMGUI_API void BulletText(const char* fmt, ...) IM_FMTARGS(1); // shortcut for Bullet()+Text() IMGUI_API void BulletTextV(const char* fmt, va_list args) IM_FMTLIST(1); - IMGUI_API void SeparatorText(const char* label); // currently: formatted text with an horizontal line + IMGUI_API void SeparatorText(const char* label); // currently: formatted text with a horizontal line // Widgets: Main // - Most widgets return true when the value has been changed or when pressed/selected @@ -555,14 +641,17 @@ namespace ImGui IMGUI_API void ProgressBar(float fraction, const ImVec2& size_arg = ImVec2(-FLT_MIN, 0), const char* overlay = NULL); IMGUI_API void Bullet(); // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses IMGUI_API bool TextLink(const char* label); // hyperlink text button, return true when clicked - IMGUI_API void TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked + IMGUI_API bool TextLinkOpenURL(const char* label, const char* url = NULL); // hyperlink text button, automatically open file/url when clicked // Widgets: Images - // - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples + // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. - // - Note that Image() may add +2.0f to provided size if a border is visible, ImageButton() adds style.FramePadding*2.0f to provided size. - IMGUI_API void Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)); - IMGUI_API bool ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + // - Image() pads adds style.ImageBorderSize on each side, ImageButton() adds style.FramePadding on each side. + // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. + // - An obsolete version of Image(), before 1.91.9 (March 2025), had a 'tint_col' parameter which is now supported by the ImageWithBg() function. + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1)); + IMGUI_API void ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); + IMGUI_API bool ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // Widgets: Combo Box (Dropdown) // - The BeginCombo()/EndCombo() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() items. @@ -574,13 +663,13 @@ namespace ImGui IMGUI_API bool Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items = -1); // Widgets: Drag Sliders - // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. + // - Ctrl+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every function, note that a 'float v[X]' function argument is the same as 'float* v', // the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). - // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). - // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used. + // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For keyboard/gamepad navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). + // - Use v_min < v_max to clamp edits to given limits. Note that Ctrl+Click manual input can override those limits if ImGuiSliderFlags_AlwaysClamp is not used. // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. // - We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. // - Legacy: Pre-1.78 there are DragXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. @@ -599,7 +688,7 @@ namespace ImGui IMGUI_API bool DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed = 1.0f, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, ImGuiSliderFlags flags = 0); // Widgets: Regular Sliders - // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. + // - Ctrl+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). // - Legacy: Pre-1.78 there are SliderXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. @@ -620,7 +709,7 @@ namespace ImGui IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0); // Widgets: Input with Keyboard - // - If you want to use InputText() with std::string or any custom dynamic string type, see misc/cpp/imgui_stdlib.h and comments in imgui_demo.cpp. + // - If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp! // - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc. IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); @@ -650,7 +739,7 @@ namespace ImGui // Widgets: Trees // - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents. IMGUI_API bool TreeNode(const char* label); - IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // helper variation to easily decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). + IMGUI_API bool TreeNode(const char* str_id, const char* fmt, ...) IM_FMTARGS(2); // helper variation to easily decorrelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet(). IMGUI_API bool TreeNode(const void* ptr_id, const char* fmt, ...) IM_FMTARGS(2); // " IMGUI_API bool TreeNodeV(const char* str_id, const char* fmt, va_list args) IM_FMTLIST(2); IMGUI_API bool TreeNodeV(const void* ptr_id, const char* fmt, va_list args) IM_FMTLIST(2); @@ -675,7 +764,7 @@ namespace ImGui IMGUI_API bool Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags = 0, const ImVec2& size = ImVec2(0, 0)); // "bool* p_selected" point to the selection state (read-write), as a convenient helper. // Multi-selection system for Selectable(), Checkbox(), TreeNode() functions [BETA] - // - This enables standard multi-selection/range-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. + // - This enables standard multi-selection/range-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc.) in a way that also allow a clipper to be used. // - ImGuiSelectionUserData is often used to store your item index within the current view (but may store something else). // - Read comments near ImGuiMultiSelectIO for instructions/details and see 'Demo->Widgets->Selection State & Multi-Select' for demo. // - TreeNode() is technically supported but... using this correctly is more complicated. You need some sort of linear/random access to your tree, @@ -688,8 +777,9 @@ namespace ImGui // Widgets: List Boxes // - This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. + // - If you don't need a label you can probably simply use BeginChild() with the ImGuiChildFlags_FrameStyle flag for the same result. // - You can submit contents and manage your selection state however you want it, by creating e.g. Selectable() or any other items. - // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created. + // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analogous to how Combos are created. // - Choose frame width: size.x > 0.0f: custom / size.x < 0.0f or -FLT_MIN: right-align / size.x = 0.0f (default): use current ItemWidth // - Choose frame height: size.y > 0.0f: custom / size.y < 0.0f or -FLT_MIN: bottom-align / size.y = 0.0f (default): arbitrary default height which can fit ~7 items IMGUI_API bool BeginListBox(const char* label, const ImVec2& size = ImVec2(0, 0)); // open a framed scrolling region @@ -834,7 +924,7 @@ namespace ImGui IMGUI_API ImGuiTableSortSpecs* TableGetSortSpecs(); // get latest sort specs for the table (NULL if not sorting). Lifetime: don't hold on this pointer over multiple frames or past any subsequent call to BeginTable(). IMGUI_API int TableGetColumnCount(); // return number of columns (value passed to BeginTable) IMGUI_API int TableGetColumnIndex(); // return current column index. - IMGUI_API int TableGetRowIndex(); // return current row index. + IMGUI_API int TableGetRowIndex(); // return current row index (header rows are accounted for) IMGUI_API const char* TableGetColumnName(int column_n = -1); // return "" if column didn't have a name declared by TableSetupColumn(). Pass -1 to use current column. IMGUI_API ImGuiTableColumnFlags TableGetColumnFlags(int column_n = -1); // return column flags so you can query their Enabled/Visible/Sorted/Hovered status flags. Pass -1 to use current column. IMGUI_API void TableSetColumnEnabled(int column_n, bool v);// change user accessible enabled/disabled state of a column. Set to false to hide the column. User can use the context menu to change this themselves (right-click in headers, or right-click in columns body with ImGuiTableFlags_ContextMenuInBody) @@ -887,7 +977,7 @@ namespace ImGui // Disabling [BETA API] // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) - // - Tooltips windows by exception are opted out of disabling. + // - Tooltips windows are automatically opted out of disabling. Note that IsItemHovered() by default returns false on disabled items, unless using ImGuiHoveredFlags_AllowWhenDisabled. // - BeginDisabled(false)/EndDisabled() essentially does nothing but is provided to facilitate use of boolean expressions (as a micro-optimization: if you have tens of thousands of BeginDisabled(false)/EndDisabled() pairs, you might want to reformulate your code to avoid making those calls) IMGUI_API void BeginDisabled(bool disabled = true); IMGUI_API void EndDisabled(); @@ -898,10 +988,12 @@ namespace ImGui IMGUI_API void PopClipRect(); // Focus, Activation - // - Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item" - IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a newly appearing window. IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. + // Keyboard/Gamepad Navigation + IMGUI_API void SetNavCursorVisible(bool visible); // alter visibility of keyboard/gamepad cursor. by default: show when using an arrow key, hide when clicking with mouse. + // Overlapping mode IMGUI_API void SetNextItemAllowOverlap(); // allow next item to be overlapped by a subsequent item. Useful with invisible buttons, selectable, treenode covering an area where subsequent items may need to be added. Note that both Selectable() and TreeNode() have dedicated flags doing this. @@ -957,21 +1049,20 @@ namespace ImGui // Inputs Utilities: Keyboard/Mouse/Gamepad // - the ImGuiKey enum contains all possible keyboard, mouse and gamepad inputs (e.g. ImGuiKey_A, ImGuiKey_MouseLeft, ImGuiKey_GamepadDpadUp...). - // - before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. About use of those legacy ImGuiKey values: - // - without IMGUI_DISABLE_OBSOLETE_KEYIO (legacy support): you can still use your legacy native/user indices (< 512) according to how your backend/engine stored them in io.KeysDown[], but need to cast them to ImGuiKey. - // - with IMGUI_DISABLE_OBSOLETE_KEYIO (this is the way forward): any use of ImGuiKey will assert with key < 512. GetKeyIndex() is pass-through and therefore deprecated (gone if IMGUI_DISABLE_OBSOLETE_KEYIO is defined). + // - (legacy: before v1.87, we used ImGuiKey to carry native/user indices as defined by each backends. This was obsoleted in 1.87 (2022-02) and completely removed in 1.91.5 (2024-11). See https://github.com/ocornut/imgui/issues/4921) + // - (legacy: any use of ImGuiKey will assert when key < 512 to detect passing legacy native/user indices) IMGUI_API bool IsKeyDown(ImGuiKey key); // is key being held. IMGUI_API bool IsKeyPressed(ImGuiKey key, bool repeat = true); // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate IMGUI_API bool IsKeyReleased(ImGuiKey key); // was key released (went from Down to !Down)? IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord); // was key chord (mods + key) pressed, e.g. you can pass 'ImGuiMod_Ctrl | ImGuiKey_S' as a key-chord. This doesn't do any routing or focus check, please consider using Shortcut() function instead. IMGUI_API int GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float rate); // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate - IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names a provided for debugging purpose and are not meant to be saved persistently not compared. + IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names are provided for debugging purpose and are not meant to be saved persistently nor compared. IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call. // Inputs Utilities: Shortcut Testing & Routing [BETA] // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super. - // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments) - // ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments) + // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments + // ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments // only ImGuiMod_XXX values are legal to combine with an ImGuiKey. You CANNOT combine two ImGuiKey values. // - The general idea is that several callers may register interest in a shortcut, and only one owner gets it. // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut. @@ -994,7 +1085,7 @@ namespace ImGui // - Many related features are still in imgui_internal.h. For instance, most IsKeyXXX()/IsMouseXXX() functions have an owner-id-aware version. IMGUI_API void SetItemKeyOwner(ImGuiKey key); // Set key owner to last item ID if it is hovered or active. Equivalent to 'if (IsItemHovered() || IsItemActive()) { SetKeyOwner(key, GetItemID());'. - // Inputs Utilities: Mouse specific + // Inputs Utilities: Mouse // - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right. // - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle. // - Dragging operations are only reported after mouse has moved a certain distance away from the initial clicking position (see 'lock_threshold' and 'io.MouseDraggingThreshold') @@ -1002,6 +1093,7 @@ namespace ImGui IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1. IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down) IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true) + IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay); // delayed mouse release (use very sparingly!). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename. IMGUI_API int GetMouseClickedCount(ImGuiMouseButton button); // return the number of successive mouse-clicks at the time where a click happen (otherwise 0). IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block. IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available @@ -1013,7 +1105,7 @@ namespace ImGui IMGUI_API void ResetMouseDragDelta(ImGuiMouseButton button = 0); // IMGUI_API ImGuiMouseCursor GetMouseCursor(); // get desired mouse cursor shape. Important: reset in ImGui::NewFrame(), this is updated during the frame. valid before Render(). If you use software rendering by setting io.MouseDrawCursor ImGui will render those for you IMGUI_API void SetMouseCursor(ImGuiMouseCursor cursor_type); // set desired mouse cursor shape - IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instucts your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. + IMGUI_API void SetNextFrameWantCaptureMouse(bool want_capture_mouse); // Override io.WantCaptureMouse flag next frame (said flag is left for your application to handle, typical when true it instructs your app to ignore inputs). This is equivalent to setting "io.WantCaptureMouse = want_capture_mouse;" after the next NewFrame() call. // Clipboard Utilities // - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard. @@ -1076,8 +1168,8 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoBringToFrontOnFocus = 1 << 13, // Disable bringing window to front when taking focus (e.g. clicking on it or programmatically giving it focus) ImGuiWindowFlags_AlwaysVerticalScrollbar= 1 << 14, // Always show vertical scrollbar (even if ContentSize.y < Size.y) ImGuiWindowFlags_AlwaysHorizontalScrollbar=1<< 15, // Always show horizontal scrollbar (even if ContentSize.x < Size.x) - ImGuiWindowFlags_NoNavInputs = 1 << 16, // No gamepad/keyboard navigation within the window - ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) + ImGuiWindowFlags_NoNavInputs = 1 << 16, // No keyboard/gamepad navigation within the window + ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing toward this window with keyboard/gamepad navigation (e.g. skipped by Ctrl+Tab) ImGuiWindowFlags_UnsavedDocument = 1 << 18, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar. ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse, @@ -1092,13 +1184,13 @@ enum ImGuiWindowFlags_ // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: Use ImGuiChildFlags_AlwaysUseWindowPadding in BeginChild() call. - ImGuiWindowFlags_NavFlattened = 1 << 31, // Obsoleted in 1.90.9: Use ImGuiChildFlags_NavFlattened in BeginChild() call. + //ImGuiWindowFlags_NavFlattened = 1 << 29, // Obsoleted in 1.90.9: moved to ImGuiChildFlags. BeginChild(name, size, 0, ImGuiWindowFlags_NavFlattened) --> BeginChild(name, size, ImGuiChildFlags_NavFlattened, 0) + //ImGuiWindowFlags_AlwaysUseWindowPadding = 1 << 30, // Obsoleted in 1.90.0: moved to ImGuiChildFlags. BeginChild(name, size, 0, ImGuiWindowFlags_AlwaysUseWindowPadding) --> BeginChild(name, size, ImGuiChildFlags_AlwaysUseWindowPadding, 0) #endif }; // Flags for ImGui::BeginChild() -// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Borders to be backward compatible with old API using 'bool border = false'. +// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Borders to be backward compatible with old API using 'bool border = false'.) // About using AutoResizeX/AutoResizeY flags: // - May be combined with SetNextWindowSizeConstraints() to set a min/max size for each axis (see "Demo->Child->Auto-resize with Constraints"). // - Size measurement for a given axis is only performed when the child window is within visible boundaries, or is just appearing. @@ -1117,11 +1209,11 @@ enum ImGuiChildFlags_ ImGuiChildFlags_AutoResizeY = 1 << 5, // Enable auto-resizing height. Read "IMPORTANT: Size measurement" details above. ImGuiChildFlags_AlwaysAutoResize = 1 << 6, // Combined with AutoResizeX/AutoResizeY. Always measure size even when child is hidden, always return true, always disable clipping optimization! NOT RECOMMENDED. ImGuiChildFlags_FrameStyle = 1 << 7, // Style the child window like a framed item: use FrameBg, FrameRounding, FrameBorderSize, FramePadding instead of ChildBg, ChildRounding, ChildBorderSize, WindowPadding. - ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow gamepad/keyboard navigation to cross over parent border to this child or between sibling child windows. + ImGuiChildFlags_NavFlattened = 1 << 8, // [BETA] Share focus scope, allow keyboard/gamepad navigation to cross over parent border to this child or between sibling child windows. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiChildFlags_Border = ImGuiChildFlags_Borders, // Renamed in 1.91.1 (August 2024) for consistency. + //ImGuiChildFlags_Border = ImGuiChildFlags_Borders, // Renamed in 1.91.1 (August 2024) for consistency. #endif }; @@ -1152,7 +1244,7 @@ enum ImGuiInputTextFlags_ // Inputs ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. + ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead! ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode, validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). @@ -1166,13 +1258,25 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + // Elide display / Alignment + ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only! + // Callback features - ImGuiInputTextFlags_CallbackCompletion = 1 << 17, // Callback on pressing TAB (for completion handling) - ImGuiInputTextFlags_CallbackHistory = 1 << 18, // Callback on pressing Up/Down arrows (for history handling) - ImGuiInputTextFlags_CallbackAlways = 1 << 19, // Callback on each iteration. User code may query cursor position, modify text buffer. - ImGuiInputTextFlags_CallbackCharFilter = 1 << 20, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. - ImGuiInputTextFlags_CallbackResize = 1 << 21, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) - ImGuiInputTextFlags_CallbackEdit = 1 << 22, // Callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + ImGuiInputTextFlags_CallbackCompletion = 1 << 18, // Callback on pressing TAB (for completion handling) + ImGuiInputTextFlags_CallbackHistory = 1 << 19, // Callback on pressing Up/Down arrows (for history handling) + ImGuiInputTextFlags_CallbackAlways = 1 << 20, // Callback on each iteration. User code may query cursor position, modify text buffer. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 21, // Callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + ImGuiInputTextFlags_CallbackResize = 1 << 22, // Callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + ImGuiInputTextFlags_CallbackEdit = 1 << 23, // Callback on any edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. + + // Multi-line Word-Wrapping [BETA] + // - Not well tested yet. Please report any incorrect cursor movement, selection behavior etc. bug to https://github.com/ocornut/imgui/issues/3237. + // - Wrapping style is not ideal. Wrapping of long words/sections (e.g. words larger than total available width) may be particularly unpleasing. + // - Wrapping width needs to always account for the possibility of a vertical scrollbar. + // - It is much slower than regular text fields. + // Ballpark estimate of cost on my 2019 desktop PC: for a 100 KB text buffer: +~0.3 ms (Optimized) / +~1.0 ms (Debug build). + // The CPU cost is very roughly proportional to text length, so a 10 KB buffer should cost about ten times less. + ImGuiInputTextFlags_WordWrap = 1 << 24, // InputTextMultiline(): word-wrap lines that are too long. // Obsolete names //ImGuiInputTextFlags_AlwaysInsertMode = ImGuiInputTextFlags_AlwaysOverwrite // [renamed in 1.82] name was not matching behavior @@ -1195,14 +1299,23 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding() before the node. ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (cover the indent area). - ImGuiTreeNodeFlags_SpanTextWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. - ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (text will still fit in current column) - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 15, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + ImGuiTreeNodeFlags_SpanLabelWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. + ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (label will still fit in current column) + ImGuiTreeNodeFlags_LabelSpanAllColumns = 1 << 15, // Label will span all columns of its container table //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 16, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_NavLeftJumpsToParent = 1 << 17, // Nav: left arrow moves back to parent. This is processed in TreePop() when there's an unfulfilled Left nav request remaining. ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, + // [EXPERIMENTAL] Draw lines connecting TreeNode hierarchy. Discuss in GitHub issue #2920. + // Default value is pulled from style.TreeLinesFlags. May be overridden in TreeNode calls. + ImGuiTreeNodeFlags_DrawLinesNone = 1 << 18, // No lines drawn + ImGuiTreeNodeFlags_DrawLinesFull = 1 << 19, // Horizontal lines to child nodes. Vertical line drawn down to TreePop() position: cover full contents. Faster (for large trees). + ImGuiTreeNodeFlags_DrawLinesToNodes = 1 << 20, // Horizontal lines to child nodes. Vertical line drawn down to bottom-most child node. Slower (for large trees). + #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = ImGuiTreeNodeFlags_NavLeftJumpsToParent, // Renamed in 1.92.0 + ImGuiTreeNodeFlags_SpanTextWidth = ImGuiTreeNodeFlags_SpanLabelWidth, // Renamed in 1.90.7 + //ImGuiTreeNodeFlags_AllowItemOverlap = ImGuiTreeNodeFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; @@ -1241,10 +1354,11 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_Disabled = 1 << 3, // Cannot be selected, display grayed out text ImGuiSelectableFlags_AllowOverlap = 1 << 4, // (WIP) Hit testing to allow subsequent widgets to overlap this one ImGuiSelectableFlags_Highlight = 1 << 5, // Make the item be displayed as if it is hovered + ImGuiSelectableFlags_SelectOnNav = 1 << 6, // Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS ImGuiSelectableFlags_DontClosePopups = ImGuiSelectableFlags_NoAutoClosePopups, // Renamed in 1.91.0 - ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 + //ImGuiSelectableFlags_AllowItemOverlap = ImGuiSelectableFlags_AllowOverlap, // Renamed in 1.89.7 #endif }; @@ -1274,10 +1388,17 @@ enum ImGuiTabBarFlags_ ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, // Disable scrolling buttons (apply when fitting policy is ImGuiTabBarFlags_FittingPolicyScroll) ImGuiTabBarFlags_NoTooltip = 1 << 5, // Disable tooltips when hovering a tab ImGuiTabBarFlags_DrawSelectedOverline = 1 << 6, // Draw selected overline markers over selected tab - ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 7, // Resize tabs when they don't fit - ImGuiTabBarFlags_FittingPolicyScroll = 1 << 8, // Add scroll buttons when tabs don't fit - ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, - ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown, + + // Fitting/Resize policy + ImGuiTabBarFlags_FittingPolicyMixed = 1 << 7, // Shrink down tabs when they don't fit, until width is style.TabMinWidthShrink, then enable scrolling buttons. + ImGuiTabBarFlags_FittingPolicyShrink = 1 << 8, // Shrink down tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 9, // Enable scrolling buttons when tabs don't fit + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyMixed | ImGuiTabBarFlags_FittingPolicyShrink | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyMixed, + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiTabBarFlags_FittingPolicyResizeDown = ImGuiTabBarFlags_FittingPolicyShrink, // Renamed in 1.92.2 +#endif }; // Flags for ImGui::BeginTabItem() @@ -1324,7 +1445,7 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_AllowWhenOverlappedByItem = 1 << 8, // IsItemHovered() only: Return true even if the item uses AllowOverlap mode and is overlapped by another hoverable item. ImGuiHoveredFlags_AllowWhenOverlappedByWindow = 1 << 9, // IsItemHovered() only: Return true even if the position is obstructed or overlapped by another window. ImGuiHoveredFlags_AllowWhenDisabled = 1 << 10, // IsItemHovered() only: Return true even if the item is disabled - ImGuiHoveredFlags_NoNavOverride = 1 << 11, // IsItemHovered() only: Disable using gamepad/keyboard navigation state when active, always query mouse + ImGuiHoveredFlags_NoNavOverride = 1 << 11, // IsItemHovered() only: Disable using keyboard/gamepad navigation state when active, always query mouse ImGuiHoveredFlags_AllowWhenOverlapped = ImGuiHoveredFlags_AllowWhenOverlappedByItem | ImGuiHoveredFlags_AllowWhenOverlappedByWindow, ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, @@ -1332,7 +1453,7 @@ enum ImGuiHoveredFlags_ // Tooltips mode // - typically used in IsItemHovered() + SetTooltip() sequence. // - this is a shortcut to pull flags from 'style.HoverFlagsForTooltipMouse' or 'style.HoverFlagsForTooltipNav' where you can reconfigure desired behavior. - // e.g. 'TooltipHoveredFlagsForMouse' defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort'. + // e.g. 'HoverFlagsForTooltipMouse' defaults to 'ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled'. // - for frequently actioned or hovered items providing a tooltip, you want may to use ImGuiHoveredFlags_ForTooltip (stationary + delay) so the tooltip doesn't show too often. // - for items which main purpose is to be hovered, or items with low affordance, or in less consistent apps, prefer no delay or shorter delay. ImGuiHoveredFlags_ForTooltip = 1 << 12, // Shortcut for standard flags when using IsItemHovered() + SetTooltip() sequence. @@ -1364,6 +1485,7 @@ enum ImGuiDragDropFlags_ ImGuiDragDropFlags_AcceptBeforeDelivery = 1 << 10, // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered. ImGuiDragDropFlags_AcceptNoDrawDefaultRect = 1 << 11, // Do not draw the default highlight rectangle when hovering over target. ImGuiDragDropFlags_AcceptNoPreviewTooltip = 1 << 12, // Request hiding the BeginDragDropSource tooltip from the BeginDragDropTarget site. + ImGuiDragDropFlags_AcceptDrawAsHovered = 1 << 13, // Accepting item will render as if hovered. Useful for e.g. a Button() used as a drop target. ImGuiDragDropFlags_AcceptPeekOnly = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect, // For peeking ahead and inspecting the payload before delivery. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1389,6 +1511,7 @@ enum ImGuiDataType_ ImGuiDataType_Float, // float ImGuiDataType_Double, // double ImGuiDataType_Bool, // bool (provided for user convenience, not supported by scalar widgets) + ImGuiDataType_String, // char* (provided for user convenience, not supported by scalar widgets) ImGuiDataType_COUNT }; @@ -1411,21 +1534,18 @@ enum ImGuiSortDirection : ImU8 ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; -// Since 1.90, defining IMGUI_DISABLE_OBSOLETE_FUNCTIONS automatically defines IMGUI_DISABLE_OBSOLETE_KEYIO as well. -#if defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_OBSOLETE_KEYIO) -#define IMGUI_DISABLE_OBSOLETE_KEYIO -#endif - // A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value): can represent Keyboard, Mouse and Gamepad values. -// All our named keys are >= 512. Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87). -// Since >= 1.89 we increased typing (went from int to enum), some legacy code may need a cast to ImGuiKey. -// Read details about the 1.87 and 1.89 transition : https://github.com/ocornut/imgui/issues/4921 +// All our named keys are >= 512. Keys value 0 to 511 are left unused and were legacy native/opaque key values (< 1.87). +// Support for legacy keys was completely removed in 1.91.5. +// Read details about the 1.87+ transition : https://github.com/ocornut/imgui/issues/4921 // Note that "Keys" related to physical keys and are not the same concept as input "Characters", the later are submitted via io.AddInputCharacter(). // The keyboard key enum values are named after the keys on a standard US keyboard, and on other keyboard types the keys reported may not match the keycaps. enum ImGuiKey : int { // Keyboard ImGuiKey_None = 0, + ImGuiKey_NamedKey_BEGIN = 512, // First valid key value (other than 0) + ImGuiKey_Tab = 512, // == ImGuiKey_NamedKey_BEGIN ImGuiKey_LeftArrow, ImGuiKey_RightArrow, @@ -1441,7 +1561,7 @@ enum ImGuiKey : int ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, - ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, + ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_LeftAlt, ImGuiKey_LeftSuper, // Also see ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiMod_Super below! ImGuiKey_RightCtrl, ImGuiKey_RightShift, ImGuiKey_RightAlt, ImGuiKey_RightSuper, ImGuiKey_Menu, ImGuiKey_0, ImGuiKey_1, ImGuiKey_2, ImGuiKey_3, ImGuiKey_4, ImGuiKey_5, ImGuiKey_6, ImGuiKey_7, ImGuiKey_8, ImGuiKey_9, @@ -1479,33 +1599,36 @@ enum ImGuiKey : int ImGuiKey_KeypadEqual, ImGuiKey_AppBack, // Available on some keyboard/mouses. Often referred as "Browser Back" ImGuiKey_AppForward, + ImGuiKey_Oem102, // Non-US backslash. - // Gamepad (some of those are analog values, 0.0f to 1.0f) // NAVIGATION ACTION + // Gamepad + // (analog values are 0.0f to 1.0f) // (download controller mapping PNG/PSD at http://dearimgui.com/controls_sheets) - ImGuiKey_GamepadStart, // Menu (Xbox) + (Switch) Start/Options (PS) - ImGuiKey_GamepadBack, // View (Xbox) - (Switch) Share (PS) - ImGuiKey_GamepadFaceLeft, // X (Xbox) Y (Switch) Square (PS) // Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) - ImGuiKey_GamepadFaceRight, // B (Xbox) A (Switch) Circle (PS) // Cancel / Close / Exit - ImGuiKey_GamepadFaceUp, // Y (Xbox) X (Switch) Triangle (PS) // Text Input / On-screen Keyboard - ImGuiKey_GamepadFaceDown, // A (Xbox) B (Switch) Cross (PS) // Activate / Open / Toggle / Tweak - ImGuiKey_GamepadDpadLeft, // D-pad Left // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadRight, // D-pad Right // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadUp, // D-pad Up // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadDpadDown, // D-pad Down // Move / Tweak / Resize Window (in Windowing mode) - ImGuiKey_GamepadL1, // L Bumper (Xbox) L (Switch) L1 (PS) // Tweak Slower / Focus Previous (in Windowing mode) - ImGuiKey_GamepadR1, // R Bumper (Xbox) R (Switch) R1 (PS) // Tweak Faster / Focus Next (in Windowing mode) - ImGuiKey_GamepadL2, // L Trig. (Xbox) ZL (Switch) L2 (PS) [Analog] - ImGuiKey_GamepadR2, // R Trig. (Xbox) ZR (Switch) R2 (PS) [Analog] - ImGuiKey_GamepadL3, // L Stick (Xbox) L3 (Switch) L3 (PS) - ImGuiKey_GamepadR3, // R Stick (Xbox) R3 (Switch) R3 (PS) - ImGuiKey_GamepadLStickLeft, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickRight, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickUp, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadLStickDown, // [Analog] // Move Window (in Windowing mode) - ImGuiKey_GamepadRStickLeft, // [Analog] - ImGuiKey_GamepadRStickRight, // [Analog] - ImGuiKey_GamepadRStickUp, // [Analog] - ImGuiKey_GamepadRStickDown, // [Analog] + // // XBOX | SWITCH | PLAYSTA. | -> ACTION + ImGuiKey_GamepadStart, // Menu | + | Options | + ImGuiKey_GamepadBack, // View | - | Share | + ImGuiKey_GamepadFaceLeft, // X | Y | Square | Tap: Toggle Menu. Hold: Windowing mode (Focus/Move/Resize windows) + ImGuiKey_GamepadFaceRight, // B | A | Circle | Cancel / Close / Exit + ImGuiKey_GamepadFaceUp, // Y | X | Triangle | Text Input / On-screen Keyboard + ImGuiKey_GamepadFaceDown, // A | B | Cross | Activate / Open / Toggle / Tweak + ImGuiKey_GamepadDpadLeft, // D-pad Left | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadRight, // D-pad Right | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadUp, // D-pad Up | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadDpadDown, // D-pad Down | " | " | Move / Tweak / Resize Window (in Windowing mode) + ImGuiKey_GamepadL1, // L Bumper | L | L1 | Tweak Slower / Focus Previous (in Windowing mode) + ImGuiKey_GamepadR1, // R Bumper | R | R1 | Tweak Faster / Focus Next (in Windowing mode) + ImGuiKey_GamepadL2, // L Trigger | ZL | L2 | [Analog] + ImGuiKey_GamepadR2, // R Trigger | ZR | R2 | [Analog] + ImGuiKey_GamepadL3, // L Stick | L3 | L3 | + ImGuiKey_GamepadR3, // R Stick | R3 | R3 | + ImGuiKey_GamepadLStickLeft, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickRight, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickUp, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadLStickDown, // | | | [Analog] Move Window (in Windowing mode) + ImGuiKey_GamepadRStickLeft, // | | | [Analog] + ImGuiKey_GamepadRStickRight, // | | | [Analog] + ImGuiKey_GamepadRStickUp, // | | | [Analog] + ImGuiKey_GamepadRStickDown, // | | | [Analog] // Aliases: Mouse Buttons (auto-submitted from AddMouseButtonEvent() calls) // - This is mirroring the data also written to io.MouseDown[], io.MouseWheel, in a format allowing them to be accessed via standard key API. @@ -1513,11 +1636,15 @@ enum ImGuiKey : int // [Internal] Reserved for mod storage ImGuiKey_ReservedForModCtrl, ImGuiKey_ReservedForModShift, ImGuiKey_ReservedForModAlt, ImGuiKey_ReservedForModSuper, - ImGuiKey_COUNT, + + // [Internal] If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. + ImGuiKey_NamedKey_END, + ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, // Keyboard Modifiers (explicitly submitted by backend via AddKeyEvent() calls) - // - This is mirroring the data also written to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper, in a format allowing - // them to be accessed via standard key API, allowing calls such as IsKeyPressed(), IsKeyReleased(), querying duration etc. + // - Any functions taking a ImGuiKeyChord parameter can binary-or those with regular keys, e.g. Shortcut(ImGuiMod_Ctrl | ImGuiKey_S). + // - Those are written back into io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper for convenience, + // but may be accessed via standard key API such as IsKeyPressed(), IsKeyReleased(), querying duration etc. // - Code polling every key (e.g. an interface to detect a key press for input mapping) might want to ignore those // and prefer using the real keys (e.g. ImGuiKey_LeftCtrl, ImGuiKey_RightCtrl instead of ImGuiMod_Ctrl). // - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys. @@ -1531,23 +1658,10 @@ enum ImGuiKey : int ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS) ImGuiMod_Mask_ = 0xF000, // 4-bits - // [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array. - // We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE) - // If you need to iterate all keys (for e.g. an input mapper) you may use ImGuiKey_NamedKey_BEGIN..ImGuiKey_NamedKey_END. - ImGuiKey_NamedKey_BEGIN = 512, - ImGuiKey_NamedKey_END = ImGuiKey_COUNT, - ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, -#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO - ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#else - ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys - ImGuiKey_KeysData_OFFSET = 0, // Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET) index. -#endif - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiKey_COUNT = ImGuiKey_NamedKey_END, // Obsoleted in 1.91.5 because it was misleading (since named keys don't start at 0 anymore) ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl - ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 + //ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89 //ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87 #endif }; @@ -1569,7 +1683,7 @@ enum ImGuiInputFlags_ ImGuiInputFlags_RouteAlways = 1 << 13, // Do not register route, poll keys directly. // - Routing options ImGuiInputFlags_RouteOverFocused = 1 << 14, // Option: global route: higher priority than focused route (unless active item in focused route). - ImGuiInputFlags_RouteOverActive = 1 << 15, // Option: global route: higher priority than active item. Unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overridden by this. May not be fully honored as user/internal code is likely to always assume they can access keys when active. + ImGuiInputFlags_RouteOverActive = 1 << 15, // Option: global route: higher priority than active item. Unlikely you need to use that: will interfere with every active items, e.g. Ctrl+A registered by InputText will be overridden by this. May not be fully honored as user/internal code is likely to always assume they can access keys when active. ImGuiInputFlags_RouteUnlessBgFocused = 1 << 16, // Option: global route: will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. ImGuiInputFlags_RouteFromRootWindow = 1 << 17, // Option: route evaluated from the point of view of root window rather than current window. @@ -1577,26 +1691,12 @@ enum ImGuiInputFlags_ ImGuiInputFlags_Tooltip = 1 << 18, // Automatically display a tooltip when hovering item [BETA] Unsure of right api (opt-in/opt-out) }; -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO -// OBSOLETED in 1.88 (from July 2022): ImGuiNavInput and io.NavInputs[]. -// Official backends between 1.60 and 1.86: will keep working and feed gamepad inputs as long as IMGUI_DISABLE_OBSOLETE_KEYIO is not set. -// Custom backends: feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. -enum ImGuiNavInput -{ - ImGuiNavInput_Activate, ImGuiNavInput_Cancel, ImGuiNavInput_Input, ImGuiNavInput_Menu, ImGuiNavInput_DpadLeft, ImGuiNavInput_DpadRight, ImGuiNavInput_DpadUp, ImGuiNavInput_DpadDown, - ImGuiNavInput_LStickLeft, ImGuiNavInput_LStickRight, ImGuiNavInput_LStickUp, ImGuiNavInput_LStickDown, ImGuiNavInput_FocusPrev, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakSlow, ImGuiNavInput_TweakFast, - ImGuiNavInput_COUNT, -}; -#endif - // Configuration flags stored in io.ConfigFlags. Set by user/application. enum ImGuiConfigFlags_ { ImGuiConfigFlags_None = 0, ImGuiConfigFlags_NavEnableKeyboard = 1 << 0, // Master keyboard navigation enable flag. Enable full Tabbing + directional arrows + space/enter to activate. ImGuiConfigFlags_NavEnableGamepad = 1 << 1, // Master gamepad navigation enable flag. Backend also needs to set ImGuiBackendFlags_HasGamepad. - ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // Instruct navigation to move the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is awkward. Will update io.MousePos and set io.WantSetMousePos=true. If enabled you MUST honor io.WantSetMousePos requests in your backend, otherwise ImGui will react as if the mouse is jumping around back and forth. - ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // Instruct navigation to not set the io.WantCaptureKeyboard flag when io.NavActive is set. ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct dear imgui to disable mouse inputs and interactions. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. ImGuiConfigFlags_NoKeyboard = 1 << 6, // Instruct dear imgui to disable keyboard inputs and interactions. This is done by ignoring keyboard events and clearing existing states. @@ -1604,6 +1704,11 @@ enum ImGuiConfigFlags_ // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21, // Application is using a touch screen instead of a mouse. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiConfigFlags_NavEnableSetMousePos = 1 << 2, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavMoveSetMousePos + ImGuiConfigFlags_NavNoCaptureKeyboard = 1 << 3, // [moved/renamed in 1.91.4] -> use bool io.ConfigNavCaptureKeyboard +#endif }; // Backend capabilities flags stored in io.BackendFlags. Set by imgui_impl_xxx or custom backend. @@ -1612,8 +1717,9 @@ enum ImGuiBackendFlags_ ImGuiBackendFlags_None = 0, ImGuiBackendFlags_HasGamepad = 1 << 0, // Backend Platform supports gamepad and currently has one connected. ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Backend Platform supports honoring GetMouseCursor() value to change the OS cursor shape. - ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). + ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Backend Platform supports io.WantSetMousePos requests to reposition the OS mouse position (only used if io.ConfigNavMoveSetMousePos is set). ImGuiBackendFlags_RendererHasVtxOffset = 1 << 3, // Backend Renderer supports ImDrawCmd::VtxOffset. This enables output of large meshes (64K+ vertices) while still using 16-bit indices. + ImGuiBackendFlags_RendererHasTextures = 1 << 4, // Backend Renderer supports ImTextureData requests to create/update/destroy textures. This enables incremental texture updates and texture reloads. See https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md for instructions on how to upgrade your custom backend. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -1652,6 +1758,7 @@ enum ImGuiCol_ ImGuiCol_ResizeGrip, // Resize grip in lower-right and lower-left corners of windows. ImGuiCol_ResizeGripHovered, ImGuiCol_ResizeGripActive, + ImGuiCol_InputTextCursor, // InputText cursor/caret ImGuiCol_TabHovered, // Tab background, when hovered ImGuiCol_Tab, // Tab background, when tab-bar is focused & tab is unselected ImGuiCol_TabSelected, // Tab background, when tab-bar is focused & tab is selected @@ -1669,11 +1776,14 @@ enum ImGuiCol_ ImGuiCol_TableRowBg, // Table row background (even rows) ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextLink, // Hyperlink color - ImGuiCol_TextSelectedBg, - ImGuiCol_DragDropTarget, // Rectangle highlighting a drop target - ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item - ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB - ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active + ImGuiCol_TextSelectedBg, // Selected text inside an InputText + ImGuiCol_TreeLines, // Tree node hierarchy outlines when using ImGuiTreeNodeFlags_DrawLines + ImGuiCol_DragDropTarget, // Rectangle border highlighting a drop target + ImGuiCol_DragDropTargetBg, // Rectangle background highlighting a drop target + ImGuiCol_UnsavedMarker, // Unsaved Document marker (in window title and tabs) + ImGuiCol_NavCursor, // Color of keyboard/gamepad navigation cursor/rectangle, when visible + ImGuiCol_NavWindowingHighlight, // Highlight window when using Ctrl+Tab + ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the Ctrl+Tab window list, when active ImGuiCol_ModalWindowDimBg, // Darken/colorize entire screen behind a modal window, when one is active ImGuiCol_COUNT, @@ -1681,6 +1791,7 @@ enum ImGuiCol_ ImGuiCol_TabActive = ImGuiCol_TabSelected, // [renamed in 1.90.9] ImGuiCol_TabUnfocused = ImGuiCol_TabDimmed, // [renamed in 1.90.9] ImGuiCol_TabUnfocusedActive = ImGuiCol_TabDimmedSelected, // [renamed in 1.90.9] + ImGuiCol_NavHighlight = ImGuiCol_NavCursor, // [renamed in 1.91.4] #endif }; @@ -1688,9 +1799,9 @@ enum ImGuiCol_ // - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. // During initialization or between frames, feel free to just poke into ImGuiStyle directly. // - Tip: Use your programming IDE navigation facilities on the names in the _second column_ below to find the actual members and their description. -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. -// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - In Visual Studio: Ctrl+Comma ("Edit.GoToAll") can follow symbols inside comments, whereas Ctrl+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. // - When changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. enum ImGuiStyleVar_ { @@ -1715,14 +1826,20 @@ enum ImGuiStyleVar_ ImGuiStyleVar_CellPadding, // ImVec2 CellPadding ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_ScrollbarPadding, // float ScrollbarPadding ImGuiStyleVar_GrabMinSize, // float GrabMinSize ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_ImageBorderSize, // float ImageBorderSize ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_TabBorderSize, // float TabBorderSize + ImGuiStyleVar_TabMinWidthBase, // float TabMinWidthBase + ImGuiStyleVar_TabMinWidthShrink, // float TabMinWidthShrink ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize ImGuiStyleVar_TabBarOverlineSize, // float TabBarOverlineSize ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign + ImGuiStyleVar_TreeLinesSize, // float TreeLinesSize + ImGuiStyleVar_TreeLinesRounding, // float TreeLinesRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize @@ -1739,7 +1856,7 @@ enum ImGuiButtonFlags_ ImGuiButtonFlags_MouseButtonRight = 1 << 1, // React on right mouse button ImGuiButtonFlags_MouseButtonMiddle = 1 << 2, // React on center mouse button ImGuiButtonFlags_MouseButtonMask_ = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight | ImGuiButtonFlags_MouseButtonMiddle, // [Internal] - //ImGuiButtonFlags_MouseButtonDefault_ = ImGuiButtonFlags_MouseButtonLeft, + ImGuiButtonFlags_EnableNav = 1 << 3, // InvisibleButton(): do not disable navigation/tabbing. Otherwise disabled by default. }; // Flags for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4() / ColorButton() @@ -1757,10 +1874,16 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_NoDragDrop = 1 << 9, // // ColorEdit: disable drag and drop target. ColorButton: disable drag and drop source. ImGuiColorEditFlags_NoBorder = 1 << 10, // // ColorButton: disable border (which is enforced by default) + // Alpha preview + // - Prior to 1.91.8 (2025/01/21): alpha was made opaque in the preview by default using old name ImGuiColorEditFlags_AlphaPreview. + // - We now display the preview as transparent by default. You can use ImGuiColorEditFlags_AlphaOpaque to use old behavior. + // - The new flags may be combined better and allow finer controls. + ImGuiColorEditFlags_AlphaOpaque = 1 << 11, // // ColorEdit, ColorPicker, ColorButton: disable alpha in the preview,. Contrary to _NoAlpha it may still be edited when calling ColorEdit4()/ColorPicker4(). For ColorButton() this does the same as _NoAlpha. + ImGuiColorEditFlags_AlphaNoBg = 1 << 12, // // ColorEdit, ColorPicker, ColorButton: disable rendering a checkerboard background behind transparent color. + ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 13, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half transparent preview. + // User Options (right-click on widget to change some of them). ImGuiColorEditFlags_AlphaBar = 1 << 16, // // ColorEdit, ColorPicker: show vertical alpha bar/gradient in picker. - ImGuiColorEditFlags_AlphaPreview = 1 << 17, // // ColorEdit, ColorPicker, ColorButton: display preview as a transparent color over a checkerboard, instead of opaque. - ImGuiColorEditFlags_AlphaPreviewHalf= 1 << 18, // // ColorEdit, ColorPicker, ColorButton: display half opaque / half checkerboard, instead of opaque. ImGuiColorEditFlags_HDR = 1 << 19, // // (WIP) ColorEdit: Currently only disable 0.0f..1.0f limits in RGBA edition (note: you probably want to use ImGuiColorEditFlags_Float flag as well). ImGuiColorEditFlags_DisplayRGB = 1 << 20, // [Display] // ColorEdit: override _display_ type among RGB/HSV/Hex. ColorPicker: select any combination using one or more of RGB/HSV/Hex. ImGuiColorEditFlags_DisplayHSV = 1 << 21, // [Display] // " @@ -1777,12 +1900,16 @@ enum ImGuiColorEditFlags_ ImGuiColorEditFlags_DefaultOptions_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_PickerHueBar, // [Internal] Masks + ImGuiColorEditFlags_AlphaMask_ = ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque | ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf, ImGuiColorEditFlags_DisplayMask_ = ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_DisplayHex, ImGuiColorEditFlags_DataTypeMask_ = ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_Float, ImGuiColorEditFlags_PickerMask_ = ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_PickerHueBar, ImGuiColorEditFlags_InputMask_ = ImGuiColorEditFlags_InputRGB | ImGuiColorEditFlags_InputHSV, // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiColorEditFlags_AlphaPreview = 0, // Removed in 1.91.8. This is the default now. Will display a checkerboard unless ImGuiColorEditFlags_AlphaNoBg is set. +#endif //ImGuiColorEditFlags_RGB = ImGuiColorEditFlags_DisplayRGB, ImGuiColorEditFlags_HSV = ImGuiColorEditFlags_DisplayHSV, ImGuiColorEditFlags_HEX = ImGuiColorEditFlags_DisplayHex // [renamed in 1.69] }; @@ -1794,10 +1921,11 @@ enum ImGuiSliderFlags_ ImGuiSliderFlags_None = 0, ImGuiSliderFlags_Logarithmic = 1 << 5, // Make the widget logarithmic (linear otherwise). Consider using ImGuiSliderFlags_NoRoundToFormat with this if using a format-string with small amount of digits. ImGuiSliderFlags_NoRoundToFormat = 1 << 6, // Disable rounding underlying value to match precision of the display format string (e.g. %.3f values are rounded to those 3 digits). - ImGuiSliderFlags_NoInput = 1 << 7, // Disable CTRL+Click or Enter key allowing to input text directly into the widget. + ImGuiSliderFlags_NoInput = 1 << 7, // Disable Ctrl+Click or Enter key allowing to input text directly into the widget. ImGuiSliderFlags_WrapAround = 1 << 8, // Enable wrapping around from max to min and from min to max. Only supported by DragXXX() functions for now. - ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds. + ImGuiSliderFlags_ClampOnInput = 1 << 9, // Clamp value to min/max bounds when input manually with Ctrl+Click. By default Ctrl+Click allows going out of bounds. ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it. + ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic. ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange, ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. }; @@ -1825,6 +1953,8 @@ enum ImGuiMouseCursor_ ImGuiMouseCursor_ResizeNESW, // When hovering over the bottom-left corner of a window ImGuiMouseCursor_ResizeNWSE, // When hovering over the bottom-right corner of a window ImGuiMouseCursor_Hand, // (Unused by Dear ImGui functions. Use for e.g. hyperlinks) + ImGuiMouseCursor_Wait, // When waiting for something to process/load. + ImGuiMouseCursor_Progress, // When waiting for something to process/load, but application is still interactive. ImGuiMouseCursor_NotAllowed, // When hovering something with disallowed interaction. Usually a crossed circle. ImGuiMouseCursor_COUNT }; @@ -2073,7 +2203,7 @@ struct ImVector // Constructors, destructor inline ImVector() { Size = Capacity = 0; Data = NULL; } inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } - inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); if (src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); if (Data && src.Data) memcpy(Data, src.Data, (size_t)Size * sizeof(T)); return *this; } inline ~ImVector() { if (Data) IM_FREE(Data); } // Important: does not destruct anything inline void clear() { if (Data) { Size = Capacity = 0; IM_FREE(Data); Data = NULL; } } // Important: does not destruct anything @@ -2133,11 +2263,18 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE struct ImGuiStyle { + // Font scaling + // - recap: ImGui::GetFontSize() == FontSizeBase * (FontScaleMain * FontScaleDpi * other_scaling_factors) + float FontSizeBase; // Current base font size before external global factors are applied. Use PushFont(NULL, size) to modify. Use ImGui::GetFontSize() to obtain scaled value. + float FontScaleMain; // Main global scale factor. May be set by application once, or exposed to end-user. + float FontScaleDpi; // Additional global scale factor from viewport/monitor contents scale. When io.ConfigDpiScaleFonts is enabled, this is automatically overwritten when changing monitor DPI. + float Alpha; // Global alpha applies to everything in Dear ImGui. float DisabledAlpha; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. ImVec2 WindowPadding; // Padding within a window. float WindowRounding; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. float WindowBorderSize; // Thickness of border around windows. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). + float WindowBorderHoverPadding; // Hit-testing extent outside/inside resizing border. Also extend determination of hovered window. Generally meaningfully larger than WindowBorderSize to make it easy to reach borders. ImVec2 WindowMinSize; // Minimum window size. This is a global setting. If you want to constrain individual windows, use SetNextWindowSizeConstraints(). ImVec2 WindowTitleAlign; // Alignment for title bar text. Defaults to (0.0f,0.5f) for left-aligned,vertically centered. ImGuiDir WindowMenuButtonPosition; // Side of the collapsing/docking button in the title bar (None/Left/Right). Defaults to ImGuiDir_Left. @@ -2156,16 +2293,27 @@ struct ImGuiStyle float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). float ScrollbarSize; // Width of the vertical scrollbar, Height of the horizontal scrollbar. float ScrollbarRounding; // Radius of grab corners for scrollbar. + float ScrollbarPadding; // Padding of scrollbar grab within its frame (same for both axes). float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. float LogSliderDeadzone; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + float ImageBorderSize; // Thickness of border around Image() calls. float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. float TabBorderSize; // Thickness of border around tabs. - float TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + float TabMinWidthBase; // Minimum tab width, to make tabs larger than their contents. TabBar buttons are not affected. + float TabMinWidthShrink; // Minimum tab width after shrinking, when using ImGuiTabBarFlags_FittingPolicyMixed policy. + float TabCloseButtonMinWidthSelected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. + float TabCloseButtonMinWidthUnselected; // -1: always visible. 0.0f: visible when hovered. >0.0f: visible when hovered if minimum width. FLT_MAX: never show close button when unselected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. float TabBarOverlineSize; // Thickness of tab-bar overline, which highlights the selected tab-bar. float TableAngledHeadersAngle; // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees). ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell + ImGuiTreeNodeFlags TreeLinesFlags; // Default way to draw lines connecting TreeNode hierarchy. ImGuiTreeNodeFlags_DrawLinesNone or ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes. + float TreeLinesSize; // Thickness of outlines when using ImGuiTreeNodeFlags_DrawLines. + float TreeLinesRounding; // Radius of lines connecting child nodes to the vertical line. + float DragDropTargetRounding; // Radius of the drag and drop target frame. + float DragDropTargetBorderSize; // Thickness of the drag and drop target border. + float DragDropTargetPadding; // Size to expand the drag and drop target from actual target item size. ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -2180,6 +2328,8 @@ struct ImGuiStyle bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + + // Colors ImVec4 Colors[ImGuiCol_COUNT]; // Behaviors @@ -2190,8 +2340,18 @@ struct ImGuiStyle ImGuiHoveredFlags HoverFlagsForTooltipMouse;// Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. ImGuiHoveredFlags HoverFlagsForTooltipNav; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. - IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); + // [Internal] + float _MainScale; // FIXME-WIP: Reference scale, as applied by ScaleAllSizes(). + float _NextFrameFontSizeBase; // FIXME: Temporary hack until we finish remaining work. + + // Functions + IMGUI_API ImGuiStyle(); + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. + + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9. +#endif }; //----------------------------------------------------------------------------- @@ -2222,9 +2382,10 @@ struct ImGuiIO // Configuration // Default value //------------------------------------------------------------------ - ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. + ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Keyboard/Gamepad navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // See ImGuiBackendFlags_ enum. Set by backend (imgui_impl_xxx files or custom backend) to communicate features supported by the backend. - ImVec2 DisplaySize; // // Main display size, in pixels (generally == GetMainViewport()->Size). May change every frame. + ImVec2 DisplaySize; // // Main display size, in pixels (== GetMainViewport()->Size). May change every frame. + ImVec2 DisplayFramebufferScale; // = (1, 1) // Main display density. For retina display where window coordinates are different from framebuffer coordinates. This will affect font density + will end up in ImDrawData::FramebufferScale. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. May change every frame. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file (important: default "imgui.ini" is relative to current working dir!). Set NULL to disable automatic .ini loading/saving or if you want to manually call LoadIniSettingsXXX() / SaveIniSettingsXXX() functions. @@ -2233,22 +2394,29 @@ struct ImGuiIO // Font system ImFontAtlas*Fonts; // // Font atlas: load, rasterize and pack one or more fonts into a single texture. - float FontGlobalScale; // = 1.0f // Global scale all fonts - bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. - ImVec2 DisplayFramebufferScale; // = (1, 1) // For retina display or other situations where window coordinates are different from framebuffer coordinates. This generally ends up in ImDrawData::FramebufferScale. + bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with Ctrl+Wheel. + + // Keyboard/Gamepad Navigation options + bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. + bool ConfigNavMoveSetMousePos; // = false // Directional/tabbing navigation teleports the mouse cursor. May be useful on TV/console systems where moving a virtual mouse is difficult. Will update io.MousePos and set io.WantSetMousePos=true. + bool ConfigNavCaptureKeyboard; // = true // Sets io.WantCaptureKeyboard when io.NavActive is set. + bool ConfigNavEscapeClearFocusItem; // = true // Pressing Escape can clear focused item + navigation id/highlight. Set to false if you want to always keep highlight on. + bool ConfigNavEscapeClearFocusWindow;// = false // Pressing Escape can clear focused window as well (super set of io.ConfigNavEscapeClearFocusItem). + bool ConfigNavCursorVisibleAuto; // = true // Using directional navigation key makes the cursor visible. Mouse click hides the cursor. + bool ConfigNavCursorVisibleAlways; // = false // Navigation cursor is always visible. // Miscellaneous options // (you can visualize and interact with all options in 'Demo->Configuration') bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by backend implementations. bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // Swap Cmd<>Ctrl keys + OS X style text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl. - bool ConfigNavSwapGamepadButtons; // = false // Swap Activate<>Cancel (A<>B) buttons, matching typical "Nintendo/Japanese style" gamepad layout. bool ConfigInputTrickleEventQueue; // = true // Enable input queue trickling: some types of events submitted during the same frame (e.g. button down + up) will be spread over multiple frames, improving interactions with low framerates. bool ConfigInputTextCursorBlink; // = true // Enable blinking cursor (optional as some users consider it to be distracting). bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will keep item active and select contents (single-line only). bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard. - bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) - bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar. + bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order. bool ConfigScrollbarScrollByPage; // = true // Enable scrolling page by page when clicking outside the scrollbar grab. When disabled, always scroll to clicked location. When enabled, Shift+Click scrolls to clicked location. float ConfigMemoryCompactTimer; // = 60.0f // Timer (in seconds) to free transient windows/tables memory buffers when unused. Set to -1.0f to disable. @@ -2286,14 +2454,15 @@ struct ImGuiIO // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro. // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability. // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application. - // e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version). + // e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version. bool ConfigDebugIsDebuggerPresent; // = false // Enable various tools calling IM_DEBUG_BREAK(). // Tools to detect code submitting items with conflicting/duplicate IDs // - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers. // - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead! // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system - bool ConfigDebugHighlightIdConflicts;// = true // Highlight and show an error message when multiple items have conflicting identifiers. + bool ConfigDebugHighlightIdConflicts;// = true // Highlight and show an error message popup when multiple items have conflicting identifiers. + bool ConfigDebugHighlightIdConflictsShowItemPicker;//=true // Show "Item Picker" button in aforementioned popup. // Tools to test correct Begin/End and BeginChild/EndChild behaviors. // - Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX() @@ -2315,6 +2484,7 @@ struct ImGuiIO // (the imgui_impl_xxxx backend files are setting those up for you) //------------------------------------------------------------------ + // Nowadays those would be stored in ImGuiPlatformIO but we are leaving them here for legacy reasons. // Optional: Platform/Renderer backend name (informational only! will be displayed in About Window) + User data for backend/wrappers to store their own stuff. const char* BackendPlatformName; // = NULL const char* BackendRendererName; // = NULL @@ -2343,9 +2513,6 @@ struct ImGuiIO IMGUI_API void ClearEventsQueue(); // Clear all incoming events. IMGUI_API void ClearInputKeys(); // Clear current keyboard/gamepad state + current frame text input buffer. Equivalent to releasing all keys/buttons. IMGUI_API void ClearInputMouse(); // Clear current mouse state. -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - IMGUI_API void ClearInputCharacters(); // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys(). -#endif //------------------------------------------------------------------ // Output - Updated by NewFrame() or EndFrame()/Render() @@ -2356,10 +2523,10 @@ struct ImGuiIO bool WantCaptureMouse; // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.). bool WantCaptureKeyboard; // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.). bool WantTextInput; // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active). - bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled. + bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when io.ConfigNavMoveSetMousePos is enabled. bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving! bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag. - bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events). + bool NavVisible; // Keyboard/Gamepad navigation highlight is visible and allowed (will handle ImGuiKey_NavXXX events). float Framerate; // Estimate of application framerate (rolling average over 60 frames, based on io.DeltaTime), in frame per second. Solely for convenience. Slow applications may not want to use a moving average or may want to reset underlying buffers occasionally. int MetricsRenderVertices; // Vertices output during last call to Render() int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3 @@ -2378,17 +2545,17 @@ struct ImGuiIO // (reading from those variables is fair game, as they are extremely unlikely to be moving anywhere) ImVec2 MousePos; // Mouse position, in pixels. Set to ImVec2(-FLT_MAX, -FLT_MAX) if mouse is unavailable (on another screen, etc.) bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras (ImGuiMouseButton_COUNT == 5). Dear ImGui mostly uses left and right buttons. Other buttons allow us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. - float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold SHIFT to turn vertical scroll into horizontal scroll. + float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. >0 scrolls Up, <0 scrolls Down. Hold Shift to turn vertical scroll into horizontal scroll. float MouseWheelH; // Mouse wheel Horizontal. >0 scrolls Left, <0 scrolls Right. Most users don't have a mouse with a horizontal wheel, may not be filled by all backends. ImGuiMouseSource MouseSource; // Mouse actual input peripheral (Mouse/TouchScreen/Pen). - bool KeyCtrl; // Keyboard modifier down: Control + bool KeyCtrl; // Keyboard modifier down: Ctrl (non-macOS), Cmd (macOS) bool KeyShift; // Keyboard modifier down: Shift bool KeyAlt; // Keyboard modifier down: Alt - bool KeySuper; // Keyboard modifier down: Cmd/Super/Windows + bool KeySuper; // Keyboard modifier down: Windows/Super (non-macOS), Ctrl (macOS) // Other state maintained from data above + IO function calls - ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags. Read-only, updated by NewFrame() - ImGuiKeyData KeysData[ImGuiKey_KeysData_SIZE]; // Key state for all known keys. Use IsKeyXXX() functions to access this. + ImGuiKeyChord KeyMods; // Key mods flags (any of ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Alt/ImGuiMod_Super flags, same as io.KeyCtrl/KeyShift/KeyAlt/KeySuper but merged into flags). Read-only, updated by NewFrame() + ImGuiKeyData KeysData[ImGuiKey_NamedKey_COUNT];// Key state for all known keys. MUST use 'key - ImGuiKey_NamedKey_BEGIN' as index. Use IsKeyXXX() functions to access this. bool WantCaptureMouseUnlessPopupClose; // Alternative to WantCaptureMouse: (WantCaptureMouse == true && WantCaptureMouseUnlessPopupClose == false) when a click over void is expected to close a popup. ImVec2 MousePosPrev; // Previous mouse position (note that MouseDelta is not necessary == MousePos-MousePosPrev, in case either position is invalid) ImVec2 MouseClickedPos[5]; // Position at time of clicking @@ -2398,37 +2565,40 @@ struct ImGuiIO ImU16 MouseClickedCount[5]; // == 0 (not clicked), == 1 (same as MouseClicked[]), == 2 (double-clicked), == 3 (triple-clicked) etc. when going from !Down to Down ImU16 MouseClickedLastCount[5]; // Count successive number of clicks. Stays valid after mouse release. Reset after another click is done. bool MouseReleased[5]; // Mouse button went from Down to !Down + double MouseReleasedTime[5]; // Time of last released (rarely used! but useful to handle delayed single-click when trying to disambiguate them from double-click). bool MouseDownOwned[5]; // Track if button was clicked inside a dear imgui window or over void blocked by a popup. We don't request mouse capture from the application if click started outside ImGui bounds. bool MouseDownOwnedUnlessPopupClose[5]; // Track if button was clicked inside a dear imgui window. - bool MouseWheelRequestAxisSwap; // On a non-Mac system, holding SHIFT requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. - bool MouseCtrlLeftAsRightClick; // (OSX) Set to true when the current click was a ctrl-click that spawned a simulated right click + bool MouseWheelRequestAxisSwap; // On a non-Mac system, holding Shift requests WheelY to perform the equivalent of a WheelX event. On a Mac system this is already enforced by the system. + bool MouseCtrlLeftAsRightClick; // (OSX) Set to true when the current click was a Ctrl+Click that spawned a simulated right click float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) float MouseDownDurationPrev[5]; // Previous time the mouse button has been down float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the clicking point (used for moving thresholds) float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. bool AppFocusLost; // Only modify via AddFocusEvent() bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() - ImS8 BackendUsingLegacyKeyArrays; // -1: unknown, 0: using AddKeyEvent(), 1: using legacy io.KeysDown[] - bool BackendUsingLegacyNavInputArray; // 0: using AddKeyAnalogEvent(), 1: writing to legacy io.NavInputs[] directly ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. - bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. - float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. + // Old (<1.87): ImGui::IsKeyPressed(MYPLATFORM_KEY_SPACE) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) + // Read https://github.com/ocornut/imgui/issues/4921 for details. + //int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. + //bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. + //float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. -#endif + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float FontGlobalScale; // Moved io.FontGlobalScale to style.FontScaleMain in 1.92 (June 2025) // Legacy: before 1.91.1, clipboard functions were stored in ImGuiIO instead of ImGuiPlatformIO. // As this is will affect all users of custom engines/backends, we are providing proper legacy redirection (will obsolete). -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const char* (*GetClipboardTextFn)(void* user_data); void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; + + //void ClearInputCharacters() { InputQueueCharacters.resize(0); } // [Obsoleted in 1.89.8] Clear the current frame text input buffer. Now included within ClearInputKeys(). Removed this as it is ambiguous/misleading and generally incorrect to use with the existence of a higher-level input queue. #endif IMGUI_API ImGuiIO(); @@ -2441,7 +2611,7 @@ struct ImGuiIO // Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used. // The callback function should return 0 by default. // Callbacks (follow a flag name and see comments in ImGuiInputTextFlags_ declarations for more details) -// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) +// - ImGuiInputTextFlags_CallbackEdit: Callback on buffer edit. Note that InputText() already returns true on edit + you can always use IsItemEdited(). The callback is useful to manipulate the underlying buffer while focus is active. // - ImGuiInputTextFlags_CallbackAlways: Callback on each iteration // - ImGuiInputTextFlags_CallbackCompletion: Callback on pressing TAB // - ImGuiInputTextFlags_CallbackHistory: Callback on pressing Up/Down arrows @@ -2463,10 +2633,10 @@ struct ImGuiInputTextCallbackData ImGuiKey EventKey; // Key pressed (Up/Down/TAB) // Read-only // [Completion,History] char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() - int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 + int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 bool BufDirty; // Set if you modify Buf/BufTextLen! // Write // [Completion,History,Always] int CursorPos; // // Read-write // [Completion,History,Always] - int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection) + int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection int SelectionEnd; // // Read-write // [Completion,History,Always] // Helper functions for text manipulation. @@ -2568,10 +2738,11 @@ struct ImGuiTextBuffer ImGuiTextBuffer() { } inline char operator[](int i) const { IM_ASSERT(Buf.Data != NULL); return Buf.Data[i]; } const char* begin() const { return Buf.Data ? &Buf.front() : EmptyString; } - const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator + const char* end() const { return Buf.Data ? &Buf.back() : EmptyString; } // Buf is zero-terminated, so end() will point on the zero-terminator int size() const { return Buf.Size ? Buf.Size - 1 : 0; } bool empty() const { return Buf.Size <= 1; } void clear() { Buf.clear(); } + void resize(int size) { if (Buf.Size > size) Buf.Data[size] = 0; Buf.resize(size ? size + 1 : 0, 0); } // Similar to resize(0) on ImVector: empty string but don't free buffer. void reserve(int capacity) { Buf.reserve(capacity); } const char* c_str() const { return Buf.Data ? Buf.Data : EmptyString; } IMGUI_API void append(const char* str, const char* str_end = NULL); @@ -2634,6 +2805,13 @@ struct ImGuiStorage #endif }; +// Flags for ImGuiListClipper (currently not fully exposed in function calls: a future refactor will likely add this to ImGuiListClipper::Begin function equivalent) +enum ImGuiListClipperFlags_ +{ + ImGuiListClipperFlags_None = 0, + ImGuiListClipperFlags_NoSetTableRowCounters = 1 << 0, // [Internal] Disabled modifying table row counters. Avoid assumption that 1 clipper item == 1 table row. +}; + // Helper: Manually clip large list of items. // If you have lots evenly spaced items and you have random access to the list, you can perform coarse // clipping based on visibility to only submit items that are in view. @@ -2661,9 +2839,10 @@ struct ImGuiListClipper int DisplayEnd; // End of items to display (exclusive) int ItemsCount; // [Internal] Number of items float ItemsHeight; // [Internal] Height of item after a first step and item submission can calculate it - float StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed + double StartPosY; // [Internal] Cursor position at the time of Begin() or after table frozen rows are all processed double StartSeekOffsetY; // [Internal] Account for frozen rows in a table and initial loss of precision in very large windows. void* TempData; // [Internal] Internal data + ImGuiListClipperFlags Flags; // [Internal] Flags, currently not yet well exposed. // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step, and you can call SeekCursorForItem() manually if you need) // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). @@ -2684,8 +2863,8 @@ struct ImGuiListClipper IMGUI_API void SeekCursorForItem(int item_index); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] - inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] + //inline void IncludeRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.9] + //inline void ForceDisplayRangeByIndices(int item_begin, int item_end) { IncludeItemsByIndex(item_begin, item_end); } // [renamed in 1.89.6] //inline ImGuiListClipper(int items_count, float items_height = -1.0f) { memset(this, 0, sizeof(*this)); ItemsCount = -1; Begin(items_count, items_height); } // [removed in 1.79] #endif }; @@ -2694,34 +2873,42 @@ struct ImGuiListClipper // - It is important that we are keeping those disabled by default so they don't leak in user space. // - This is in order to allow user enabling implicit cast operators between ImVec2/ImVec4 and their own types (using IM_VEC2_CLASS_EXTRA in imconfig.h) // - Add '#define IMGUI_DEFINE_MATH_OPERATORS' before including this file (or in imconfig.h) to access courtesy maths operators for ImVec2 and ImVec4. +// - We intentionally provide ImVec2*float but not float*ImVec2: this is rare enough and we want to reduce the surface for possible user mistake. #ifdef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS_IMPLEMENTED IM_MSVC_RUNTIME_CHECKS_OFF -static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } -static inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } -static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } -static inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } -static inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } -static inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } -static inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } -static inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } -static inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } -static inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } -static inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } -static inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } -static inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } -static inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } -static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } -static inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } -static inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } +// ImVec2 operators +inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); } +inline ImVec2 operator/(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); } +inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); } +inline ImVec2 operator*(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline ImVec2 operator/(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x / rhs.x, lhs.y / rhs.y); } +inline ImVec2 operator-(const ImVec2& lhs) { return ImVec2(-lhs.x, -lhs.y); } +inline ImVec2& operator*=(ImVec2& lhs, const float rhs) { lhs.x *= rhs; lhs.y *= rhs; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const float rhs) { lhs.x /= rhs; lhs.y /= rhs; return lhs; } +inline ImVec2& operator+=(ImVec2& lhs, const ImVec2& rhs) { lhs.x += rhs.x; lhs.y += rhs.y; return lhs; } +inline ImVec2& operator-=(ImVec2& lhs, const ImVec2& rhs) { lhs.x -= rhs.x; lhs.y -= rhs.y; return lhs; } +inline ImVec2& operator*=(ImVec2& lhs, const ImVec2& rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; return lhs; } +inline ImVec2& operator/=(ImVec2& lhs, const ImVec2& rhs) { lhs.x /= rhs.x; lhs.y /= rhs.y; return lhs; } +inline bool operator==(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y; } +inline bool operator!=(const ImVec2& lhs, const ImVec2& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y; } +// ImVec4 operators +inline ImVec4 operator*(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); } +inline ImVec4 operator/(const ImVec4& lhs, const float rhs) { return ImVec4(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); } +inline ImVec4 operator+(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); } +inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); } +inline ImVec4 operator/(const ImVec4& lhs, const ImVec4& rhs) { return ImVec4(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); } +inline ImVec4 operator-(const ImVec4& lhs) { return ImVec4(-lhs.x, -lhs.y, -lhs.z, -lhs.w); } +inline bool operator==(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z && lhs.w == rhs.w; } +inline bool operator!=(const ImVec4& lhs, const ImVec4& rhs) { return lhs.x != rhs.x || lhs.y != rhs.y || lhs.z != rhs.z || lhs.w != rhs.w; } IM_MSVC_RUNTIME_CHECKS_RESTORE #endif // Helpers macros to generate 32-bit encoded colors -// User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - User can declare their own format by #defining the 5 _SHIFT/_MASK macros in their imconfig file. +// - Any setting other than the default will need custom backend support. The only standard backend that supports anything else than the default is DirectX9. #ifndef IM_COL32_R_SHIFT #ifdef IMGUI_USE_BGRA_PACKED_COLOR #define IM_COL32_R_SHIFT 16 @@ -2770,7 +2957,7 @@ struct ImColor // Multi-selection system // Documentation at: https://github.com/ocornut/imgui/wiki/Multi-Select // - Refer to 'Demo->Widgets->Selection State & Multi-Select' for demos using this. -// - This system implements standard multi-selection idioms (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) +// - This system implements standard multi-selection idioms (Ctrl+Mouse/Keyboard, Shift+Mouse/Keyboard, etc) // with support for clipper (skipping non-visible items), box-select and many other details. // - Selectable(), Checkbox() are supported but custom widgets may use it as well. // - TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, @@ -2808,7 +2995,7 @@ enum ImGuiMultiSelectFlags_ { ImGuiMultiSelectFlags_None = 0, ImGuiMultiSelectFlags_SingleSelect = 1 << 0, // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho! - ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable CTRL+A shortcut to select all. + ImGuiMultiSelectFlags_NoSelectAll = 1 << 1, // Disable Ctrl+A shortcut to select all. ImGuiMultiSelectFlags_NoRangeSelect = 1 << 2, // Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests. ImGuiMultiSelectFlags_NoAutoSelect = 1 << 3, // Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes). ImGuiMultiSelectFlags_NoAutoClear = 1 << 4, // Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags_NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes). @@ -2824,6 +3011,7 @@ enum ImGuiMultiSelectFlags_ ImGuiMultiSelectFlags_SelectOnClickRelease = 1 << 14, // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection. //ImGuiMultiSelectFlags_RangeSelect2d = 1 << 15, // Shift+Selection uses 2d geometry instead of linear sequence, so possible to use Shift+up/down to select vertically in grid. Analogous to what BoxSelect does. ImGuiMultiSelectFlags_NavWrapX = 1 << 16, // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one. + ImGuiMultiSelectFlags_NoSelectOnRightClick = 1 << 17, // Disable default right-click processing, which selects item on mouse down, and is designed for context-menus. }; // Main IO structure returned by BeginMultiSelect()/EndMultiSelect(). @@ -2894,7 +3082,7 @@ struct ImGuiSelectionBasicStorage IMGUI_API void Clear(); // Clear selection IMGUI_API void Swap(ImGuiSelectionBasicStorage& r); // Swap two selections IMGUI_API void SetItemSelected(ImGuiID id, bool selected); // Add/remove an item from selection (generally done by ApplyRequests() function) - IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiId id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' + IMGUI_API bool GetNextSelectedItem(void** opaque_it, ImGuiID* out_id); // Iterate selection with 'void* it = NULL; ImGuiID id; while (selection.GetNextSelectedItem(&it, &id)) { ... }' inline ImGuiID GetStorageIdFromIndex(int idx) { return AdapterIndexToStorageId(this, idx); } // Convert index to item id based on provided adapter. }; @@ -2918,7 +3106,14 @@ struct ImGuiSelectionExternalStorage // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (63) +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) +#endif + +// ImDrawIdx: vertex index. [Compile-time configurable type] +// - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). +// - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. +#ifndef ImDrawIdx +typedef unsigned short ImDrawIdx; // Default: 16-bit (for maximum compatibility with renderer backends) #endif // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] @@ -2942,21 +3137,24 @@ typedef void (*ImDrawCallback)(const ImDrawList* parent_list, const ImDrawCmd* c // - VtxOffset: When 'io.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset' is enabled, // this fields allow us to render meshes larger than 64K vertices while keeping 16-bit indices. // Backends made for <1.71. will typically ignore the VtxOffset fields. -// - The ClipRect/TextureId/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). +// - The ClipRect/TexRef/VtxOffset fields must be contiguous as we memcmp() them together (this is asserted for). struct ImDrawCmd { ImVec4 ClipRect; // 4*4 // Clipping rectangle (x1, y1, x2, y2). Subtract ImDrawData->DisplayPos to get clipping rectangle in "viewport" coordinates - ImTextureID TextureId; // 4-8 // User-provided texture ID. Set by user in ImfontAtlas::SetTexID() for fonts or passed to Image*() functions. Ignore if never using images or multiple fonts atlas. + ImTextureRef TexRef; // 16 // Reference to a font/texture atlas (where backend called ImTextureData::SetTexID()) or to a user-provided texture ID (via e.g. ImGui::Image() calls). Both will lead to a ImTextureID value. unsigned int VtxOffset; // 4 // Start offset in vertex buffer. ImGuiBackendFlags_RendererHasVtxOffset: always 0, otherwise may be >0 to support meshes larger than 64K vertices with 16-bit indices. unsigned int IdxOffset; // 4 // Start offset in index buffer. unsigned int ElemCount; // 4 // Number of indices (multiple of 3) to be rendered as triangles. Vertices are stored in the callee ImDrawList's vtx_buffer[] array, indices in idx_buffer[]. ImDrawCallback UserCallback; // 4-8 // If != NULL, call the function instead of rendering the vertices. clip_rect and texture_id will be set normally. - void* UserCallbackData; // 4-8 // The draw callback code can access this. + void* UserCallbackData; // 4-8 // Callback user data (when UserCallback != NULL). If called AddCallback() with size == 0, this is a copy of the AddCallback() argument. If called AddCallback() with size > 0, this is pointing to a buffer where data is stored. + int UserCallbackDataSize; // 4 // Size of callback user data when using storage, otherwise 0. + int UserCallbackDataOffset;// 4 // [Internal] Offset of callback user data when using storage, otherwise -1. - ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed + ImDrawCmd() { memset(this, 0, sizeof(*this)); } // Also ensure our padding fields are zeroed // Since 1.83: returns ImTextureID associated with this draw call. Warning: DO NOT assume this is always same as 'TextureId' (we will change this function for an upcoming feature) - inline ImTextureID GetTexID() const { return TextureId; } + // Since 1.92: removed ImDrawCmd::TextureId field, the getter function must be used! + inline ImTextureID GetTexID() const; // == (TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID) }; // Vertex layout @@ -2979,7 +3177,7 @@ IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT; struct ImDrawCmdHeader { ImVec4 ClipRect; - ImTextureID TextureId; + ImTextureRef TexRef; unsigned int VtxOffset; }; @@ -2990,7 +3188,6 @@ struct ImDrawChannel ImVector _IdxBuffer; }; - // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. // This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. struct ImDrawListSplitter @@ -3046,7 +3243,7 @@ enum ImDrawListFlags_ // access the current window draw list and draw custom primitives. // You can interleave normal ImGui:: calls and adding primitives to the current draw list. // In single viewport mode, top-left is == GetMainViewport()->Pos (generally 0,0), bottom-right is == GetMainViewport()->Pos+Size (generally io.DisplaySize). -// You are totally free to apply whatever transformation matrix to want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) +// You are totally free to apply whatever transformation matrix you want to the data (depending on the use of the transformation you may want to apply it to ClipRect as well!) // Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. struct ImDrawList { @@ -3065,19 +3262,21 @@ struct ImDrawList ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] + ImVector _TextureStack; // [Internal] + ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content const char* _OwnerName; // Pointer to owner window's name for debugging - // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) - ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } + // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). + // (advanced: you may create and use your own ImDrawListSharedData so you can use ImDrawList without ImGui, but that's more involved) + IMGUI_API ImDrawList(ImDrawListSharedData* shared_data); + IMGUI_API ~ImDrawList(); - ~ImDrawList() { _ClearFreeMemory(); } IMGUI_API void PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect = false); // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) IMGUI_API void PushClipRectFullScreen(); IMGUI_API void PopClipRect(); - IMGUI_API void PushTextureID(ImTextureID texture_id); - IMGUI_API void PopTextureID(); + IMGUI_API void PushTexture(ImTextureRef tex_ref); + IMGUI_API void PopTexture(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } @@ -3103,24 +3302,24 @@ struct ImDrawList IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). - // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library. + // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives - // - Read FAQ to understand what ImTextureID is. + // - Read FAQ to understand what ImTextureID/ImTextureRef are. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. - IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); - IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); + IMGUI_API void AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min = ImVec2(0, 0), const ImVec2& uv_max = ImVec2(1, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1 = ImVec2(0, 0), const ImVec2& uv2 = ImVec2(1, 0), const ImVec2& uv3 = ImVec2(1, 1), const ImVec2& uv4 = ImVec2(0, 1), ImU32 col = IM_COL32_WHITE); + IMGUI_API void AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); // Stateful path API, add points then finish with PathFillConvex() or PathStroke() // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. @@ -3138,10 +3337,20 @@ struct ImDrawList IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); - // Advanced - IMGUI_API void AddCallback(ImDrawCallback callback, void* callback_data); // Your rendering function must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. + // Advanced: Draw Callbacks + // - May be used to alter render state (change sampler, blending, current shader). May be used to emit custom rendering commands (difficult to do correctly, but possible). + // - Use special ImDrawCallback_ResetRenderState callback to instruct backend to reset its render state to the default. + // - Your rendering loop must check for 'UserCallback' in ImDrawCmd and call the function instead of rendering triangles. All standard backends are honoring this. + // - For some backends, the callback may access selected render-states exposed by the backend in a ImGui_ImplXXXX_RenderState structure pointed to by platform_io.Renderer_RenderState. + // - IMPORTANT: please be mindful of the different level of indirection between using size==0 (copying argument) and using size>0 (copying pointed data into a buffer). + // - If userdata_size == 0: we copy/store the 'userdata' argument as-is. It will be available unmodified in ImDrawCmd::UserCallbackData during render. + // - If userdata_size > 0, we copy/store 'userdata_size' bytes pointed to by 'userdata'. We store them in a buffer stored inside the drawlist. ImDrawCmd::UserCallbackData will point inside that buffer so you have to retrieve data from there. Your callback may need to use ImDrawCmd::UserCallbackDataSize if you expect dynamically-sized data. + // - Support for userdata_size > 0 was added in v1.91.4, October 2024. So earlier code always only allowed to copy/store a simple void*. + IMGUI_API void AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size = 0); + + // Advanced: Miscellaneous IMGUI_API void AddDrawCmd(); // This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending). Otherwise primitives are merged into the same draw-call as much as possible - IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. + IMGUI_API ImDrawList* CloneOutput() const; // Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer. For multi-threaded rendering, consider using `imgui_threaded_rendering` from https://github.com/ocornut/imgui_club instead. // Advanced: Channels // - Use to split render into layers. By switching channels to can render out-of-order (e.g. submit FG primitives before BG primitives) @@ -3166,6 +3375,10 @@ struct ImDrawList inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void PushTextureID(ImTextureRef tex_ref) { PushTexture(tex_ref); } // RENAMED in 1.92.0 + inline void PopTextureID() { PopTexture(); } // RENAMED in 1.92.0 +#endif //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) @@ -3173,14 +3386,15 @@ struct ImDrawList //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) // [Internal helpers] + IMGUI_API void _SetDrawListSharedData(ImDrawListSharedData* data); IMGUI_API void _ResetForNewFrame(); IMGUI_API void _ClearFreeMemory(); IMGUI_API void _PopUnusedDrawCmd(); IMGUI_API void _TryMergeDrawCmds(); IMGUI_API void _OnChangedClipRect(); - IMGUI_API void _OnChangedTextureID(); + IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); - IMGUI_API void _SetTextureID(ImTextureID texture_id); + IMGUI_API void _SetTexture(ImTextureRef tex_ref); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); @@ -3192,14 +3406,15 @@ struct ImDrawList struct ImDrawData { bool Valid; // Only valid after Render() is called and before the next NewFrame() is called. - int CmdListsCount; // Number of ImDrawList* to render (should always be == CmdLists.size) + int CmdListsCount; // == CmdLists.Size. (OBSOLETE: exists for legacy reasons). Number of ImDrawList* to render. int TotalIdxCount; // For convenience, sum of all ImDrawList's IdxBuffer.Size int TotalVtxCount; // For convenience, sum of all ImDrawList's VtxBuffer.Size ImVector CmdLists; // Array of ImDrawList* to render. The ImDrawLists are owned by ImGuiContext and only pointed to from here. ImVec2 DisplayPos; // Top-left position of the viewport to render (== top-left of the orthogonal projection matrix to use) (== GetMainViewport()->Pos for the main viewport, == (0.0) in most single-viewport applications) ImVec2 DisplaySize; // Size of the viewport to render (== GetMainViewport()->Size for the main viewport, == io.DisplaySize in most single-viewport applications) - ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Copied from viewport->FramebufferScale (== io.DisplayFramebufferScale for main viewport). Generally (1,1) on normal display, (2,2) on OSX with Retina display. ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). + ImVector* Textures; // List of textures to update. Most of the times the list is shared by all ImDrawData, has only 1 texture and it doesn't need any update. This almost always points to ImGui::GetPlatformIO().Textures[]. May be overridden or set to NULL if you want to manually update textures. // Functions ImDrawData() { Clear(); } @@ -3209,48 +3424,146 @@ struct ImDrawData IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than Dear ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. }; +//----------------------------------------------------------------------------- +// [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) +//----------------------------------------------------------------------------- +// In principle, the only data types that user/application code should care about are 'ImTextureRef' and 'ImTextureID'. +// They are defined above in this header file. Read their description to the difference between ImTextureRef and ImTextureID. +// FOR ALL OTHER ImTextureXXXX TYPES: ONLY CORE LIBRARY AND RENDERER BACKENDS NEED TO KNOW AND CARE ABOUT THEM. +//----------------------------------------------------------------------------- + +#undef Status // X11 headers are leaking this. + +// We intentionally support a limited amount of texture formats to limit burden on CPU-side code and extension. +// Most standard backends only support RGBA32 but we provide a single channel option for low-resource/embedded systems. +enum ImTextureFormat +{ + ImTextureFormat_RGBA32, // 4 components per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 + ImTextureFormat_Alpha8, // 1 component per pixel, each is unsigned 8-bit. Total size = TexWidth * TexHeight +}; + +// Status of a texture to communicate with Renderer Backend. +enum ImTextureStatus +{ + ImTextureStatus_OK, + ImTextureStatus_Destroyed, // Backend destroyed the texture. + ImTextureStatus_WantCreate, // Requesting backend to create the texture. Set status OK when done. + ImTextureStatus_WantUpdates, // Requesting backend to update specific blocks of pixels (write to texture portions which have never been used before). Set status OK when done. + ImTextureStatus_WantDestroy, // Requesting backend to destroy the texture. Set status to Destroyed when done. +}; + +// Coordinates of a rectangle within a texture. +// When a texture is in ImTextureStatus_WantUpdates state, we provide a list of individual rectangles to copy to the graphics system. +// You may use ImTextureData::Updates[] for the list, or ImTextureData::UpdateBox for a single bounding box. +struct ImTextureRect +{ + unsigned short x, y; // Upper-left coordinates of rectangle to update + unsigned short w, h; // Size of rectangle to update (in pixels) +}; + +// Specs and pixel storage for a texture used by Dear ImGui. +// This is only useful for (1) core library and (2) backends. End-user/applications do not need to care about this. +// Renderer Backends will create a GPU-side version of this. +// Why does we store two identifiers: TexID and BackendUserData? +// - ImTextureID TexID = lower-level identifier stored in ImDrawCmd. ImDrawCmd can refer to textures not created by the backend, and for which there's no ImTextureData. +// - void* BackendUserData = higher-level opaque storage for backend own book-keeping. Some backends may have enough with TexID and not need both. + // In columns below: who reads/writes each fields? 'r'=read, 'w'=write, 'core'=main library, 'backend'=renderer backend +struct ImTextureData +{ + //------------------------------------------ core / backend --------------------------------------- + int UniqueID; // w - // [DEBUG] Sequential index to facilitate identifying a texture when debugging/printing. Unique per atlas. + ImTextureStatus Status; // rw rw // ImTextureStatus_OK/_WantCreate/_WantUpdates/_WantDestroy. Always use SetStatus() to modify! + void* BackendUserData; // - rw // Convenience storage for backend. Some backends may have enough with TexID. + ImTextureID TexID; // r w // Backend-specific texture identifier. Always use SetTexID() to modify! The identifier will stored in ImDrawCmd::GetTexID() and passed to backend's RenderDrawData function. + ImTextureFormat Format; // w r // ImTextureFormat_RGBA32 (default) or ImTextureFormat_Alpha8 + int Width; // w r // Texture width + int Height; // w r // Texture height + int BytesPerPixel; // w r // 4 or 1 + unsigned char* Pixels; // w r // Pointer to buffer holding 'Width*Height' pixels and 'Width*Height*BytesPerPixels' bytes. + ImTextureRect UsedRect; // w r // Bounding box encompassing all past and queued Updates[]. + ImTextureRect UpdateRect; // w r // Bounding box encompassing all queued Updates[]. + ImVector Updates; // w r // Array of individual updates. + int UnusedFrames; // w r // In order to facilitate handling Status==WantDestroy in some backend: this is a count successive frames where the texture was not used. Always >0 when Status==WantDestroy. + unsigned short RefCount; // w r // Number of contexts using this texture. Used during backend shutdown. + bool UseColors; // w r // Tell whether our texture data is known to use colors (rather than just white + alpha). + bool WantDestroyNextFrame; // rw - // [Internal] Queued to set ImTextureStatus_WantDestroy next frame. May still be used in the current frame. + + // Functions + ImTextureData() { memset(this, 0, sizeof(*this)); Status = ImTextureStatus_Destroyed; TexID = ImTextureID_Invalid; } + ~ImTextureData() { DestroyPixels(); } + IMGUI_API void Create(ImTextureFormat format, int w, int h); + IMGUI_API void DestroyPixels(); + void* GetPixels() { IM_ASSERT(Pixels != NULL); return Pixels; } + void* GetPixelsAt(int x, int y) { IM_ASSERT(Pixels != NULL); return Pixels + (x + y * Width) * BytesPerPixel; } + int GetSizeInBytes() const { return Width * Height * BytesPerPixel; } + int GetPitch() const { return Width * BytesPerPixel; } + ImTextureRef GetTexRef() { ImTextureRef tex_ref; tex_ref._TexData = this; tex_ref._TexID = ImTextureID_Invalid; return tex_ref; } + ImTextureID GetTexID() const { return TexID; } + + // Called by Renderer backend + // - Call SetTexID() and SetStatus() after honoring texture requests. Never modify TexID and Status directly! + // - A backend may decide to destroy a texture that we did not request to destroy, which is fine (e.g. freeing resources), but we immediately set the texture back in _WantCreate mode. + void SetTexID(ImTextureID tex_id) { TexID = tex_id; } + void SetStatus(ImTextureStatus status) { Status = status; if (status == ImTextureStatus_Destroyed && !WantDestroyNextFrame) Status = ImTextureStatus_WantCreate; } +}; + //----------------------------------------------------------------------------- // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont) //----------------------------------------------------------------------------- +// A font input/source (we may rename this to ImFontSource in the future) struct ImFontConfig { + // Data Source + char Name[40]; // // Name (strictly to ease debugging, hence limited size buffer) void* FontData; // // TTF/OTF data int FontDataSize; // // TTF/OTF data size - bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself). - int FontNo; // 0 // Index of font within TTF/OTF file + bool FontDataOwnedByAtlas; // true // TTF/OTF data ownership taken by the owner ImFontAtlas (will delete memory itself). + + // Options + bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. + bool PixelSnapH; // false // Align every glyph AdvanceX to pixel boundaries. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. + bool PixelSnapV; // true // Align Scaled GlyphOffset.y to pixel boundaries. + ImS8 OversampleH; // 0 (2) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1 or 2 depending on size. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. + ImS8 OversampleV; // 0 (1) // Rasterize at higher quality for sub-pixel positioning. 0 == auto == 1. This is not really useful as we don't use sub-pixel positions on the Y axis. + ImWchar EllipsisChar; // 0 // Explicitly specify Unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. float SizePixels; // // Size in pixels for rasterizer (more or less maps to the resulting font height). - int OversampleH; // 2 // Rasterize at higher quality for sub-pixel positioning. Note the difference between 2 and 3 is minimal. You can reduce this to 1 for large glyphs save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details. - int OversampleV; // 1 // Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the Y axis. - bool PixelSnapH; // false // Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1. - ImVec2 GlyphExtraSpacing; // 0, 0 // Extra spacing (in pixels) between glyphs. Only X axis is supported for now. - ImVec2 GlyphOffset; // 0, 0 // Offset all glyphs from this font input. - const ImWchar* GlyphRanges; // NULL // THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). - float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font + const ImWchar* GlyphRanges; // NULL // *LEGACY* THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). + const ImWchar* GlyphExcludeRanges; // NULL // Pointer to a small user-provided list of Unicode ranges (2 value per range, values are inclusive, zero-terminated list). This is very close to GlyphRanges[] but designed to exclude ranges from a font source, when merging fonts with overlapping glyphs. Use "Input Glyphs Overlap Detection Tool" to find about your overlapping ranges. + //ImVec2 GlyphExtraSpacing; // 0, 0 // (REMOVED AT IT SEEMS LARGELY OBSOLETE. PLEASE REPORT IF YOU WERE USING THIS). Extra spacing (in pixels) between glyphs when rendered: essentially add to glyph->AdvanceX. Only X axis is supported for now. + ImVec2 GlyphOffset; // 0, 0 // Offset (in pixels) all glyphs from this font input. Absolute value for default size, other sizes will scale this value. + float GlyphMinAdvanceX; // 0 // Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font. Absolute value for default size, other sizes will scale this value. float GlyphMaxAdvanceX; // FLT_MAX // Maximum AdvanceX for glyphs - bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights. - unsigned int FontBuilderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + float GlyphExtraAdvanceX; // 0 // Extra spacing (in pixels) between glyphs. Please contact us if you are using this. // FIXME-NEWATLAS: Intentionally unscaled + ImU32 FontNo; // 0 // Index of font within TTF/OTF file + unsigned int FontLoaderFlags; // 0 // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure. + //unsigned int FontBuilderFlags; // -- // [Renamed in 1.92] Ue FontLoaderFlags. float RasterizerMultiply; // 1.0f // Linearly brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable. This is a silly thing we may remove in the future. - float RasterizerDensity; // 1.0f // DPI scale for rasterization, not altering other font metrics: make it easy to swap between e.g. a 100% and a 400% fonts for a zooming display. IMPORTANT: If you increase this it is expected that you increase font scale accordingly, otherwise quality may look lowered. - ImWchar EllipsisChar; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used. + float RasterizerDensity; // 1.0f // [LEGACY: this only makes sense when ImGuiBackendFlags_RendererHasTextures is not supported] DPI scale multiplier for rasterization. Not altering other font metrics: makes it easy to swap between e.g. a 100% and a 400% fonts for a zooming display, or handle Retina screen. IMPORTANT: If you change this it is expected that you increase/decrease font scale roughly to the inverse of this, otherwise quality may look lowered. // [Internal] - char Name[40]; // Name (strictly to ease debugging) - ImFont* DstFont; + ImFontFlags Flags; // Font flags (don't use just yet, will be exposed in upcoming 1.92.X updates) + ImFont* DstFont; // Target font (as we merging fonts, multiple ImFontConfig may target the same font) + const ImFontLoader* FontLoader; // Custom font backend for this source (default source is the one stored in ImFontAtlas) + void* FontLoaderData; // Font loader opaque storage (per font config) IMGUI_API ImFontConfig(); }; // Hold rendering data for one glyph. -// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) +// (Note: some language parsers may fail to convert the bitfield members, in this case maybe drop store a single u32 or we can rework this) struct ImFontGlyph { unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops) unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering. - unsigned int Codepoint : 30; // 0x0000..0x10FFFF - float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) - float X0, Y0, X1, Y1; // Glyph corners - float U0, V0, U1, V1; // Texture coordinates + unsigned int SourceIdx : 4; // Index of source in parent font + unsigned int Codepoint : 26; // 0x0000..0x10FFFF + float AdvanceX; // Horizontal distance to advance cursor/layout position. + float X0, Y0, X1, Y1; // Glyph corners. Offsets from current cursor/layout position. + float U0, V0, U1, V1; // Texture coordinates for the current value of ImFontAtlas->TexRef. Cached equivalent of calling GetCustomRect() with PackId. + int PackId; // [Internal] ImFontAtlasRectId value (FIXME: Cold data, could be moved elsewhere?) + + ImFontGlyph() { memset(this, 0, sizeof(*this)); PackId = -1; } }; // Helper to build glyph ranges from text/string data. Feed your application strings/characters to it then call BuildRanges(). @@ -3269,17 +3582,21 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* out_ranges); // Output new ranges }; -// See ImFontAtlas::AddCustomRectXXX functions. -struct ImFontAtlasCustomRect +// An opaque identifier to a rectangle in the atlas. -1 when invalid. +// The rectangle may move and UV may be invalidated, use GetCustomRect() to retrieve it. +typedef int ImFontAtlasRectId; +#define ImFontAtlasRectId_Invalid -1 + +// Output of ImFontAtlas::GetCustomRect() when using custom rectangles. +// Those values may not be cached/stored as they are only valid for the current value of atlas->TexRef +// (this is in theory derived from ImTextureRect but we use separate structures for reasons) +struct ImFontAtlasRect { - unsigned short Width, Height; // Input // Desired rectangle dimension - unsigned short X, Y; // Output // Packed position in Atlas - unsigned int GlyphID; // Input // For custom font glyphs only (ID < 0x110000) - float GlyphAdvanceX; // Input // For custom font glyphs only: glyph xadvance - ImVec2 GlyphOffset; // Input // For custom font glyphs only: glyph display offset - ImFont* Font; // Input // For custom font glyphs only: target font - ImFontAtlasCustomRect() { Width = Height = 0; X = Y = 0xFFFF; GlyphID = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } - bool IsPacked() const { return X != 0xFFFF; } + unsigned short x, y; // Position (in current texture) + unsigned short w, h; // Size + ImVec2 uv0, uv1; // UV coordinates (in current texture) + + ImFontAtlasRect() { memset(this, 0, sizeof(*this)); } }; // Flags for ImFontAtlas build @@ -3295,12 +3612,14 @@ enum ImFontAtlasFlags_ // - One or more fonts. // - Custom graphics data needed to render the shapes needed by Dear ImGui. // - Mouse cursor shapes for software cursor rendering (unless setting 'Flags |= ImFontAtlasFlags_NoMouseCursors' in the font atlas). -// It is the user-code responsibility to setup/build the atlas, then upload the pixel data into a texture accessible by your graphics api. -// - Optionally, call any of the AddFont*** functions. If you don't call any, the default font embedded in the code will be loaded for you. -// - Call GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. -// - Upload the pixels data into a texture within your graphics system (see imgui_impl_xxxx.cpp examples) +// - If you don't call any AddFont*** functions, the default font embedded in the code will be loaded for you. +// It is the rendering backend responsibility to upload texture into your graphics API: +// - ImGui_ImplXXXX_RenderDrawData() functions generally iterate platform_io->Textures[] to create/update/destroy each ImTextureData instance. +// - Backend then set ImTextureData's TexID and BackendUserData. +// - Texture id are passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID/ImTextureRef for more details. +// Legacy path: +// - Call Build() + GetTexDataAsAlpha8() or GetTexDataAsRGBA32() to build and retrieve pixels data. // - Call SetTexID(my_tex_id); and pass the pointer/identifier to your texture in a format natural to your graphics API. -// This value will be passed back to you during rendering to identify the texture. Read FAQ entry about ImTextureID for more details. // Common pitfalls: // - If you pass a 'glyph_ranges' array to AddFont*** functions, you need to make sure that your array persist up until the // atlas is build (when calling GetTexData*** or Build()). We only copy the pointer, not the data. @@ -3314,35 +3633,49 @@ struct ImFontAtlas IMGUI_API ~ImFontAtlas(); IMGUI_API ImFont* AddFont(const ImFontConfig* font_cfg); IMGUI_API ImFont* AddFontDefault(const ImFontConfig* font_cfg = NULL); - IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); - IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. - IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. - IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. - IMGUI_API void ClearInputData(); // Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. - IMGUI_API void ClearTexData(); // Clear output texture data (CPU side). Saves RAM once the texture has been copied to graphics memory. - IMGUI_API void ClearFonts(); // Clear output font data (glyphs storage, UV coordinates). - IMGUI_API void Clear(); // Clear all input and output. - - // Build atlas, retrieve pixel data. - // User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). - // The pitch is always = Width * BytesPerPixels (1 or 4) - // Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into - // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste. - IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. - IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel - IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel - bool IsBuilt() const { return Fonts.Size > 0 && TexReady; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent... - void SetTexID(ImTextureID id) { TexID = id; } + IMGUI_API ImFont* AddFontFromFileTTF(const char* filename, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); + IMGUI_API ImFont* AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // Note: Transfer ownership of 'ttf_data' to ImFontAtlas! Will be deleted after destruction of the atlas. Set font_cfg->FontDataOwnedByAtlas=false to keep ownership of your data and it won't be freed. + IMGUI_API ImFont* AddFontFromMemoryCompressedTTF(const void* compressed_font_data, int compressed_font_data_size, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data' still owned by caller. Compress with binary_to_compressed_c.cpp. + IMGUI_API ImFont* AddFontFromMemoryCompressedBase85TTF(const char* compressed_font_data_base85, float size_pixels = 0.0f, const ImFontConfig* font_cfg = NULL, const ImWchar* glyph_ranges = NULL); // 'compressed_font_data_base85' still owned by caller. Compress with binary_to_compressed_c.cpp with -base85 parameter. + IMGUI_API void RemoveFont(ImFont* font); + + IMGUI_API void Clear(); // Clear everything (input fonts, output glyphs/textures). + IMGUI_API void CompactCache(); // Compact cached glyphs and texture. + IMGUI_API void SetFontLoader(const ImFontLoader* font_loader); // Change font loader at runtime. + + // As we are transitioning toward a new font system, we expect to obsolete those soon: + IMGUI_API void ClearInputData(); // [OBSOLETE] Clear input data (all ImFontConfig structures including sizes, TTF data, glyph ranges, etc.) = all the data used to build the texture and fonts. + IMGUI_API void ClearFonts(); // [OBSOLETE] Clear input+output font data (same as ClearInputData() + glyphs storage, UV coordinates). + IMGUI_API void ClearTexData(); // [OBSOLETE] Clear CPU-side copy of the texture data. Saves RAM once the texture has been copied to graphics memory. + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy path for build atlas + retrieving pixel data. + // - User is in charge of copying the pixels into graphics memory (e.g. create a texture with your engine). Then store your texture handle with SetTexID(). + // - The pitch is always = Width * BytesPerPixels (1 or 4) + // - Building in RGBA32 format is provided for convenience and compatibility, but note that unless you manually manipulate or copy color data into + // the texture (e.g. when using the AddCustomRect*** api), then the RGB pixels emitted will always be white (~75% of memory/bandwidth waste). + // - From 1.92 with backends supporting ImGuiBackendFlags_RendererHasTextures: + // - Calling Build(), GetTexDataAsAlpha8(), GetTexDataAsRGBA32() is not needed. + // - In backend: replace calls to ImFontAtlas::SetTexID() with calls to ImTextureData::SetTexID() after honoring texture creation. + IMGUI_API bool Build(); // Build pixels data. This is called automatically for you by the GetTexData*** functions. + IMGUI_API void GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 1 byte per-pixel + IMGUI_API void GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel = NULL); // 4 bytes-per-pixel + void SetTexID(ImTextureID id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid); TexRef._TexData->TexID = id; } // Called by legacy backends. May be called before texture creation. + void SetTexID(ImTextureRef id) { IM_ASSERT(TexRef._TexID == ImTextureID_Invalid && id._TexData == NULL); TexRef._TexData->TexID = id._TexID; } // Called by legacy backends. + bool IsBuilt() const { return Fonts.Size > 0 && TexIsBuilt; } // Bit ambiguous: used to detect when user didn't build texture but effectively we should check TexID != 0 except that would be backend dependent.. +#endif //------------------------------------------- // Glyph Ranges //------------------------------------------- + // Since 1.92: specifying glyph ranges is only useful/necessary if your backend doesn't support ImGuiBackendFlags_RendererHasTextures! + IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Helpers to retrieve list of common Unicode ranges (2 value per range, values are inclusive, zero-terminated list) // NB: Make sure that your string are UTF-8 and NOT in your local code page. // Read https://github.com/ocornut/imgui/blob/master/docs/FONTS.md/#about-utf-8-encoding for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. - IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs @@ -3351,120 +3684,211 @@ struct ImFontAtlas IMGUI_API const ImWchar* GetGlyphRangesCyrillic(); // Default + about 400 Cyrillic characters IMGUI_API const ImWchar* GetGlyphRangesThai(); // Default + Thai characters IMGUI_API const ImWchar* GetGlyphRangesVietnamese(); // Default + Vietnamese characters +#endif //------------------------------------------- - // [BETA] Custom Rectangles/Glyphs API + // [ALPHA] Custom Rectangles/Glyphs API //------------------------------------------- - // You can request arbitrary rectangles to be packed into the atlas, for your own purposes. - // - After calling Build(), you can query the rectangle position and render your pixels. - // - If you render colored output, set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. - // - You can also request your rectangles to be mapped as font glyph (given a font + Unicode point), - // so you can render e.g. custom colorful icons and use them as regular glyphs. + // Register and retrieve custom rectangles + // - You can request arbitrary rectangles to be packed into the atlas, for your own purpose. + // - Since 1.92.0, packing is done immediately in the function call (previously packing was done during the Build call) + // - You can render your pixels into the texture right after calling the AddCustomRect() functions. + // - VERY IMPORTANT: + // - Texture may be created/resized at any time when calling ImGui or ImFontAtlas functions. + // - IT WILL INVALIDATE RECTANGLE DATA SUCH AS UV COORDINATES. Always use latest values from GetCustomRect(). + // - UV coordinates are associated to the current texture identifier aka 'atlas->TexRef'. Both TexRef and UV coordinates are typically changed at the same time. + // - If you render colored output into your custom rectangles: set 'atlas->TexPixelsUseColors = true' as this may help some backends decide of preferred texture format. // - Read docs/FONTS.md for more details about using colorful icons. - // - Note: this API may be redesigned later in order to support multi-monitor varying DPI settings. - IMGUI_API int AddCustomRectRegular(int width, int height); - IMGUI_API int AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset = ImVec2(0, 0)); - ImFontAtlasCustomRect* GetCustomRectByIndex(int index) { IM_ASSERT(index >= 0); return &CustomRects[index]; } - - // [Internal] - IMGUI_API void CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const; - IMGUI_API bool GetMouseCursorTexData(ImGuiMouseCursor cursor, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); + // - Note: this API may be reworked further in order to facilitate supporting e.g. multi-monitor, varying DPI settings. + // - (Pre-1.92 names) ------------> (1.92 names) + // - GetCustomRectByIndex() --> Use GetCustomRect() + // - CalcCustomRectUV() --> Use GetCustomRect() and read uv0, uv1 fields. + // - AddCustomRectRegular() --> Renamed to AddCustomRect() + // - AddCustomRectFontGlyph() --> Prefer using custom ImFontLoader inside ImFontConfig + // - ImFontAtlasCustomRect --> Renamed to ImFontAtlasRect + IMGUI_API ImFontAtlasRectId AddCustomRect(int width, int height, ImFontAtlasRect* out_r = NULL);// Register a rectangle. Return -1 (ImFontAtlasRectId_Invalid) on error. + IMGUI_API void RemoveCustomRect(ImFontAtlasRectId id); // Unregister a rectangle. Existing pixels will stay in texture until resized / garbage collected. + IMGUI_API bool GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const; // Get rectangle coordinates for current texture. Valid immediately, never store this (read above)! //------------------------------------------- // Members //------------------------------------------- + // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) - ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure. - int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. - int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). - bool Locked; // Marked as Locked by ImGui::NewFrame() so attempt to modify the atlas will assert. + ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). + int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. + int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. + int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. + int TexMaxHeight; // Maximum desired texture height. Must be a power of two. Default to 8192. void* UserData; // Store your own atlas related user-data (if e.g. you have multiple font atlas). + // Output + // - Because textures are dynamically created/resized, the current texture identifier may changed at *ANY TIME* during the frame. + // - This should not affect you as you can always use the latest value. But note that any precomputed UV coordinates are only valid for the current TexRef. +#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImTextureRef TexRef; // Latest texture identifier == TexData->GetTexRef(). +#else + union { ImTextureRef TexRef; ImTextureRef TexID; }; // Latest texture identifier == TexData->GetTexRef(). // RENAMED TexID to TexRef in 1.92.0. +#endif + ImTextureData* TexData; // Latest texture. + // [Internal] - // NB: Access texture data via GetTexData*() calls! Which will setup a default font for you. - bool TexReady; // Set when texture was built matching current font input - bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format. - unsigned char* TexPixelsAlpha8; // 1 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight - unsigned int* TexPixelsRGBA32; // 4 component per pixel, each component is unsigned 8-bit. Total size = TexWidth * TexHeight * 4 - int TexWidth; // Texture width calculated during Build(). - int TexHeight; // Texture height calculated during Build(). - ImVec2 TexUvScale; // = (1.0f/TexWidth, 1.0f/TexHeight) - ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel + ImVector TexList; // Texture list (most often TexList.Size == 1). TexData is always == TexList.back(). DO NOT USE DIRECTLY, USE GetDrawData().Textures[]/GetPlatformIO().Textures[] instead! + bool Locked; // Marked as locked during ImGui::NewFrame()..EndFrame() scope if TexUpdates are not supported. Any attempt to modify the atlas will assert. + bool RendererHasTextures;// Copy of (BackendFlags & ImGuiBackendFlags_RendererHasTextures) from supporting context. + bool TexIsBuilt; // Set when texture was built matching current font input. Mostly useful for legacy IsBuilt() call. + bool TexPixelsUseColors; // Tell whether our texture data is known to use colors (rather than just alpha channel), in order to help backend select a format or conversion process. + ImVec2 TexUvScale; // = (1.0f/TexData->TexWidth, 1.0f/TexData->TexHeight). May change as new texture gets created. + ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. - ImVector CustomRects; // Rectangles for packing custom texture data into the atlas. - ImVector ConfigData; // Configuration data + ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + int TexNextUniqueID; // Next value to be stored in TexData->UniqueID + int FontNextUniqueID; // Next value to be stored in ImFont->FontID + ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. + ImFontAtlasBuilder* Builder; // Opaque interface to our data that doesn't need to be public and may be discarded when rebuilding. + const ImFontLoader* FontLoader; // Font loader opaque interface (default to use FreeType when IMGUI_ENABLE_FREETYPE is defined, otherwise default to use stb_truetype). Use SetFontLoader() to change this at runtime. + const char* FontLoaderName; // Font loader name (for display e.g. in About box) == FontLoader->Name + void* FontLoaderData; // Font backend opaque storage + unsigned int FontLoaderFlags; // Shared flags (for all fonts) for font loader. THIS IS BUILD IMPLEMENTATION DEPENDENT (e.g. Per-font override is also available in ImFontConfig). + int RefCount; // Number of contexts using this atlas + ImGuiContext* OwnerContext; // Context which own the atlas will be in charge of updating and destroying it. - // [Internal] Font builder - const ImFontBuilderIO* FontBuilderIO; // Opaque interface to a font builder (default to stb_truetype, can be changed to use FreeType by defining IMGUI_ENABLE_FREETYPE). - unsigned int FontBuilderFlags; // Shared flags (for all fonts) for custom font builder. THIS IS BUILD IMPLEMENTATION DEPENDENT. Per-font override is also available in ImFontConfig. + // [Obsolete] +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // Legacy: You can request your rectangles to be mapped as font glyph (given a font + Unicode point), so you can render e.g. custom colorful icons and use them as regular glyphs. --> Prefer using a custom ImFontLoader. + ImFontAtlasRect TempRect; // For old GetCustomRectByIndex() API + inline ImFontAtlasRectId AddCustomRectRegular(int w, int h) { return AddCustomRect(w, h); } // RENAMED in 1.92.0 + inline const ImFontAtlasRect* GetCustomRectByIndex(ImFontAtlasRectId id) { return GetCustomRect(id, &TempRect) ? &TempRect : NULL; } // OBSOLETED in 1.92.0 + inline void CalcCustomRectUV(const ImFontAtlasRect* r, ImVec2* out_uv_min, ImVec2* out_uv_max) const { *out_uv_min = r->uv0; *out_uv_max = r->uv1; } // OBSOLETED in 1.92.0 + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // OBSOLETED in 1.92.0: Use custom ImFontLoader in ImFontConfig + IMGUI_API ImFontAtlasRectId AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int w, int h, float advance_x, const ImVec2& offset = ImVec2(0, 0)); // ADDED AND OBSOLETED in 1.92.0 +#endif + //unsigned int FontBuilderFlags; // OBSOLETED in 1.92.0: Renamed to FontLoaderFlags. + //int TexDesiredWidth; // OBSOLETED in 1.92.0: Force texture width before calling Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height. + //typedef ImFontAtlasRect ImFontAtlasCustomRect; // OBSOLETED in 1.92.0 + //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ + //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ +}; - // [Internal] Packing data - int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines +// Font runtime data for a given size +// Important: pointers to ImFontBaked are only valid for the current frame. +struct ImFontBaked +{ + // [Internal] Members: Hot ~20/24 bytes (for CalcTextSize) + ImVector IndexAdvanceX; // 12-16 // out // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). + float FallbackAdvanceX; // 4 // out // FindGlyph(FallbackChar)->AdvanceX + float Size; // 4 // in // Height of characters/line, set during loading (doesn't change after loading) + float RasterizerDensity; // 4 // in // Density this is baked at + + // [Internal] Members: Hot ~28/36 bytes (for RenderText loop) + ImVector IndexLookup; // 12-16 // out // Sparse. Index glyphs by Unicode code-point. + ImVector Glyphs; // 12-16 // out // All glyphs. + int FallbackGlyphIndex; // 4 // out // Index of FontFallbackChar + + // [Internal] Members: Cold + float Ascent, Descent; // 4+4 // out // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) + unsigned int MetricsTotalSurface:26;// 3 // out // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) + unsigned int WantDestroy:1; // 0 // // Queued for destroy + unsigned int LoadNoFallback:1; // 0 // // Disable loading fallback in lower-level calls. + unsigned int LoadNoRenderOnLayout:1;// 0 // // Enable a two-steps mode where CalcTextSize() calls will load AdvanceX *without* rendering/packing glyphs. Only advantagous if you know that the glyph is unlikely to actually be rendered, otherwise it is slower because we'd do one query on the first CalcTextSize and one query on the first Draw. + int LastUsedFrame; // 4 // // Record of that time this was bounds + ImGuiID BakedId; // 4 // // Unique ID for this baked storage + ImFont* OwnerFont; // 4-8 // in // Parent font + void* FontLoaderDatas; // 4-8 // // Font loader opaque storage (per baked font * sources): single contiguous buffer allocated by imgui, passed to loader. - // [Obsolete] - //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ - //typedef ImFontGlyphRangesBuilder GlyphRangesBuilder; // OBSOLETED in 1.67+ + // Functions + IMGUI_API ImFontBaked(); + IMGUI_API void ClearOutputData(); + IMGUI_API ImFontGlyph* FindGlyph(ImWchar c); // Return U+FFFD glyph if requested glyph doesn't exists. + IMGUI_API ImFontGlyph* FindGlyphNoFallback(ImWchar c); // Return NULL if glyph doesn't exist + IMGUI_API float GetCharAdvance(ImWchar c); + IMGUI_API bool IsGlyphLoaded(ImWchar c); +}; + +// Font flags +// (in future versions as we redesign font loading API, this will become more important and better documented. for now please consider this as internal/advanced use) +enum ImFontFlags_ +{ + ImFontFlags_None = 0, + ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. + ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. + ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. }; // Font runtime data and rendering -// ImFontAtlas automatically loads a default embedded font for you when you call GetTexDataAsAlpha8() or GetTexDataAsRGBA32(). +// - ImFontAtlas automatically loads a default embedded font for you if you didn't load one manually. +// - Since 1.92.0 a font may be rendered as any size! Therefore a font doesn't have one specific size. +// - Use 'font->GetFontBaked(size)' to retrieve the ImFontBaked* corresponding to a given size. +// - If you used g.Font + g.FontSize (which is frequent from the ImGui layer), you can use g.FontBaked as a shortcut, as g.FontBaked == g.Font->GetFontBaked(g.FontSize). struct ImFont { - // Members: Hot ~20/24 bytes (for CalcTextSize) - ImVector IndexAdvanceX; // 12-16 // out // // Sparse. Glyphs->AdvanceX in a directly indexable way (cache-friendly for CalcTextSize functions which only this info, and are often bottleneck in large UI). - float FallbackAdvanceX; // 4 // out // = FallbackGlyph->AdvanceX - float FontSize; // 4 // in // // Height of characters/line, set during loading (don't change after loading) - - // Members: Hot ~28/40 bytes (for CalcTextSize + render loop) - ImVector IndexLookup; // 12-16 // out // // Sparse. Index glyphs by Unicode code-point. - ImVector Glyphs; // 12-16 // out // // All glyphs. - const ImFontGlyph* FallbackGlyph; // 4-8 // out // = FindGlyph(FontFallbackChar) - - // Members: Cold ~32/40 bytes - ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into - const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData - short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont. - ImWchar FallbackChar; // 2 // out // = FFFD/'?' // Character used if a glyph isn't found. - ImWchar EllipsisChar; // 2 // out // = '...'/'.'// Character used for ellipsis rendering. - short EllipsisCharCount; // 1 // out // 1 or 3 - float EllipsisWidth; // 4 // out // Width - float EllipsisCharStep; // 4 // out // Step between characters when EllipsisCount > 0 - bool DirtyLookupTables; // 1 // out // - float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale() - float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize] (unscaled) - int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs) - ImU8 Used4kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/4096/8]; // 2 bytes if ImWchar=ImWchar16, 34 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + // [Internal] Members: Hot ~12-20 bytes + ImFontBaked* LastBaked; // 4-8 // Cache last bound baked. NEVER USE DIRECTLY. Use GetFontBaked(). + ImFontAtlas* OwnerAtlas; // 4-8 // What we have been loaded into. + ImFontFlags Flags; // 4 // Font flags. + float CurrentRasterizerDensity; // Current rasterizer density. This is a varying state of the font. + + // [Internal] Members: Cold ~24-52 bytes + // Conceptually Sources[] is the list of font sources merged to create this font. + ImGuiID FontId; // Unique identifier for the font + float LegacySize; // 4 // in // Font size passed to AddFont(). Use for old code calling PushFont() expecting to use that size. (use ImGui::GetFontBaked() to get font baked at current bound size). + ImVector Sources; // 16 // in // List of sources. Pointers within OwnerAtlas->Sources[] + ImWchar EllipsisChar; // 2-4 // out // Character used for ellipsis rendering ('...'). + ImWchar FallbackChar; // 2-4 // out // Character used if a glyph isn't found (U+FFFD, '?') + ImU8 Used8kPagesMap[(IM_UNICODE_CODEPOINT_MAX+1)/8192/8]; // 1 bytes if ImWchar=ImWchar16, 16 bytes if ImWchar==ImWchar32. Store 1-bit for each block of 4K codepoints that has one active glyph. This is mainly used to facilitate iterations across all used codepoints. + bool EllipsisAutoBake; // 1 // // Mark when the "..." glyph needs to be generated. + ImGuiStorage RemapPairs; // 16 // // Remapping pairs when using AddRemapChar(), otherwise empty. +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + float Scale; // 4 // in // Legacy base font scale (~1.0f), multiplied by the per-window font scale which you can adjust with SetWindowFontScale() +#endif // Methods IMGUI_API ImFont(); IMGUI_API ~ImFont(); - IMGUI_API const ImFontGlyph*FindGlyph(ImWchar c) const; - IMGUI_API const ImFontGlyph*FindGlyphNoFallback(ImWchar c) const; - float GetCharAdvance(ImWchar c) const { return ((int)c < IndexAdvanceX.Size) ? IndexAdvanceX[(int)c] : FallbackAdvanceX; } - bool IsLoaded() const { return ContainerAtlas != NULL; } - const char* GetDebugName() const { return ConfigData ? ConfigData->Name : ""; } + IMGUI_API bool IsGlyphInFont(ImWchar c); + bool IsLoaded() const { return OwnerAtlas != NULL; } + const char* GetDebugName() const { return Sources.Size ? Sources[0]->Name : ""; } // Fill ImFontConfig::Name. + // [Internal] Don't use! // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const; - IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + IMGUI_API ImFontBaked* GetFontBaked(float font_size, float density = -1.0f); // Get or create baked data for given size + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** out_remaining = NULL); + IMGUI_API const char* CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip = NULL); + IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, ImDrawTextFlags flags = 0); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) { return CalcWordWrapPosition(LegacySize * scale, text, text_end, wrap_width); } +#endif // [Internal] Don't use! - IMGUI_API void BuildLookupTable(); IMGUI_API void ClearOutputData(); - IMGUI_API void GrowIndex(int new_size); - IMGUI_API void AddGlyph(const ImFontConfig* src_cfg, ImWchar c, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x); - IMGUI_API void AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst = true); // Makes 'dst' character/glyph points to 'src' character/glyph. Currently needs to be called AFTER fonts have been built. - IMGUI_API void SetGlyphVisible(ImWchar c, bool visible); + IMGUI_API void AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint); // Makes 'from_codepoint' character points to 'to_codepoint' glyph. IMGUI_API bool IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last); }; +// This is provided for consistency (but we don't actually use this) +inline ImTextureID ImTextureRef::GetTexID() const +{ + IM_ASSERT(!(_TexData != NULL && _TexID != ImTextureID_Invalid)); + return _TexData ? _TexData->TexID : _TexID; +} + +// Using an indirection to avoid patching ImDrawCmd after a SetTexID() call (but this could be an alternative solution too) +inline ImTextureID ImDrawCmd::GetTexID() const +{ + // If you are getting this assert: A renderer backend with support for ImGuiBackendFlags_RendererHasTextures (1.92) + // must iterate and handle ImTextureData requests stored in ImDrawData::Textures[]. + ImTextureID tex_id = TexRef._TexData ? TexRef._TexData->TexID : TexRef._TexID; // == TexRef.GetTexID() above. + if (TexRef._TexData != NULL) + IM_ASSERT(tex_id != ImTextureID_Invalid && "ImDrawCmd is referring to ImTextureData that wasn't uploaded to graphics system. Backend must call ImTextureData::SetTexID() after handling ImTextureStatus_WantCreate request!"); + return tex_id; +} + //----------------------------------------------------------------------------- // [SECTION] Viewports //----------------------------------------------------------------------------- @@ -3491,6 +3915,7 @@ struct ImGuiViewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. + ImVec2 FramebufferScale; // Density of the viewport for Retina display (always 1,1 on Windows, may be 2,2 etc on macOS/iOS). This will affect font rasterizer density. ImVec2 WorkPos; // Work Area: Position of the viewport minus task bars, menus bars, status bars (>= Pos) ImVec2 WorkSize; // Work Area: Size of the viewport minus task bars, menu bars, status bars (<= Size) @@ -3515,7 +3940,7 @@ struct ImGuiPlatformIO IMGUI_API ImGuiPlatformIO(); //------------------------------------------------------------------ - // Input - Interface with OS/backends + // Input - Interface with OS and Platform backend (most common stuff) //------------------------------------------------------------------ // Optional: Access OS clipboard @@ -3525,7 +3950,7 @@ struct ImGuiPlatformIO void* Platform_ClipboardUserData; // Optional: Open link/folder/file in OS Shell - // (default to use ShellExecuteA() on Windows, system() on Linux/Mac) + // (default to use ShellExecuteW() on Windows, system() on Linux/Mac. expected to return false on failure, but some platforms may always return true) bool (*Platform_OpenInShellFn)(ImGuiContext* ctx, const char* path); void* Platform_OpenInShellUserData; @@ -3538,16 +3963,44 @@ struct ImGuiPlatformIO // Optional: Platform locale // [Experimental] Configure decimal point e.g. '.' or ',' useful for some languages (e.g. German), generally pulled from *localeconv()->decimal_point ImWchar Platform_LocaleDecimalPoint; // '.' + + //------------------------------------------------------------------ + // Input - Interface with Renderer Backend + //------------------------------------------------------------------ + + // Optional: Maximum texture size supported by renderer (used to adjust how we size textures). 0 if not known. + int Renderer_TextureMaxWidth; + int Renderer_TextureMaxHeight; + + // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure. + void* Renderer_RenderState; + + //------------------------------------------------------------------ + // Output + //------------------------------------------------------------------ + + // Textures list (the list is updated by calling ImGui::EndFrame or ImGui::Render) + // The ImGui_ImplXXXX_RenderDrawData() function of each backend generally access this via ImDrawData::Textures which points to this. The array is available here mostly because backends will want to destroy textures on shutdown. + ImVector Textures; // List of textures used by Dear ImGui (most often 1) + contents of external texture list is automatically appended into this. + + //------------------------------------------------------------------ + // Functions + //------------------------------------------------------------------ + + IMGUI_API void ClearPlatformHandlers(); // Clear all Platform_XXX fields. Typically called on Platform Backend shutdown. + IMGUI_API void ClearRendererHandlers(); // Clear all Renderer_XXX fields. Typically called on Renderer Backend shutdown. }; -// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. +// (Optional) Support for IME (Input Method Editor) via the platform_io.Platform_SetImeDataFn() function. Handler is called during EndFrame(). struct ImGuiPlatformImeData { - bool WantVisible; // A widget wants the IME to be visible - ImVec2 InputPos; // Position of the input cursor - float InputLineHeight; // Line height + bool WantVisible; // A widget wants the IME to be visible. + bool WantTextInput; // A widget wants text input, not necessarily IME to be visible. This is automatically set to the upcoming value of io.WantTextInput. + ImVec2 InputPos; // Position of input cursor (for IME). + float InputLineHeight; // Line height (for IME). + ImGuiID ViewportId; // ID of platform window/viewport. - ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); } + ImGuiPlatformImeData() { memset(this, 0, sizeof(*this)); } }; //----------------------------------------------------------------------------- @@ -3559,37 +4012,42 @@ struct ImGuiPlatformImeData #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.92.0 (from June 2025) + inline void PushFont(ImFont* font) { PushFont(font, font ? font->LegacySize : 0.0f); } + IMGUI_API void SetWindowFontScale(float scale); // Set font scale factor for current window. Prefer using PushFont(NULL, style.FontSizeBase * factor) or use style.FontScaleMain to scale all windows. + // OBSOLETED in 1.91.9 (from February 2025) + IMGUI_API void Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col); // <-- 'border_col' was removed in favor of ImGuiCol_ImageBorder. If you use 'tint_col', use ImageWithBg() instead. // OBSOLETED in 1.91.0 (from July 2024) - static inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } - static inline void PopButtonRepeat() { PopItemFlag(); } - static inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } - static inline void PopTabStop() { PopItemFlag(); } + inline void PushButtonRepeat(bool repeat) { PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat); } + inline void PopButtonRepeat() { PopItemFlag(); } + inline void PushTabStop(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + inline void PopTabStop() { PopItemFlag(); } IMGUI_API ImVec2 GetContentRegionMax(); // Content boundaries max (e.g. window boundaries including scrolling, or current column boundaries). You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMin(); // Content boundaries min for the window (roughly (0,0)-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! IMGUI_API ImVec2 GetWindowContentRegionMax(); // Content boundaries max for the window (roughly (0,0)+Size-Scroll), in window-local coordinates. You should never need this. Always use GetCursorScreenPos() and GetContentRegionAvail()! // OBSOLETED in 1.90.0 (from September 2023) - static inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } - static inline void EndChildFrame() { EndChild(); } - //static inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - //static inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders - static inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } + inline bool BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags window_flags = 0) { return BeginChild(id, size, ImGuiChildFlags_FrameStyle, window_flags); } + inline void EndChildFrame() { EndChild(); } + //inline bool BeginChild(const char* str_id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags){ return BeginChild(str_id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + //inline bool BeginChild(ImGuiID id, const ImVec2& size_arg, bool borders, ImGuiWindowFlags window_flags) { return BeginChild(id, size_arg, borders ? ImGuiChildFlags_Borders : ImGuiChildFlags_None, window_flags); } // Unnecessary as true == ImGuiChildFlags_Borders + inline void ShowStackToolWindow(bool* p_open = NULL) { ShowIDStackToolWindow(p_open); } IMGUI_API bool Combo(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int popup_max_height_in_items = -1); IMGUI_API bool ListBox(const char* label, int* current_item, bool (*old_callback)(void* user_data, int idx, const char** out_text), void* user_data, int items_count, int height_in_items = -1); - // OBSOLETED in 1.89.7 (from June 2023) - IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() before item. - // OBSOLETED in 1.89.4 (from March 2023) - static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } - static inline void PopAllowKeyboardFocus() { PopItemFlag(); } - // OBSOLETED in 1.87 (from February 2022 but more formally obsoleted April 2024) - IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! - //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) + // OBSOLETED in 1.89.7 (from June 2023) + //IMGUI_API void SetItemAllowOverlap(); // Use SetNextItemAllowOverlap() _before_ item. + //-- OBSOLETED in 1.89.4 (from March 2023) + //static inline void PushAllowKeyboardFocus(bool tab_stop) { PushItemFlag(ImGuiItemFlags_NoTabStop, !tab_stop); } + //static inline void PopAllowKeyboardFocus() { PopItemFlag(); } //-- OBSOLETED in 1.89 (from August 2022) //IMGUI_API bool ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); // --> Use new ImageButton() signature (explicit item id, regular FramePadding). Refer to code in 1.91 if you want to grab a copy of this version. //-- OBSOLETED in 1.88 (from May 2022) - //static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. - //static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + //static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. + //static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + //-- OBSOLETED in 1.87 (from February 2022, more formally obsoleted April 2024) + //IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); const ImGuiKeyData* key_data = GetKeyData(key); return (ImGuiKey)(key_data - g.IO.KeysData); } // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! + //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } //-- OBSOLETED in 1.86 (from November 2021) //IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // Code removed, see 1.90 for last version of the code. Calculate range of visible items for large list of evenly sized items. Prefer using ImGuiListClipper. //-- OBSOLETED in 1.85 (from August 2021) @@ -3644,6 +4102,25 @@ namespace ImGui //static inline void SetScrollPosHere() { SetScrollHere(); } // OBSOLETED in 1.42 } +//-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect +// - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y +// - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h +// - ImFontAtlasCustomRect::GlyphColored --> if you need to write to this, instead you can write to 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph() +// We could make ImTextureRect an union to use old names, but 1) this would be confusing 2) the fix is easy 3) ImFontAtlasCustomRect was always a rather esoteric api. +typedef ImFontAtlasRect ImFontAtlasCustomRect; +/*struct ImFontAtlasCustomRect +{ + unsigned short X, Y; // Output // Packed position in Atlas + unsigned short Width, Height; // Input // [Internal] Desired rectangle dimension + unsigned int GlyphID:31; // Input // [Internal] For custom font glyphs only (ID < 0x110000) + unsigned int GlyphColored:1; // Input // [Internal] For custom font glyphs only: glyph is colored, removed tinting. + float GlyphAdvanceX; // Input // [Internal] For custom font glyphs only: glyph xadvance + ImVec2 GlyphOffset; // Input // [Internal] For custom font glyphs only: glyph display offset + ImFont* Font; // Input // [Internal] For custom font glyphs only: target font + ImFontAtlasCustomRect() { X = Y = 0xFFFF; Width = Height = 0; GlyphID = 0; GlyphColored = 0; GlyphAdvanceX = 0.0f; GlyphOffset = ImVec2(0, 0); Font = NULL; } + bool IsPacked() const { return X != 0xFFFF; } +};*/ + //-- OBSOLETED in 1.82 (from Mars 2021): flags for AddRect(), AddRectFilled(), AddImageRounded(), PathRect() //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ @@ -3661,7 +4138,7 @@ namespace ImGui //}; // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) -// RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obscolescence schedule to reduce confusion and because they were not meant to be used in the first place. +// RENAMED ImGuiKeyModFlags -> ImGuiModFlags in 1.88 (from April 2022). Exceptionally commented out ahead of obsolescence schedule to reduce confusion and because they were not meant to be used in the first place. //typedef ImGuiKeyChord ImGuiModFlags; // == int. We generally use ImGuiKeyChord to mean "a ImGuiKey or-ed with any number of ImGuiMod_XXX value", so you may store mods in there. //enum ImGuiModFlags_ { ImGuiModFlags_None = 0, ImGuiModFlags_Ctrl = ImGuiMod_Ctrl, ImGuiModFlags_Shift = ImGuiMod_Shift, ImGuiModFlags_Alt = ImGuiMod_Alt, ImGuiModFlags_Super = ImGuiMod_Super }; //typedef ImGuiKeyChord ImGuiKeyModFlags; // == int @@ -3672,10 +4149,7 @@ namespace ImGui #endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // RENAMED IMGUI_DISABLE_METRICS_WINDOW > IMGUI_DISABLE_DEBUG_TOOLS in 1.88 (from June 2022) -#if defined(IMGUI_DISABLE_METRICS_WINDOW) && !defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) -#define IMGUI_DISABLE_DEBUG_TOOLS -#endif -#if defined(IMGUI_DISABLE_METRICS_WINDOW) && defined(IMGUI_DISABLE_OBSOLETE_FUNCTIONS) +#ifdef IMGUI_DISABLE_METRICS_WINDOW #error IMGUI_DISABLE_METRICS_WINDOW was renamed to IMGUI_DISABLE_DEBUG_TOOLS, please use new name. #endif diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_draw.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_draw.cpp" index 8084e53c..de646229 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_draw.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_draw.cpp" @@ -1,4 +1,4 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (drawing and font code) /* @@ -13,8 +13,9 @@ Index of this file: // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions // [SECTION] ImFontConfig -// [SECTION] ImFontAtlas -// [SECTION] ImFontAtlas glyph ranges helpers +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +// [SECTION] ImFontAtlas: backend for stb_truetype +// [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFont // [SECTION] ImGui Internal Render Helpers @@ -39,6 +40,7 @@ Index of this file: #endif #include // vsnprintf, sscanf, printf +#include // intptr_t // Visual Studio warnings #ifdef _MSC_VER @@ -46,7 +48,7 @@ Index of this file: #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff) #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2). -#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #endif // Clang/GCC warnings with -Weverything @@ -66,13 +68,19 @@ Index of this file: #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier +#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label #elif defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used -#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function -#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value -#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value +#pragma GCC diagnostic ignored "-Wstack-protector" // warning: stack protector not protecting local variables: variable length buffer +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif //------------------------------------------------------------------------- @@ -101,16 +109,15 @@ namespace IMGUI_STB_NAMESPACE #if defined(__clang__) #pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used #pragma clang diagnostic ignored "-Wmissing-prototypes" #pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#pragma clang diagnostic ignored "-Wcast-qual" // warning: cast from 'const xxxx *' to 'xxx *' drops const qualifier #endif #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" // warning: comparison is always true due to limited range of data type [-Wtype-limits] -#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" // warning: this statement may fall through #endif #ifndef STB_RECT_PACK_IMPLEMENTATION // in case the user already have an implementation in the _same_ compilation unit (e.g. unity builds) @@ -139,6 +146,7 @@ namespace IMGUI_STB_NAMESPACE #define STBTT_fabs(x) ImFabs(x) #define STBTT_ifloor(x) ((int)ImFloor(x)) #define STBTT_iceil(x) ((int)ImCeil(x)) +#define STBTT_strlen(x) ImStrlen(x) #define STBTT_STATIC #define STB_TRUETYPE_IMPLEMENTATION #else @@ -211,13 +219,14 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.20f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.50f, 0.50f, 0.50f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -229,8 +238,11 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_DragDropTargetBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_UnsavedMarker] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_NavCursor] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); @@ -274,13 +286,14 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.10f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = colors[ImGuiCol_HeaderActive]; + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.53f, 0.53f, 0.87f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -292,8 +305,11 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_DragDropTargetBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_UnsavedMarker] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); @@ -338,13 +354,14 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.35f, 0.35f, 0.35f, 0.17f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_InputTextCursor] = colors[ImGuiCol_Text]; colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); colors[ImGuiCol_TabSelected] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); colors[ImGuiCol_TabSelectedOverline] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TabDimmed] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); colors[ImGuiCol_TabDimmedSelected] = ImLerp(colors[ImGuiCol_TabSelected], colors[ImGuiCol_TitleBg], 0.40f); - colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 1.00f); + colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.26f, 0.59f, 1.00f, 0.00f); colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -356,8 +373,11 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_TableRowBgAlt] = ImVec4(0.30f, 0.30f, 0.30f, 0.09f); colors[ImGuiCol_TextLink] = colors[ImGuiCol_HeaderActive]; colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_TreeLines] = colors[ImGuiCol_Border]; colors[ImGuiCol_DragDropTarget] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); - colors[ImGuiCol_NavHighlight] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_DragDropTargetBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_UnsavedMarker] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + colors[ImGuiCol_NavCursor] = colors[ImGuiCol_HeaderHovered]; colors[ImGuiCol_NavWindowingHighlight] = ImVec4(0.70f, 0.70f, 0.70f, 0.70f); colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.20f); colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f); @@ -370,6 +390,7 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) ImDrawListSharedData::ImDrawListSharedData() { memset(this, 0, sizeof(*this)); + InitialFringeScale = 1.0f; for (int i = 0; i < IM_ARRAYSIZE(ArcFastVtx); i++) { const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx); @@ -378,6 +399,11 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } +ImDrawListSharedData::~ImDrawListSharedData() +{ + IM_ASSERT(DrawLists.Size == 0); +} + void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) { if (CircleSegmentMaxError == max_error) @@ -393,14 +419,35 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError); } +ImDrawList::ImDrawList(ImDrawListSharedData* shared_data) +{ + memset(this, 0, sizeof(*this)); + _SetDrawListSharedData(shared_data); +} + +ImDrawList::~ImDrawList() +{ + _ClearFreeMemory(); + _SetDrawListSharedData(NULL); +} + +void ImDrawList::_SetDrawListSharedData(ImDrawListSharedData* data) +{ + if (_Data != NULL) + _Data->DrawLists.find_erase_unsorted(this); + _Data = data; + if (_Data != NULL) + _Data->DrawLists.push_back(this); +} + // Initialize before use in a new frame. We always have a command ready in the buffer. -// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. +// In the majority of cases, you would want to call PushClipRect() and PushTexture() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. IM_STATIC_ASSERT(offsetof(ImDrawCmd, ClipRect) == 0); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, TextureId) == sizeof(ImVec4)); - IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureID)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, TexRef) == sizeof(ImVec4)); + IM_STATIC_ASSERT(offsetof(ImDrawCmd, VtxOffset) == sizeof(ImVec4) + sizeof(ImTextureRef)); if (_Splitter._Count > 1) _Splitter.Merge(this); @@ -413,11 +460,12 @@ void ImDrawList::_ResetForNewFrame() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.resize(0); - _TextureIdStack.resize(0); + _TextureStack.resize(0); + _CallbacksDataBuf.resize(0); _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); - _FringeScale = 1.0f; + _FringeScale = _Data->InitialFringeScale; } void ImDrawList::_ClearFreeMemory() @@ -430,14 +478,16 @@ void ImDrawList::_ClearFreeMemory() _VtxWritePtr = NULL; _IdxWritePtr = NULL; _ClipRectStack.clear(); - _TextureIdStack.clear(); + _TextureStack.clear(); + _CallbacksDataBuf.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); } +// Note: For multi-threaded rendering, consider using `imgui_threaded_rendering` from https://github.com/ocornut/imgui_club ImDrawList* ImDrawList::CloneOutput() const { - ImDrawList* dst = IM_NEW(ImDrawList(_Data)); + ImDrawList* dst = IM_NEW(ImDrawList(NULL)); dst->CmdBuffer = CmdBuffer; dst->IdxBuffer = IdxBuffer; dst->VtxBuffer = VtxBuffer; @@ -449,7 +499,7 @@ void ImDrawList::AddDrawCmd() { ImDrawCmd draw_cmd; draw_cmd.ClipRect = _CmdHeader.ClipRect; // Same as calling ImDrawCmd_HeaderCopy() - draw_cmd.TextureId = _CmdHeader.TextureId; + draw_cmd.TexRef = _CmdHeader.TexRef; draw_cmd.VtxOffset = _CmdHeader.VtxOffset; draw_cmd.IdxOffset = IdxBuffer.Size; @@ -470,26 +520,45 @@ void ImDrawList::_PopUnusedDrawCmd() } } -void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data) +void ImDrawList::AddCallback(ImDrawCallback callback, void* userdata, size_t userdata_size) { IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; + IM_ASSERT(callback != NULL); IM_ASSERT(curr_cmd->UserCallback == NULL); if (curr_cmd->ElemCount != 0) { AddDrawCmd(); curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; } + curr_cmd->UserCallback = callback; - curr_cmd->UserCallbackData = callback_data; + if (userdata_size == 0) + { + // Store user data directly in command (no indirection) + curr_cmd->UserCallbackData = userdata; + curr_cmd->UserCallbackDataSize = 0; + curr_cmd->UserCallbackDataOffset = -1; + } + else + { + // Copy and store user data in a buffer + IM_ASSERT(userdata != NULL); + IM_ASSERT(userdata_size < (1u << 31)); + curr_cmd->UserCallbackData = NULL; // Will be resolved during Render() + curr_cmd->UserCallbackDataSize = (int)userdata_size; + curr_cmd->UserCallbackDataOffset = _CallbacksDataBuf.Size; + _CallbacksDataBuf.resize(_CallbacksDataBuf.Size + (int)userdata_size); + memcpy(_CallbacksDataBuf.Data + (size_t)curr_cmd->UserCallbackDataOffset, userdata, userdata_size); + } AddDrawCmd(); // Force a new command after us (see comment below) } -// Compare ClipRect, TextureId and VtxOffset with a single memcmp() +// Compare ClipRect, TexRef and VtxOffset with a single memcmp() #define ImDrawCmd_HeaderSize (offsetof(ImDrawCmd, VtxOffset) + sizeof(unsigned int)) -#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TextureId, VtxOffset -#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TextureId, VtxOffset +#define ImDrawCmd_HeaderCompare(CMD_LHS, CMD_RHS) (memcmp(CMD_LHS, CMD_RHS, ImDrawCmd_HeaderSize)) // Compare ClipRect, TexRef, VtxOffset +#define ImDrawCmd_HeaderCopy(CMD_DST, CMD_SRC) (memcpy(CMD_DST, CMD_SRC, ImDrawCmd_HeaderSize)) // Copy ClipRect, TexRef, VtxOffset #define ImDrawCmd_AreSequentialIdxOffset(CMD_0, CMD_1) (CMD_0->IdxOffset + CMD_0->ElemCount == CMD_1->IdxOffset) // Try to merge two last draw commands @@ -529,17 +598,20 @@ void ImDrawList::_OnChangedClipRect() curr_cmd->ClipRect = _CmdHeader.ClipRect; } -void ImDrawList::_OnChangedTextureID() +void ImDrawList::_OnChangedTexture() { // If current command is used with different settings we need to add a new command IM_ASSERT_PARANOID(CmdBuffer.Size > 0); ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1]; - if (curr_cmd->ElemCount != 0 && curr_cmd->TextureId != _CmdHeader.TextureId) + if (curr_cmd->ElemCount != 0 && curr_cmd->TexRef != _CmdHeader.TexRef) { AddDrawCmd(); return; } - IM_ASSERT(curr_cmd->UserCallback == NULL); + + // Unlike other _OnChangedXXX functions this may be called by ImFontAtlasUpdateDrawListsTextures() in more locations so we need to handle this case. + if (curr_cmd->UserCallback != NULL) + return; // Try to merge with previous command if it matches, else use current command ImDrawCmd* prev_cmd = curr_cmd - 1; @@ -548,7 +620,7 @@ void ImDrawList::_OnChangedTextureID() CmdBuffer.pop_back(); return; } - curr_cmd->TextureId = _CmdHeader.TextureId; + curr_cmd->TexRef = _CmdHeader.TexRef; } void ImDrawList::_OnChangedVtxOffset() @@ -609,27 +681,30 @@ void ImDrawList::PopClipRect() _OnChangedClipRect(); } -void ImDrawList::PushTextureID(ImTextureID texture_id) +void ImDrawList::PushTexture(ImTextureRef tex_ref) { - _TextureIdStack.push_back(texture_id); - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _TextureStack.push_back(tex_ref); + _CmdHeader.TexRef = tex_ref; + if (tex_ref._TexData != NULL) + IM_ASSERT(tex_ref._TexData->WantDestroyNextFrame == false); + _OnChangedTexture(); } -void ImDrawList::PopTextureID() +void ImDrawList::PopTexture() { - _TextureIdStack.pop_back(); - _CmdHeader.TextureId = (_TextureIdStack.Size == 0) ? (ImTextureID)NULL : _TextureIdStack.Data[_TextureIdStack.Size - 1]; - _OnChangedTextureID(); + _TextureStack.pop_back(); + _CmdHeader.TexRef = (_TextureStack.Size == 0) ? ImTextureRef() : _TextureStack.Data[_TextureStack.Size - 1]; + _OnChangedTexture(); } -// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTextureID()/PopTextureID(). -void ImDrawList::_SetTextureID(ImTextureID texture_id) +// This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). +void ImDrawList::_SetTexture(ImTextureRef tex_ref) { - if (_CmdHeader.TextureId == texture_id) + if (_CmdHeader.TexRef == tex_ref) return; - _CmdHeader.TextureId = texture_id; - _OnChangedTextureID(); + _CmdHeader.TexRef = tex_ref; + _TextureStack.back() = tex_ref; + _OnChangedTexture(); } // Reserve space for a number of vertices and indices. @@ -752,7 +827,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off - IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); + IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); @@ -815,7 +890,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area dm_y *= half_draw_size; - // Add temporary vertexes for the outer edges + // Add temporary vertices for the outer edges ImVec2* out_vtx = &temp_points[i2 * 2]; out_vtx[0].x = points[i2].x + dm_x; out_vtx[0].y = points[i2].y + dm_y; @@ -842,7 +917,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 idx1 = idx2; } - // Add vertexes for each point on the line + // Add vertices for each point on the line if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices @@ -1626,7 +1701,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathStroke(col, 0, thickness); } -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1634,8 +1709,7 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, // Accept null ranges if (text_begin == text_end || text_begin[0] == 0) return; - if (text_end == NULL) - text_end = text_begin + strlen(text_begin); + // No need to strlen() here: font->RenderText() will do it and may early out. // Pull default font/size from the shared ImDrawListSharedData instance if (font == NULL) @@ -1643,8 +1717,6 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, if (font_size == 0.0f) font_size = _Data->FontSize; - IM_ASSERT(font->ContainerAtlas->TexID == _CmdHeader.TextureId); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - ImVec4 clip_rect = _CmdHeader.ClipRect; if (cpu_fine_clip_rect) { @@ -1653,47 +1725,47 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); } - font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, (cpu_fine_clip_rect != NULL) ? ImDrawTextFlags_CpuFineClip : ImDrawTextFlags_None); } void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) { - AddText(NULL, 0.0f, pos, col, text_begin, text_end); + AddText(_Data->Font, _Data->FontSize, pos, col, text_begin, text_end); } -void ImDrawList::AddImage(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) +void ImDrawList::AddImage(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimRectUV(p_min, p_max, uv_min, uv_max, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageQuad(ImTextureID user_texture_id, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) +void ImDrawList::AddImageQuad(ImTextureRef tex_ref, const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, const ImVec2& uv4, ImU32 col) { if ((col & IM_COL32_A_MASK) == 0) return; - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); PrimReserve(6, 4); PrimQuadUV(p1, p2, p3, p4, uv1, uv2, uv3, uv4, col); if (push_texture_id) - PopTextureID(); + PopTexture(); } -void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) +void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1701,13 +1773,13 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi flags = FixRectCornerFlags(flags); if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col); + AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; } - const bool push_texture_id = user_texture_id != _CmdHeader.TextureId; + const bool push_texture_id = tex_ref != _CmdHeader.TexRef; if (push_texture_id) - PushTextureID(user_texture_id); + PushTexture(tex_ref); int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); @@ -1716,7 +1788,7 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); if (push_texture_id) - PopTextureID(); + PopTexture(); } //----------------------------------------------------------------------------- @@ -2144,7 +2216,7 @@ void ImDrawListSplitter::Merge(ImDrawList* draw_list) // If current command is used with different settings we need to add a new command ImDrawCmd* curr_cmd = &draw_list->CmdBuffer.Data[draw_list->CmdBuffer.Size - 1]; if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); @@ -2170,7 +2242,7 @@ void ImDrawListSplitter::SetCurrentChannel(ImDrawList* draw_list, int idx) if (curr_cmd == NULL) draw_list->AddDrawCmd(); else if (curr_cmd->ElemCount == 0) - ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TextureId, VtxOffset + ImDrawCmd_HeaderCopy(curr_cmd, &draw_list->_CmdHeader); // Copy ClipRect, TexRef, VtxOffset else if (ImDrawCmd_HeaderCompare(curr_cmd, &draw_list->_CmdHeader) != 0) draw_list->AddDrawCmd(); } @@ -2186,6 +2258,7 @@ void ImDrawData::Clear() CmdLists.resize(0); // The ImDrawList are NOT owned by ImDrawData but e.g. by ImGuiContext, so we don't clear them. DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.0f, 0.0f); OwnerViewport = NULL; + Textures = NULL; } // Important: 'out_list' is generally going to be draw_data->CmdLists, but may be another temporary list @@ -2222,6 +2295,12 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector if (sizeof(ImDrawIdx) == 2) IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above"); + // Resolve callback data pointers + if (draw_list->_CallbacksDataBuf.Size > 0) + for (ImDrawCmd& cmd : draw_list->CmdBuffer) + if (cmd.UserCallback != NULL && cmd.UserCallbackDataOffset != -1 && cmd.UserCallbackDataSize > 0) + cmd.UserCallbackData = draw_list->_CallbacksDataBuf.Data + cmd.UserCallbackDataOffset; + // Add to output list + records state in ImDrawData out_list->push_back(draw_list); draw_data->CmdListsCount++; @@ -2241,17 +2320,16 @@ void ImDrawData::DeIndexAllBuffers() { ImVector new_vtx_buffer; TotalVtxCount = TotalIdxCount = 0; - for (int i = 0; i < CmdListsCount; i++) + for (ImDrawList* draw_list : CmdLists) { - ImDrawList* cmd_list = CmdLists[i]; - if (cmd_list->IdxBuffer.empty()) + if (draw_list->IdxBuffer.empty()) continue; - new_vtx_buffer.resize(cmd_list->IdxBuffer.Size); - for (int j = 0; j < cmd_list->IdxBuffer.Size; j++) - new_vtx_buffer[j] = cmd_list->VtxBuffer[cmd_list->IdxBuffer[j]]; - cmd_list->VtxBuffer.swap(new_vtx_buffer); - cmd_list->IdxBuffer.resize(0); - TotalVtxCount += cmd_list->VtxBuffer.Size; + new_vtx_buffer.resize(draw_list->IdxBuffer.Size); + for (int j = 0; j < draw_list->IdxBuffer.Size; j++) + new_vtx_buffer[j] = draw_list->VtxBuffer[draw_list->IdxBuffer[j]]; + draw_list->VtxBuffer.swap(new_vtx_buffer); + draw_list->IdxBuffer.resize(0); + TotalVtxCount += draw_list->VtxBuffer.Size; } } @@ -2330,20 +2408,171 @@ void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, in // [SECTION] ImFontConfig //----------------------------------------------------------------------------- +// FIXME-NEWATLAS: Oversample specification could be more dynamic. For now, favoring automatic selection. ImFontConfig::ImFontConfig() { memset(this, 0, sizeof(*this)); FontDataOwnedByAtlas = true; - OversampleH = 2; - OversampleV = 1; + OversampleH = 0; // Auto == 1 or 2 depending on size + OversampleV = 0; // Auto == 1 GlyphMaxAdvanceX = FLT_MAX; RasterizerMultiply = 1.0f; RasterizerDensity = 1.0f; - EllipsisChar = (ImWchar)-1; + EllipsisChar = 0; +} + +//----------------------------------------------------------------------------- +// [SECTION] ImTextureData +//----------------------------------------------------------------------------- +// - ImTextureData::Create() +// - ImTextureData::DestroyPixels() +//----------------------------------------------------------------------------- + +int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return 1; + case ImTextureFormat_RGBA32: return 4; + } + IM_ASSERT(0); + return 0; +} + +const char* ImTextureDataGetStatusName(ImTextureStatus status) +{ + switch (status) + { + case ImTextureStatus_OK: return "OK"; + case ImTextureStatus_Destroyed: return "Destroyed"; + case ImTextureStatus_WantCreate: return "WantCreate"; + case ImTextureStatus_WantUpdates: return "WantUpdates"; + case ImTextureStatus_WantDestroy: return "WantDestroy"; + } + return "N/A"; +} + +const char* ImTextureDataGetFormatName(ImTextureFormat format) +{ + switch (format) + { + case ImTextureFormat_Alpha8: return "Alpha8"; + case ImTextureFormat_RGBA32: return "RGBA32"; + } + return "N/A"; +} + +void ImTextureData::Create(ImTextureFormat format, int w, int h) +{ + IM_ASSERT(Status == ImTextureStatus_Destroyed); + DestroyPixels(); + Format = format; + Status = ImTextureStatus_WantCreate; + Width = w; + Height = h; + BytesPerPixel = ImTextureDataGetFormatBytesPerPixel(format); + UseColors = false; + Pixels = (unsigned char*)IM_ALLOC(Width * Height * BytesPerPixel); + IM_ASSERT(Pixels != NULL); + memset(Pixels, 0, Width * Height * BytesPerPixel); + UsedRect.x = UsedRect.y = UsedRect.w = UsedRect.h = 0; + UpdateRect.x = UpdateRect.y = (unsigned short)~0; + UpdateRect.w = UpdateRect.h = 0; +} + +void ImTextureData::DestroyPixels() +{ + if (Pixels) + IM_FREE(Pixels); + Pixels = NULL; + UseColors = false; } //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas +// [SECTION] ImFontAtlas, ImFontAtlasBuilder +//----------------------------------------------------------------------------- +// - Default texture data encoded in ASCII +// - ImFontAtlas() +// - ImFontAtlas::Clear() +// - ImFontAtlas::CompactCache() +// - ImFontAtlas::ClearInputData() +// - ImFontAtlas::ClearTexData() +// - ImFontAtlas::ClearFonts() +//----------------------------------------------------------------------------- +// - ImFontAtlasUpdateNewFrame() +// - ImFontAtlasTextureBlockConvert() +// - ImFontAtlasTextureBlockPostProcess() +// - ImFontAtlasTextureBlockPostProcessMultiply() +// - ImFontAtlasTextureBlockFill() +// - ImFontAtlasTextureBlockCopy() +// - ImFontAtlasTextureBlockQueueUpload() +//----------------------------------------------------------------------------- +// - ImFontAtlas::GetTexDataAsAlpha8() [legacy] +// - ImFontAtlas::GetTexDataAsRGBA32() [legacy] +// - ImFontAtlas::Build() [legacy] +//----------------------------------------------------------------------------- +// - ImFontAtlas::AddFont() +// - ImFontAtlas::AddFontDefault() +// - ImFontAtlas::AddFontFromFileTTF() +// - ImFontAtlas::AddFontFromMemoryTTF() +// - ImFontAtlas::AddFontFromMemoryCompressedTTF() +// - ImFontAtlas::AddFontFromMemoryCompressedBase85TTF() +// - ImFontAtlas::RemoveFont() +// - ImFontAtlasBuildNotifySetFont() +//----------------------------------------------------------------------------- +// - ImFontAtlas::AddCustomRect() +// - ImFontAtlas::RemoveCustomRect() +// - ImFontAtlas::GetCustomRect() +// - ImFontAtlas::AddCustomRectFontGlyph() [legacy] +// - ImFontAtlas::AddCustomRectFontGlyphForSize() [legacy] +// - ImFontAtlasGetMouseCursorTexData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildMain() +// - ImFontAtlasBuildSetupFontLoader() +// - ImFontAtlasBuildPreloadAllGlyphRanges() +// - ImFontAtlasBuildUpdatePointers() +// - ImFontAtlasBuildRenderBitmapFromString() +// - ImFontAtlasBuildUpdateBasicTexData() +// - ImFontAtlasBuildUpdateLinesTexData() +// - ImFontAtlasBuildAddFont() +// - ImFontAtlasBuildSetupFontBakedEllipsis() +// - ImFontAtlasBuildSetupFontBakedBlanks() +// - ImFontAtlasBuildSetupFontBakedFallback() +// - ImFontAtlasBuildSetupFontSpecialGlyphs() +// - ImFontAtlasBuildDiscardBakes() +// - ImFontAtlasBuildDiscardFontBakedGlyph() +// - ImFontAtlasBuildDiscardFontBaked() +// - ImFontAtlasBuildDiscardFontBakes() +//----------------------------------------------------------------------------- +// - ImFontAtlasAddDrawListSharedData() +// - ImFontAtlasRemoveDrawListSharedData() +// - ImFontAtlasUpdateDrawListsTextures() +// - ImFontAtlasUpdateDrawListsSharedData() +//----------------------------------------------------------------------------- +// - ImFontAtlasBuildSetTexture() +// - ImFontAtlasBuildAddTexture() +// - ImFontAtlasBuildMakeSpace() +// - ImFontAtlasBuildRepackTexture() +// - ImFontAtlasBuildGrowTexture() +// - ImFontAtlasBuildRepackOrGrowTexture() +// - ImFontAtlasBuildGetTextureSizeEstimate() +// - ImFontAtlasBuildCompactTexture() +// - ImFontAtlasBuildInit() +// - ImFontAtlasBuildDestroy() +//----------------------------------------------------------------------------- +// - ImFontAtlasPackInit() +// - ImFontAtlasPackAllocRectEntry() +// - ImFontAtlasPackReuseRectEntry() +// - ImFontAtlasPackDiscardRect() +// - ImFontAtlasPackAddRect() +// - ImFontAtlasPackGetRect() +//----------------------------------------------------------------------------- +// - ImFontBaked_BuildGrowIndex() +// - ImFontBaked_BuildLoadGlyph() +// - ImFontBaked_BuildLoadGlyphAdvanceX() +// - ImFontAtlasDebugLogTextureRequests() +//----------------------------------------------------------------------------- +// - ImFontAtlasGetFontLoaderForStbTruetype() //----------------------------------------------------------------------------- // A work of art lies ahead! (. = white layer, X = black layer, others are blank) @@ -2393,147 +2622,480 @@ static const ImVec2 FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[ImGuiMouseCursor_COUNT][3 { ImVec2(73,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNESW { ImVec2(55,0), ImVec2(17,17), ImVec2( 8, 8) }, // ImGuiMouseCursor_ResizeNWSE { ImVec2(91,0), ImVec2(17,22), ImVec2( 5, 0) }, // ImGuiMouseCursor_Hand + { ImVec2(0,3), ImVec2(12,19), ImVec2(0, 0) }, // ImGuiMouseCursor_Wait // Arrow + custom code in ImGui::RenderMouseCursor() + { ImVec2(0,3), ImVec2(12,19), ImVec2(0, 0) }, // ImGuiMouseCursor_Progress // Arrow + custom code in ImGui::RenderMouseCursor() { ImVec2(109,0),ImVec2(13,15), ImVec2( 6, 7) }, // ImGuiMouseCursor_NotAllowed }; +#define IM_FONTGLYPH_INDEX_UNUSED ((ImU16)-1) // 0xFFFF +#define IM_FONTGLYPH_INDEX_NOT_FOUND ((ImU16)-2) // 0xFFFE + ImFontAtlas::ImFontAtlas() { memset(this, 0, sizeof(*this)); + TexDesiredFormat = ImTextureFormat_RGBA32; TexGlyphPadding = 1; - PackIdMouseCursors = PackIdLines = -1; + TexMinWidth = 512; + TexMinHeight = 128; + TexMaxWidth = 8192; + TexMaxHeight = 8192; + TexRef._TexID = ImTextureID_Invalid; + RendererHasTextures = false; // Assumed false by default, as apps can call e.g Atlas::Build() after backend init and before ImGui can update. + TexNextUniqueID = 1; + FontNextUniqueID = 1; + Builder = NULL; } ImFontAtlas::~ImFontAtlas() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - Clear(); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + TexList.clear_delete(); + TexData = NULL; } -void ImFontAtlas::ClearInputData() +// If you call this mid-frame, you would need to add new font and bind them! +void ImFontAtlas::Clear() { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - for (ImFontConfig& font_cfg : ConfigData) - if (font_cfg.FontData && font_cfg.FontDataOwnedByAtlas) - { - IM_FREE(font_cfg.FontData); - font_cfg.FontData = NULL; - } + bool backup_renderer_has_textures = RendererHasTextures; + RendererHasTextures = false; // Full Clear() is supported, but ClearTexData() only isn't. + ClearFonts(); + ClearTexData(); + RendererHasTextures = backup_renderer_has_textures; +} + +void ImFontAtlas::CompactCache() +{ + ImFontAtlasTextureCompact(this); +} + +void ImFontAtlas::SetFontLoader(const ImFontLoader* font_loader) +{ + ImFontAtlasBuildSetupFontLoader(this, font_loader); +} + +void ImFontAtlas::ClearInputData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + + for (ImFont* font : Fonts) + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig& font_cfg : Sources) + ImFontAtlasFontDestroySourceData(this, &font_cfg); + for (ImFont* font : Fonts) + { + // When clearing this we lose access to the font name and other information used to build the font. + font->Sources.clear(); + font->Flags |= ImFontFlags_NoLoadGlyphs; + } + Sources.clear(); +} + +// Clear CPU-side copy of the texture data. +void ImFontAtlas::ClearTexData() +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT(RendererHasTextures == false && "Not supported for dynamic atlases, but you may call Clear()."); + for (ImTextureData* tex : TexList) + tex->DestroyPixels(); + //Locked = true; // Hoped to be able to lock this down but some reload patterns may not be happy with it. +} - // When clearing this we lose access to the font name and other information used to build the font. +void ImFontAtlas::ClearFonts() +{ + // FIXME-NEWATLAS: Illegal to remove currently bound font. + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); for (ImFont* font : Fonts) - if (font->ConfigData >= ConfigData.Data && font->ConfigData < ConfigData.Data + ConfigData.Size) + ImFontAtlasBuildNotifySetFont(this, font, NULL); + ImFontAtlasBuildDestroy(this); + ClearInputData(); + Fonts.clear_delete(); + TexIsBuilt = false; + for (ImDrawListSharedData* shared_data : DrawListSharedDatas) + if (shared_data->FontAtlas == this) { - font->ConfigData = NULL; - font->ConfigDataCount = 0; + shared_data->Font = NULL; + shared_data->FontScale = shared_data->FontSize = 0.0f; } - ConfigData.clear(); - CustomRects.clear(); - PackIdMouseCursors = PackIdLines = -1; - // Important: we leave TexReady untouched } -void ImFontAtlas::ClearTexData() +static void ImFontAtlasBuildUpdateRendererHasTexturesFromContext(ImFontAtlas* atlas) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - if (TexPixelsAlpha8) - IM_FREE(TexPixelsAlpha8); - if (TexPixelsRGBA32) - IM_FREE(TexPixelsRGBA32); - TexPixelsAlpha8 = NULL; - TexPixelsRGBA32 = NULL; - TexPixelsUseColors = false; - // Important: we leave TexReady untouched + // [LEGACY] Copy back the ImGuiBackendFlags_RendererHasTextures flag from ImGui context. + // - This is the 1% exceptional case where that dependency if useful, to bypass an issue where otherwise at the + // time of an early call to Build(), it would be impossible for us to tell if the backend supports texture update. + // - Without this hack, we would have quite a pitfall as many legacy codebases have an early call to Build(). + // Whereas conversely, the portion of people using ImDrawList without ImGui is expected to be pathologically rare. + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (ImGuiContext* imgui_ctx = shared_data->Context) + { + atlas->RendererHasTextures = (imgui_ctx->IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) != 0; + break; + } } -void ImFontAtlas::ClearFonts() +// Called by NewFrame() for atlases owned by a context. +// If you manually manage font atlases, you'll need to call this yourself. +// - 'frame_count' needs to be provided because we can gc/prioritize baked fonts based on their age. +// - 'frame_count' may not match those of all imgui contexts using this atlas, as contexts may be updated as different frequencies. But generally you can use ImGui::GetFrameCount() on one of your context. +void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - Fonts.clear_delete(); - TexReady = false; + IM_ASSERT(atlas->Builder == NULL || atlas->Builder->FrameCount < frame_count); // Protection against being called twice. + atlas->RendererHasTextures = renderer_has_textures; + + // Check that font atlas was built or backend support texture reload in which case we can build now + if (atlas->RendererHasTextures) + { + atlas->TexIsBuilt = true; + if (atlas->Builder == NULL) // This will only happen if fonts were not already loaded. + ImFontAtlasBuildMain(atlas); + } + // Legacy backend + if (!atlas->RendererHasTextures) + IM_ASSERT_USER_ERROR(atlas->TexIsBuilt, "Backend does not support ImGuiBackendFlags_RendererHasTextures, and font atlas is not built! Update backend OR make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()."); + if (atlas->TexIsBuilt && atlas->Builder->PreloadedAllGlyphsRanges) + IM_ASSERT_USER_ERROR(atlas->RendererHasTextures == false, "Called ImFontAtlas::Build() before ImGuiBackendFlags_RendererHasTextures got set! With new backends: you don't need to call Build()."); + + // Clear BakedCurrent cache, this is important because it ensure the uncached path gets taken once. + // We also rely on ImFontBaked* pointers never crossing frames. + ImFontAtlasBuilder* builder = atlas->Builder; + builder->FrameCount = frame_count; + for (ImFont* font : atlas->Fonts) + font->LastBaked = NULL; + + // Garbage collect BakedPool + if (builder->BakedDiscardedCount > 0) + { + int dst_n = 0, src_n = 0; + for (; src_n < builder->BakedPool.Size; src_n++) + { + ImFontBaked* p_src = &builder->BakedPool[src_n]; + if (p_src->WantDestroy) + continue; + ImFontBaked* p_dst = &builder->BakedPool[dst_n++]; + if (p_dst == p_src) + continue; + memcpy(p_dst, p_src, sizeof(ImFontBaked)); + builder->BakedMap.SetVoidPtr(p_dst->BakedId, p_dst); + } + IM_ASSERT(dst_n + builder->BakedDiscardedCount == src_n); + builder->BakedPool.Size -= builder->BakedDiscardedCount; + builder->BakedDiscardedCount = 0; + } + + // Update texture status + for (int tex_n = 0; tex_n < atlas->TexList.Size; tex_n++) + { + ImTextureData* tex = atlas->TexList[tex_n]; + bool remove_from_list = false; + if (tex->Status == ImTextureStatus_OK) + { + tex->Updates.resize(0); + tex->UpdateRect.x = tex->UpdateRect.y = (unsigned short)~0; + tex->UpdateRect.w = tex->UpdateRect.h = 0; + } + if (tex->Status == ImTextureStatus_WantCreate && atlas->RendererHasTextures) + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture's TexID/BackendUserData but did not update Status to OK."); + + // Request destroy + // - Keep bool to true in order to differentiate a planned destroy vs a destroy decided by the backend. + // - We don't destroy pixels right away, as backend may have an in-flight copy from RAM. + if (tex->WantDestroyNextFrame && tex->Status != ImTextureStatus_Destroyed && tex->Status != ImTextureStatus_WantDestroy) + { + IM_ASSERT(tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates); + tex->Status = ImTextureStatus_WantDestroy; + } + + // If a texture has never reached the backend, they don't need to know about it. + // (note: backends between 1.92.0 and 1.92.4 could set an already destroyed texture to ImTextureStatus_WantDestroy + // when invalidating graphics objects twice, which would previously remove it from the list and crash.) + if (tex->Status == ImTextureStatus_WantDestroy && tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL) + tex->Status = ImTextureStatus_Destroyed; + + // Process texture being destroyed + if (tex->Status == ImTextureStatus_Destroyed) + { + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == NULL && "Backend set texture Status to Destroyed but did not clear TexID/BackendUserData!"); + if (tex->WantDestroyNextFrame) + remove_from_list = true; // Destroy was scheduled by us + else + tex->Status = ImTextureStatus_WantCreate; // Destroy was done was backend: recreate it (e.g. freed resources mid-run) + } + + // The backend may need defer destroying by a few frames, to handle texture used by previous in-flight rendering. + // We allow the texture staying in _WantDestroy state and increment a counter which the backend can use to take its decision. + if (tex->Status == ImTextureStatus_WantDestroy) + tex->UnusedFrames++; + + // Destroy and remove + if (remove_from_list) + { + IM_ASSERT(atlas->TexData != tex); + tex->DestroyPixels(); + IM_DELETE(tex); + atlas->TexList.erase(atlas->TexList.begin() + tex_n); + tex_n--; + } + } } -void ImFontAtlas::Clear() +void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h) { - ClearInputData(); - ClearTexData(); - ClearFonts(); + IM_ASSERT(src_pixels != NULL && dst_pixels != NULL); + if (src_fmt == dst_fmt) + { + int line_sz = w * ImTextureDataGetFormatBytesPerPixel(src_fmt); + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + memcpy(dst_pixels, src_pixels, line_sz); + } + else if (src_fmt == ImTextureFormat_Alpha8 && dst_fmt == ImTextureFormat_RGBA32) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU8* src_p = (const ImU8*)src_pixels; + ImU32* dst_p = (ImU32*)(void*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = IM_COL32(255, 255, 255, (unsigned int)(*src_p++)); + } + } + else if (src_fmt == ImTextureFormat_RGBA32 && dst_fmt == ImTextureFormat_Alpha8) + { + for (int ny = h; ny > 0; ny--, src_pixels += src_pitch, dst_pixels += dst_pitch) + { + const ImU32* src_p = (const ImU32*)(void*)src_pixels; + ImU8* dst_p = (ImU8*)dst_pixels; + for (int nx = w; nx > 0; nx--) + *dst_p++ = ((*src_p++) >> IM_COL32_A_SHIFT) & 0xFF; + } + } + else + { + IM_ASSERT(0); + } } -void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +// Source buffer may be written to (used for in-place mods). +// Post-process hooks may eventually be added here. +void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data) { - // Build atlas on demand - if (TexPixelsAlpha8 == NULL) - Build(); + // Multiply operator (legacy) + if (data->FontSrc->RasterizerMultiply != 1.0f) + ImFontAtlasTextureBlockPostProcessMultiply(data, data->FontSrc->RasterizerMultiply); +} - *out_pixels = TexPixelsAlpha8; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 1; +void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor) +{ + unsigned char* pixels = (unsigned char*)data->Pixels; + int pitch = data->Pitch; + if (data->Format == ImTextureFormat_Alpha8) + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU8* p = (ImU8*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int v = ImMin((unsigned int)(*p * multiply_factor), (unsigned int)255); + *p = (unsigned char)v; + } + } + } + else if (data->Format == ImTextureFormat_RGBA32) //-V547 + { + for (int ny = data->Height; ny > 0; ny--, pixels += pitch) + { + ImU32* p = (ImU32*)(void*)pixels; + for (int nx = data->Width; nx > 0; nx--, p++) + { + unsigned int a = ImMin((unsigned int)(((*p >> IM_COL32_A_SHIFT) & 0xFF) * multiply_factor), (unsigned int)255); + *p = IM_COL32((*p >> IM_COL32_R_SHIFT) & 0xFF, (*p >> IM_COL32_G_SHIFT) & 0xFF, (*p >> IM_COL32_B_SHIFT) & 0xFF, a); + } + } + } + else + { + IM_ASSERT(0); + } } -void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +// Fill with single color. We don't use this directly but it is convenient for anyone working on uploading custom rects. +void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col) { - // Convert to RGBA32 format on demand - // Although it is likely to be the most commonly used format, our font rendering is 1 channel / 8 bpp - if (!TexPixelsRGBA32) + if (dst_tex->Format == ImTextureFormat_Alpha8) + { + ImU8 col_a = (col >> IM_COL32_A_SHIFT) & 0xFF; + for (int y = 0; y < h; y++) + memset((ImU8*)dst_tex->GetPixelsAt(dst_x, dst_y + y), col_a, w); + } + else { - unsigned char* pixels = NULL; - GetTexDataAsAlpha8(&pixels, NULL, NULL); - if (pixels) + for (int y = 0; y < h; y++) { - TexPixelsRGBA32 = (unsigned int*)IM_ALLOC((size_t)TexWidth * (size_t)TexHeight * 4); - const unsigned char* src = pixels; - unsigned int* dst = TexPixelsRGBA32; - for (int n = TexWidth * TexHeight; n > 0; n--) - *dst++ = IM_COL32(255, 255, 255, (unsigned int)(*src++)); + ImU32* p = (ImU32*)(void*)dst_tex->GetPixelsAt(dst_x, dst_y + y); + for (int x = w; x > 0; x--, p++) + *p = col; } } +} + +// Copy block from one texture to another +void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h) +{ + IM_ASSERT(src_tex->Pixels != NULL && dst_tex->Pixels != NULL); + IM_ASSERT(src_tex->Format == dst_tex->Format); + IM_ASSERT(src_x >= 0 && src_x + w <= src_tex->Width); + IM_ASSERT(src_y >= 0 && src_y + h <= src_tex->Height); + IM_ASSERT(dst_x >= 0 && dst_x + w <= dst_tex->Width); + IM_ASSERT(dst_y >= 0 && dst_y + h <= dst_tex->Height); + for (int y = 0; y < h; y++) + memcpy(dst_tex->GetPixelsAt(dst_x, dst_y + y), src_tex->GetPixelsAt(src_x, src_y + y), w * dst_tex->BytesPerPixel); +} + +// Queue texture block update for renderer backend +void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h) +{ + IM_ASSERT(tex->Status != ImTextureStatus_WantDestroy && tex->Status != ImTextureStatus_Destroyed); + IM_ASSERT(x >= 0 && x <= 0xFFFF && y >= 0 && y <= 0xFFFF && w >= 0 && x + w <= 0x10000 && h >= 0 && y + h <= 0x10000); + IM_UNUSED(atlas); + + ImTextureRect req = { (unsigned short)x, (unsigned short)y, (unsigned short)w, (unsigned short)h }; + int new_x1 = ImMax(tex->UpdateRect.w == 0 ? 0 : tex->UpdateRect.x + tex->UpdateRect.w, req.x + req.w); + int new_y1 = ImMax(tex->UpdateRect.h == 0 ? 0 : tex->UpdateRect.y + tex->UpdateRect.h, req.y + req.h); + tex->UpdateRect.x = ImMin(tex->UpdateRect.x, req.x); + tex->UpdateRect.y = ImMin(tex->UpdateRect.y, req.y); + tex->UpdateRect.w = (unsigned short)(new_x1 - tex->UpdateRect.x); + tex->UpdateRect.h = (unsigned short)(new_y1 - tex->UpdateRect.y); + tex->UsedRect.x = ImMin(tex->UsedRect.x, req.x); + tex->UsedRect.y = ImMin(tex->UsedRect.y, req.y); + tex->UsedRect.w = (unsigned short)(ImMax(tex->UsedRect.x + tex->UsedRect.w, req.x + req.w) - tex->UsedRect.x); + tex->UsedRect.h = (unsigned short)(ImMax(tex->UsedRect.y + tex->UsedRect.h, req.y + req.h) - tex->UsedRect.y); + atlas->TexIsBuilt = false; + + // No need to queue if status is == ImTextureStatus_WantCreate + if (tex->Status == ImTextureStatus_OK || tex->Status == ImTextureStatus_WantUpdates) + { + tex->Status = ImTextureStatus_WantUpdates; + tex->Updates.push_back(req); + } +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +static void GetTexDataAsFormat(ImFontAtlas* atlas, ImTextureFormat format, unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + ImTextureData* tex = atlas->TexData; + if (!atlas->TexIsBuilt || tex == NULL || tex->Pixels == NULL || atlas->TexDesiredFormat != format) + { + atlas->TexDesiredFormat = format; + atlas->Build(); + tex = atlas->TexData; + } + if (out_pixels) { *out_pixels = (unsigned char*)tex->Pixels; }; + if (out_width) { *out_width = tex->Width; }; + if (out_height) { *out_height = tex->Height; }; + if (out_bytes_per_pixel) { *out_bytes_per_pixel = tex->BytesPerPixel; } +} + +void ImFontAtlas::GetTexDataAsAlpha8(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_Alpha8, out_pixels, out_width, out_height, out_bytes_per_pixel); +} + +void ImFontAtlas::GetTexDataAsRGBA32(unsigned char** out_pixels, int* out_width, int* out_height, int* out_bytes_per_pixel) +{ + GetTexDataAsFormat(this, ImTextureFormat_RGBA32, out_pixels, out_width, out_height, out_bytes_per_pixel); +} - *out_pixels = (unsigned char*)TexPixelsRGBA32; - if (out_width) *out_width = TexWidth; - if (out_height) *out_height = TexHeight; - if (out_bytes_per_pixel) *out_bytes_per_pixel = 4; +bool ImFontAtlas::Build() +{ + ImFontAtlasBuildMain(this); + return true; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg) +ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); - IM_ASSERT(font_cfg->FontData != NULL && font_cfg->FontDataSize > 0); - IM_ASSERT(font_cfg->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); - IM_ASSERT(font_cfg->OversampleH > 0 && font_cfg->OversampleV > 0 && "Is ImFontConfig struct correctly initialized?"); + // Sanity Checks + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + IM_ASSERT((font_cfg_in->FontData != NULL && font_cfg_in->FontDataSize > 0) || (font_cfg_in->FontLoader != NULL)); + //IM_ASSERT(font_cfg_in->SizePixels > 0.0f && "Is ImFontConfig struct correctly initialized?"); + IM_ASSERT(font_cfg_in->RasterizerDensity > 0.0f && "Is ImFontConfig struct correctly initialized?"); + if (font_cfg_in->GlyphOffset.x != 0.0f || font_cfg_in->GlyphOffset.y != 0.0f || font_cfg_in->GlyphMinAdvanceX != 0.0f || font_cfg_in->GlyphMaxAdvanceX != FLT_MAX) + IM_ASSERT(font_cfg_in->SizePixels != 0.0f && "Specifying glyph offset/advances requires a reference size to base it on."); + + // Lazily create builder on the first call to AddFont + if (Builder == NULL) + ImFontAtlasBuildInit(this); // Create new font - if (!font_cfg->MergeMode) - Fonts.push_back(IM_NEW(ImFont)); + ImFont* font; + if (!font_cfg_in->MergeMode) + { + font = IM_NEW(ImFont)(); + font->FontId = FontNextUniqueID++; + font->Flags = font_cfg_in->Flags; + font->LegacySize = font_cfg_in->SizePixels; + font->CurrentRasterizerDensity = font_cfg_in->RasterizerDensity; + Fonts.push_back(font); + } else + { IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. You can use ImGui::GetIO().Fonts->AddFontDefault() to add the default imgui font. + font = font_cfg_in->DstFont ? font_cfg_in->DstFont : Fonts.back(); + } + + // Add to list + Sources.push_back(*font_cfg_in); + ImFontConfig* font_cfg = &Sources.back(); + if (font_cfg->DstFont == NULL) + font_cfg->DstFont = font; + font->Sources.push_back(font_cfg); + ImFontAtlasBuildUpdatePointers(this); // Pointers to Sources are otherwise dangling after we called Sources.push_back(). - ConfigData.push_back(*font_cfg); - ImFontConfig& new_font_cfg = ConfigData.back(); - if (new_font_cfg.DstFont == NULL) - new_font_cfg.DstFont = Fonts.back(); - if (!new_font_cfg.FontDataOwnedByAtlas) + if (font_cfg->FontDataOwnedByAtlas == false) { - new_font_cfg.FontData = IM_ALLOC(new_font_cfg.FontDataSize); - new_font_cfg.FontDataOwnedByAtlas = true; - memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize); + font_cfg->FontDataOwnedByAtlas = true; + font_cfg->FontData = ImMemdup(font_cfg->FontData, (size_t)font_cfg->FontDataSize); } - if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1) - new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar; + // Sanity check + // We don't round cfg.SizePixels yet as relative size of merged fonts are used afterwards. + if (font_cfg->GlyphExcludeRanges != NULL) + { + int size = 0; + for (const ImWchar* p = font_cfg->GlyphExcludeRanges; p[0] != 0; p++, size++) {} + IM_ASSERT((size & 1) == 0 && "GlyphExcludeRanges[] size must be multiple of two!"); + IM_ASSERT((size <= 64) && "GlyphExcludeRanges[] size must be small!"); + font_cfg->GlyphExcludeRanges = (ImWchar*)ImMemdup(font_cfg->GlyphExcludeRanges, sizeof(font_cfg->GlyphExcludeRanges[0]) * (size + 1)); + } + if (font_cfg->FontLoader != NULL) + { + IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); + IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. + } + IM_ASSERT(font_cfg->FontLoaderData == NULL); - ImFontAtlasUpdateConfigDataPointers(this); + if (!ImFontAtlasFontSourceInit(this, font_cfg)) + { + // Rollback (this is a fragile/rarely exercised code-path. TestSuite's "misc_atlas_add_invalid_font" aim to test this) + ImFontAtlasFontDestroySourceData(this, font_cfg); + Sources.pop_back(); + font->Sources.pop_back(); + if (!font_cfg->MergeMode) + { + IM_DELETE(font); + Fonts.pop_back(); + } + return NULL; + } + ImFontAtlasFontSourceAddToFont(this, font, font_cfg); - // Invalidate texture - TexReady = false; - ClearTexData(); - return new_font_cfg.DstFont; + return font; } // Default font TTF is compressed with stb_compress then base85 encoded (see misc/fonts/binary_to_compressed_c.cpp for encoder) static unsigned int stb_decompress_length(const unsigned char* input); static unsigned int stb_decompress(unsigned char* output, const unsigned char* input, unsigned int length); -static const char* GetDefaultCompressedFontDataTTFBase85(); static unsigned int Decode85Byte(char c) { return c >= '\\' ? c-36 : c-35; } static void Decode85(const unsigned char* src, unsigned char* dst) { @@ -2545,10 +3107,15 @@ static void Decode85(const unsigned char* src, unsigned char* dst) dst += 4; } } +#ifndef IMGUI_DISABLE_DEFAULT_FONT +static const char* GetDefaultCompressedFontDataTTF(int* out_size); +#endif // Load embedded ProggyClean.ttf at size 13, disable oversampling +// If you want a similar font which may be better scaled, consider using ProggyVector from the same author! ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) { +#ifndef IMGUI_DISABLE_DEFAULT_FONT ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); if (!font_cfg_template) { @@ -2558,24 +3125,32 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template) if (font_cfg.SizePixels <= 0.0f) font_cfg.SizePixels = 13.0f * 1.0f; if (font_cfg.Name[0] == '\0') - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels); + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; - font_cfg.GlyphOffset.y = 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units + font_cfg.GlyphOffset.y += 1.0f * IM_TRUNC(font_cfg.SizePixels / 13.0f); // Add +1 offset per 13 units - const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85(); - const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault(); - ImFont* font = AddFontFromMemoryCompressedBase85TTF(ttf_compressed_base85, font_cfg.SizePixels, &font_cfg, glyph_ranges); - return font; + int ttf_compressed_size = 0; + const char* ttf_compressed = GetDefaultCompressedFontDataTTF(&ttf_compressed_size); + return AddFontFromMemoryCompressedTTF(ttf_compressed, ttf_compressed_size, font_cfg.SizePixels, &font_cfg); +#else + IM_ASSERT(0 && "AddFontDefault() disabled in this build."); + IM_UNUSED(font_cfg_template); + return NULL; +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT } ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); size_t data_size = 0; void* data = ImFileLoadToMemory(filename, "rb", &data_size, 0); if (!data) { - IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + if (font_cfg_template == NULL || (font_cfg_template->Flags & ImFontFlags_NoLoadError) == 0) + { + IMGUI_DEBUG_LOG("While loading '%s'\n", filename); + IM_ASSERT_USER_ERROR(0, "Could not load font file!"); + } return NULL; } ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); @@ -2583,8 +3158,8 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, { // Store a short copy of filename into into the font name for convenience const char* p; - for (p = filename + strlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} - ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", p, size_pixels); + for (p = filename + ImStrlen(filename); p > filename && p[-1] != '/' && p[-1] != '\\'; p--) {} + ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s", p); } return AddFontFromMemoryTTF(data, (int)data_size, size_pixels, &font_cfg, glyph_ranges); } @@ -2592,7 +3167,7 @@ ImFont* ImFontAtlas::AddFontFromFileTTF(const char* filename, float size_pixels, // NB: Transfer ownership of 'ttf_data' to ImFontAtlas, unless font_cfg_template->FontDataOwnedByAtlas == false. Owned TTF buffer will be deleted after Build(). ImFont* ImFontAtlas::AddFontFromMemoryTTF(void* font_data, int font_data_size, float size_pixels, const ImFontConfig* font_cfg_template, const ImWchar* glyph_ranges) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); ImFontConfig font_cfg = font_cfg_template ? *font_cfg_template : ImFontConfig(); IM_ASSERT(font_cfg.FontData == NULL); IM_ASSERT(font_data_size > 100 && "Incorrect value for font_data_size!"); // Heuristic to prevent accidentally passing a wrong value to font_data_size. @@ -2618,7 +3193,7 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedTTF(const void* compressed_ttf_d ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed_ttf_data_base85, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) { - int compressed_ttf_size = (((int)strlen(compressed_ttf_data_base85) + 4) / 5) * 4; + int compressed_ttf_size = (((int)ImStrlen(compressed_ttf_data_base85) + 4) / 5) * 4; void* compressed_ttf = IM_ALLOC((size_t)compressed_ttf_size); Decode85((const unsigned char*)compressed_ttf_data_base85, (unsigned char*)compressed_ttf); ImFont* font = AddFontFromMemoryCompressedTTF(compressed_ttf, compressed_ttf_size, size_pixels, font_cfg, glyph_ranges); @@ -2626,649 +3201,1576 @@ ImFont* ImFontAtlas::AddFontFromMemoryCompressedBase85TTF(const char* compressed return font; } -int ImFontAtlas::AddCustomRectRegular(int width, int height) +// On font removal we need to remove references (otherwise we could queue removal?) +// We allow old_font == new_font which forces updating all values (e.g. sizes) +void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + { + if (shared_data->Font == old_font) + shared_data->Font = new_font; + if (ImGuiContext* ctx = shared_data->Context) + { + if (ctx->IO.FontDefault == old_font) + ctx->IO.FontDefault = new_font; + if (ctx->Font == old_font) + { + ImGuiContext* curr_ctx = ImGui::GetCurrentContext(); + bool need_bind_ctx = ctx != curr_ctx; + if (need_bind_ctx) + ImGui::SetCurrentContext(ctx); + ImGui::SetCurrentFont(new_font, ctx->FontSizeBase, ctx->FontSize); + if (need_bind_ctx) + ImGui::SetCurrentContext(curr_ctx); + } + for (ImFontStackData& font_stack_data : ctx->FontStack) + if (font_stack_data.Font == old_font) + font_stack_data.Font = new_font; + } + } +} + +void ImFontAtlas::RemoveFont(ImFont* font) +{ + IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas!"); + font->ClearOutputData(); + + ImFontAtlasFontDestroyOutput(this, font); + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontDestroySourceData(this, src); + for (int src_n = 0; src_n < Sources.Size; src_n++) + if (Sources[src_n].DstFont == font) + Sources.erase(&Sources[src_n--]); + + bool removed = Fonts.find_erase(font); + IM_ASSERT(removed); + IM_UNUSED(removed); + + ImFontAtlasBuildUpdatePointers(this); + + font->OwnerAtlas = NULL; + IM_DELETE(font); + + // Notify external systems + ImFont* new_current_font = Fonts.empty() ? NULL : Fonts[0]; + ImFontAtlasBuildNotifySetFont(this, font, new_current_font); +} + +// At it is common to do an AddCustomRect() followed by a GetCustomRect(), we provide an optional 'ImFontAtlasRect* out_r = NULL' argument to retrieve the info straight away. +ImFontAtlasRectId ImFontAtlas::AddCustomRect(int width, int height, ImFontAtlasRect* out_r) { IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index + + if (Builder == NULL) + ImFontAtlasBuildInit(this); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + if (out_r != NULL) + GetCustomRect(r_id, out_r); + + if (RendererHasTextures) + { + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + } + return r_id; } -int ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar id, int width, int height, float advance_x, const ImVec2& offset) +void ImFontAtlas::RemoveCustomRect(ImFontAtlasRectId id) +{ + if (ImFontAtlasPackGetRectSafe(this, id) == NULL) + return; + ImFontAtlasPackDiscardRect(this, id); +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +// This API does not make sense anymore with scalable fonts. +// - Prefer adding a font source (ImFontConfig) using a custom/procedural loader. +// - You may use ImFontFlags_LockBakedSizes to limit an existing font to known baked sizes: +// ImFont* myfont = io.Fonts->AddFontFromFileTTF(....); +// myfont->GetFontBaked(16.0f); +// myfont->Flags |= ImFontFlags_LockBakedSizes; +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyph(ImFont* font, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) +{ + float font_size = font->LegacySize; + return AddCustomRectFontGlyphForSize(font, font_size, codepoint, width, height, advance_x, offset); +} +// FIXME: we automatically set glyph.Colored=true by default. +// If you need to alter this, you can write 'font->Glyphs.back()->Colored' after calling AddCustomRectFontGlyph(). +ImFontAtlasRectId ImFontAtlas::AddCustomRectFontGlyphForSize(ImFont* font, float font_size, ImWchar codepoint, int width, int height, float advance_x, const ImVec2& offset) { #ifdef IMGUI_USE_WCHAR32 - IM_ASSERT(id <= IM_UNICODE_CODEPOINT_MAX); + IM_ASSERT(codepoint <= IM_UNICODE_CODEPOINT_MAX); #endif IM_ASSERT(font != NULL); IM_ASSERT(width > 0 && width <= 0xFFFF); IM_ASSERT(height > 0 && height <= 0xFFFF); - ImFontAtlasCustomRect r; - r.Width = (unsigned short)width; - r.Height = (unsigned short)height; - r.GlyphID = id; - r.GlyphAdvanceX = advance_x; - r.GlyphOffset = offset; - r.Font = font; - CustomRects.push_back(r); - return CustomRects.Size - 1; // Return index -} -void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* out_uv_min, ImVec2* out_uv_max) const -{ - IM_ASSERT(TexWidth > 0 && TexHeight > 0); // Font atlas needs to be built before we can calculate UV coordinates - IM_ASSERT(rect->IsPacked()); // Make sure the rectangle has been packed - *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y); - *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y); + ImFontBaked* baked = font->GetFontBaked(font_size); + + ImFontAtlasRectId r_id = ImFontAtlasPackAddRect(this, width, height); + if (r_id == ImFontAtlasRectId_Invalid) + return ImFontAtlasRectId_Invalid; + ImTextureRect* r = ImFontAtlasPackGetRect(this, r_id); + if (RendererHasTextures) + ImFontAtlasTextureBlockQueueUpload(this, TexData, r->x, r->y, r->w, r->h); + + if (baked->IsGlyphLoaded(codepoint)) + ImFontAtlasBakedDiscardFontGlyph(this, font, baked, baked->FindGlyph(codepoint)); + + ImFontGlyph glyph; + glyph.Codepoint = codepoint; + glyph.AdvanceX = advance_x; + glyph.X0 = offset.x; + glyph.Y0 = offset.y; + glyph.X1 = offset.x + r->w; + glyph.Y1 = offset.y + r->h; + glyph.Visible = true; + glyph.Colored = true; // FIXME: Arbitrary + glyph.PackId = r_id; + ImFontAtlasBakedAddFontGlyph(this, baked, font->Sources[0], &glyph); + return r_id; +} +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +bool ImFontAtlas::GetCustomRect(ImFontAtlasRectId id, ImFontAtlasRect* out_r) const +{ + ImTextureRect* r = ImFontAtlasPackGetRectSafe((ImFontAtlas*)this, id); + if (r == NULL) + return false; + IM_ASSERT(TexData->Width > 0 && TexData->Height > 0); // Font atlas needs to be built before we can calculate UV coordinates + if (out_r == NULL) + return true; + out_r->x = r->x; + out_r->y = r->y; + out_r->w = r->w; + out_r->h = r->h; + out_r->uv0 = ImVec2((float)(r->x), (float)(r->y)) * TexUvScale; + out_r->uv1 = ImVec2((float)(r->x + r->w), (float)(r->y + r->h)) * TexUvScale; + return true; } -bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) +bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]) { if (cursor_type <= ImGuiMouseCursor_None || cursor_type >= ImGuiMouseCursor_COUNT) return false; - if (Flags & ImFontAtlasFlags_NoMouseCursors) + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) return false; - IM_ASSERT(PackIdMouseCursors != -1); - ImFontAtlasCustomRect* r = GetCustomRectByIndex(PackIdMouseCursors); - ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->X, (float)r->Y); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, atlas->Builder->PackIdMouseCursors); + ImVec2 pos = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][0] + ImVec2((float)r->x, (float)r->y); ImVec2 size = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][1]; *out_size = size; *out_offset = FONT_ATLAS_DEFAULT_TEX_CURSOR_DATA[cursor_type][2]; - out_uv_border[0] = (pos) * TexUvScale; - out_uv_border[1] = (pos + size) * TexUvScale; + out_uv_border[0] = (pos) * atlas->TexUvScale; + out_uv_border[1] = (pos + size) * atlas->TexUvScale; pos.x += FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - out_uv_fill[0] = (pos) * TexUvScale; - out_uv_fill[1] = (pos + size) * TexUvScale; + out_uv_fill[0] = (pos) * atlas->TexUvScale; + out_uv_fill[1] = (pos + size) * atlas->TexUvScale; return true; } -bool ImFontAtlas::Build() +// When atlas->RendererHasTextures = true, this is only called if no font were loaded. +void ImFontAtlasBuildMain(ImFontAtlas* atlas) { - IM_ASSERT(!Locked && "Cannot modify a locked ImFontAtlas between NewFrame() and EndFrame/Render()!"); + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); + if (atlas->TexData && atlas->TexData->Format != atlas->TexDesiredFormat) + ImFontAtlasBuildClear(atlas); - // Default font is none are specified - if (ConfigData.Size == 0) - AddFontDefault(); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); - // Select builder - // - Note that we do not reassign to atlas->FontBuilderIO, since it is likely to point to static data which - // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are - // using a hot-reloading scheme that messes up static data, store your own instance of ImFontBuilderIO somewhere - // and point to it instead of pointing directly to return value of the GetBuilderXXX functions. - const ImFontBuilderIO* builder_io = FontBuilderIO; - if (builder_io == NULL) - { -#ifdef IMGUI_ENABLE_FREETYPE - builder_io = ImGuiFreeType::GetBuilderForFreeType(); -#elif defined(IMGUI_ENABLE_STB_TRUETYPE) - builder_io = ImFontAtlasGetBuilderForStbTruetype(); -#else - IM_ASSERT(0); // Invalid Build function -#endif - } + // Default font is none are specified + if (atlas->Sources.Size == 0) + atlas->AddFontDefault(); - // Build - return builder_io->FontBuilder_Build(this); + // [LEGACY] For backends not supporting RendererHasTextures: preload all glyphs + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + if (atlas->RendererHasTextures == false) // ~ImGuiBackendFlags_RendererHasTextures + ImFontAtlasBuildLegacyPreloadAllGlyphRanges(atlas); + atlas->TexIsBuilt = true; } -void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_brighten_factor) +void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v) { - for (unsigned int i = 0; i < 256; i++) - { - unsigned int value = (unsigned int)(i * in_brighten_factor); - out_table[i] = value > 255 ? 255 : (value & 0xFF); - } + // Automatically disable horizontal oversampling over size 36 + const float raster_size = baked->Size * baked->RasterizerDensity * src->RasterizerDensity; + *out_oversample_h = (src->OversampleH != 0) ? src->OversampleH : (raster_size > 36.0f || src->PixelSnapH) ? 1 : 2; + *out_oversample_v = (src->OversampleV != 0) ? src->OversampleV : 1; } -void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride) +// Setup main font loader for the atlas +// Every font source (ImFontConfig) will use this unless ImFontConfig::FontLoader specify a custom loader. +void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader) { - IM_ASSERT_PARANOID(w <= stride); - unsigned char* data = pixels + x + y * stride; - for (int j = h; j > 0; j--, data += stride - w) - for (int i = w; i > 0; i--, data++) - *data = table[*data]; -} + if (atlas->FontLoader == font_loader) + return; + IM_ASSERT(!atlas->Locked && "Cannot modify a locked ImFontAtlas!"); -#ifdef IMGUI_ENABLE_STB_TRUETYPE -// Temporary data for one source font (multiple source fonts can be merged into one destination ImFont) -// (C++03 doesn't allow instancing ImVector<> with function-local types so we declare the type here.) -struct ImFontBuildSrcData -{ - stbtt_fontinfo FontInfo; - stbtt_pack_range PackRange; // Hold the list of codepoints to pack (essentially points to Codepoints.Data) - stbrp_rect* Rects; // Rectangle to pack. We first fill in their size and the packer will give us their position. - stbtt_packedchar* PackedChars; // Output glyphs - const ImWchar* SrcRanges; // Ranges as requested by user (user is allowed to request too much, e.g. 0x0020..0xFFFF) - int DstIndex; // Index into atlas->Fonts[] and dst_tmp_array[] - int GlyphsHighest; // Highest requested codepoint - int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font) - ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB) - ImVector GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet) -}; + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + atlas->FontLoader->LoaderShutdown(atlas); -// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont) -struct ImFontBuildDstData + atlas->FontLoader = font_loader; + atlas->FontLoaderName = font_loader ? font_loader->Name : "NULL"; + IM_ASSERT(atlas->FontLoaderData == NULL); + + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontInitOutput(atlas, font); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); +} + +// Preload all glyph ranges for legacy backends. +// This may lead to multiple texture creation which might be a little slower than before. +void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas) { - int SrcCount; // Number of source fonts targeting this destination font. - int GlyphsHighest; - int GlyphsCount; - ImBitVector GlyphsSet; // This is used to resolve collision when multiple sources are merged into a same destination font. -}; + atlas->Builder->PreloadedAllGlyphsRanges = true; + for (ImFont* font : atlas->Fonts) + { + ImFontBaked* baked = font->GetFontBaked(font->LegacySize); + if (font->FallbackChar != 0) + baked->FindGlyph(font->FallbackChar); + if (font->EllipsisChar != 0) + baked->FindGlyph(font->EllipsisChar); + for (ImFontConfig* src : font->Sources) + { + const ImWchar* ranges = src->GlyphRanges ? src->GlyphRanges : atlas->GetGlyphRangesDefault(); + for (; ranges[0]; ranges += 2) + for (unsigned int c = ranges[0]; c <= ranges[1] && c <= IM_UNICODE_CODEPOINT_MAX; c++) //-V560 + baked->FindGlyph((ImWchar)c); + } + } +} -static void UnpackBitVectorToFlatIndexList(const ImBitVector* in, ImVector* out) +// FIXME: May make ImFont::Sources a ImSpan<> and move ownership to ImFontAtlas +void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas) { - IM_ASSERT(sizeof(in->Storage.Data[0]) == sizeof(int)); - const ImU32* it_begin = in->Storage.begin(); - const ImU32* it_end = in->Storage.end(); - for (const ImU32* it = it_begin; it < it_end; it++) - if (ImU32 entries_32 = *it) - for (ImU32 bit_n = 0; bit_n < 32; bit_n++) - if (entries_32 & ((ImU32)1 << bit_n)) - out->push_back((int)(((it - it_begin) << 5) + bit_n)); + for (ImFont* font : atlas->Fonts) + font->Sources.resize(0); + for (ImFontConfig& src : atlas->Sources) + src.DstFont->Sources.push_back(&src); } -static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) +// Render a white-colored bitmap encoded in a string +void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char) { - IM_ASSERT(atlas->ConfigData.Size > 0); + ImTextureData* tex = atlas->TexData; + IM_ASSERT(x >= 0 && x + w <= tex->Width); + IM_ASSERT(y >= 0 && y + h <= tex->Height); - ImFontAtlasBuildInit(atlas); + switch (tex->Format) + { + case ImTextureFormat_Alpha8: + { + ImU8* out_p = (ImU8*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? 0xFF : 0x00; + break; + } + case ImTextureFormat_RGBA32: + { + ImU32* out_p = (ImU32*)tex->GetPixelsAt(x, y); + for (int off_y = 0; off_y < h; off_y++, out_p += tex->Width, in_str += w) + for (int off_x = 0; off_x < w; off_x++) + out_p[off_x] = (in_str[off_x] == in_marker_char) ? IM_COL32_WHITE : IM_COL32_BLACK_TRANS; + break; + } + } +} + +static void ImFontAtlasBuildUpdateBasicTexData(ImFontAtlas* atlas) +{ + // Pack and store identifier so we can refresh UV coordinates on texture resize. + // FIXME-NEWATLAS: User/custom rects where user code wants to store UV coordinates will need to do the same thing. + ImFontAtlasBuilder* builder = atlas->Builder; + ImVec2i pack_size = (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) ? ImVec2i(2, 2) : ImVec2i(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); + + ImFontAtlasRect r; + bool add_and_draw = (atlas->GetCustomRect(builder->PackIdMouseCursors, &r) == false); + if (add_and_draw) + { + builder->PackIdMouseCursors = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdMouseCursors != ImFontAtlasRectId_Invalid); - // Clear atlas - atlas->TexID = (ImTextureID)NULL; - atlas->TexWidth = atlas->TexHeight = 0; - atlas->TexUvScale = ImVec2(0.0f, 0.0f); - atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); - atlas->ClearTexData(); - - // Temporary storage for building - ImVector src_tmp_array; - ImVector dst_tmp_array; - src_tmp_array.resize(atlas->ConfigData.Size); - dst_tmp_array.resize(atlas->Fonts.Size); - memset(src_tmp_array.Data, 0, (size_t)src_tmp_array.size_in_bytes()); - memset(dst_tmp_array.Data, 0, (size_t)dst_tmp_array.size_in_bytes()); - - // 1. Initialize font loading structure, check font data validity - for (int src_i = 0; src_i < atlas->ConfigData.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& cfg = atlas->ConfigData[src_i]; - IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); - - // Find index from cfg.DstFont (we allow the user to set cfg.DstFont. Also it makes casual debugging nicer than when storing indices) - src_tmp.DstIndex = -1; - for (int output_i = 0; output_i < atlas->Fonts.Size && src_tmp.DstIndex == -1; output_i++) - if (cfg.DstFont == atlas->Fonts[output_i]) - src_tmp.DstIndex = output_i; - if (src_tmp.DstIndex == -1) + // Draw to texture + if (atlas->Flags & ImFontAtlasFlags_NoMouseCursors) { - IM_ASSERT(src_tmp.DstIndex != -1); // cfg.DstFont not pointing within atlas->Fonts[] array? - return false; + // 2x2 white pixels + ImFontAtlasBuildRenderBitmapFromString(atlas, r.x, r.y, 2, 2, "XX" "XX", 'X'); } - // Initialize helper structure for font loading and verify that the TTF/OTF data is correct - const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)cfg.FontData, cfg.FontNo); - IM_ASSERT(font_offset >= 0 && "FontData is incorrect, or FontNo cannot be found."); - if (!stbtt_InitFont(&src_tmp.FontInfo, (unsigned char*)cfg.FontData, font_offset)) + else { - IM_ASSERT(0 && "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); - return false; + // 2x2 white pixels + mouse cursors + const int x_for_white = r.x; + const int x_for_black = r.x + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_white, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.'); + ImFontAtlasBuildRenderBitmapFromString(atlas, x_for_black, r.y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X'); } + } + + // Refresh UV coordinates + atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); +} + +static void ImFontAtlasBuildUpdateLinesTexData(ImFontAtlas* atlas) +{ + if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) + return; + + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; - // Measure highest codepoints - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault(); - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) + ImFontAtlasRect r; + bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); + } + + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + { + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels + + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) { - // Check for valid range. This may also help detect *some* dangling pointers, because a common - // user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent, - // or to forget to zero-terminate the glyph range array. - IM_ASSERT(src_range[0] <= src_range[1] && "Invalid range: is your glyph range array persistent? it is zero-terminated?"); - src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]); + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; + + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; } - dst_tmp.SrcCount++; - dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest); + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } +} + +//----------------------------------------------------------------------------------------------------------------------------- + +// Was tempted to lazily init FontSrc but wouldn't save much + makes it more complicated to detect invalid data at AddFont() +bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font) +{ + bool ret = true; + for (ImFontConfig* src : font->Sources) + if (!ImFontAtlasFontSourceInit(atlas, src)) + ret = false; + IM_ASSERT(ret); // Unclear how to react to this meaningfully. Assume that result will be same as initial AddFont() call. + return ret; +} - // 2. For every requested codepoint, check for their presence in the font data, and handle redundancy or overlaps between source fonts to avoid unused glyphs. - int total_glyphs_count = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) +// Keep source/input FontData +void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font) +{ + font->ClearOutputData(); + for (ImFontConfig* src : font->Sources) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; - src_tmp.GlyphsSet.Create(src_tmp.GlyphsHighest + 1); - if (dst_tmp.GlyphsSet.Storage.empty()) - dst_tmp.GlyphsSet.Create(dst_tmp.GlyphsHighest + 1); + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader && loader->FontSrcDestroy != NULL) + loader->FontSrcDestroy(atlas, src); + } +} - for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) - for (unsigned int codepoint = src_range[0]; codepoint <= src_range[1]; codepoint++) +//----------------------------------------------------------------------------------------------------------------------------- + +bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src) +{ + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcInit != NULL && !loader->FontSrcInit(atlas, src)) + return false; + return true; +} + +void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + if (src->MergeMode == false) + { + font->ClearOutputData(); + //font->FontSize = src->SizePixels; + font->OwnerAtlas = atlas; + IM_ASSERT(font->Sources[0] == src); + } + atlas->TexIsBuilt = false; // For legacy backends + ImFontAtlasBuildSetupFontSpecialGlyphs(atlas, font, src); +} + +void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + // IF YOU GET A CRASH IN THE IM_FREE() CALL HERE AND USED AddFontFromMemoryTTF(): + // - DUE TO LEGACY REASON AddFontFromMemoryTTF() TRANSFERS MEMORY OWNERSHIP BY DEFAULT. + // - IT WILL THEREFORE CRASH WHEN PASSED DATA WHICH MAY NOT BE FREEED BY IMGUI. + // - USE `ImFontConfig font_cfg; font_cfg.FontDataOwnedByAtlas = false; io.Fonts->AddFontFromMemoryTTF(....., &cfg);` to disable passing ownership/ + // WE WILL ADDRESS THIS IN A FUTURE REWORK OF THE API. + if (src->FontDataOwnedByAtlas) + IM_FREE(src->FontData); + src->FontData = NULL; + if (src->GlyphExcludeRanges) + IM_FREE((void*)src->GlyphExcludeRanges); + src->GlyphExcludeRanges = NULL; +} + +// Create a compact, baked "..." if it doesn't exist, by using the ".". +// This may seem overly complicated right now but the point is to exercise and improve a technique which should be increasingly used. +// FIXME-NEWATLAS: This borrows too much from FontLoader's FontLoadGlyph() handlers and suggest that we should add further helpers. +static ImFontGlyph* ImFontAtlasBuildSetupFontBakedEllipsis(ImFontAtlas* atlas, ImFontBaked* baked) +{ + ImFont* font = baked->OwnerFont; + IM_ASSERT(font->EllipsisChar != 0); + + const ImFontGlyph* dot_glyph = baked->FindGlyphNoFallback((ImWchar)'.'); + if (dot_glyph == NULL) + dot_glyph = baked->FindGlyphNoFallback((ImWchar)0xFF0E); + if (dot_glyph == NULL) + return NULL; + ImFontAtlasRectId dot_r_id = dot_glyph->PackId; // Deep copy to avoid invalidation of glyphs and rect pointers + ImTextureRect* dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + const int dot_spacing = 1; + const float dot_step = (dot_glyph->X1 - dot_glyph->X0) + dot_spacing; + + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, (dot_r->w * 3 + dot_spacing * 2), dot_r->h); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + ImFontGlyph glyph_in = {}; + ImFontGlyph* glyph = &glyph_in; + glyph->Codepoint = font->EllipsisChar; + glyph->AdvanceX = ImMax(dot_glyph->AdvanceX, dot_glyph->X0 + dot_step * 3.0f - dot_spacing); // FIXME: Slightly odd for normally mono-space fonts but since this is used for trailing contents. + glyph->X0 = dot_glyph->X0; + glyph->Y0 = dot_glyph->Y0; + glyph->X1 = dot_glyph->X0 + dot_step * 3 - dot_spacing; + glyph->Y1 = dot_glyph->Y1; + glyph->Visible = true; + glyph->PackId = pack_id; + glyph = ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, glyph); + dot_glyph = NULL; // Invalidated + + // Copy to texture, post-process and queue update for backend + // FIXME-NEWATLAS-V2: Dot glyph is already post-processed as this point, so this would damage it. + dot_r = ImFontAtlasPackGetRect(atlas, dot_r_id); + ImTextureData* tex = atlas->TexData; + for (int n = 0; n < 3; n++) + ImFontAtlasTextureBlockCopy(tex, dot_r->x, dot_r->y, tex, r->x + (dot_r->w + dot_spacing) * n, r->y, dot_r->w, dot_r->h); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); + + return glyph; +} + +// Load fallback in order to obtain its index +// (this is called from in hot-path so we avoid extraneous parameters to minimize impact on code size) +static void ImFontAtlasBuildSetupFontBakedFallback(ImFontBaked* baked) +{ + IM_ASSERT(baked->FallbackGlyphIndex == -1); + IM_ASSERT(baked->FallbackAdvanceX == 0.0f); + ImFont* font = baked->OwnerFont; + ImFontGlyph* fallback_glyph = NULL; + if (font->FallbackChar != 0) + fallback_glyph = baked->FindGlyphNoFallback(font->FallbackChar); + if (fallback_glyph == NULL) + { + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + ImFontGlyph glyph; + glyph.Codepoint = 0; + glyph.AdvanceX = space_glyph ? space_glyph->AdvanceX : IM_ROUND(baked->Size * 0.40f); + fallback_glyph = ImFontAtlasBakedAddFontGlyph(font->OwnerAtlas, baked, NULL, &glyph); + } + baked->FallbackGlyphIndex = baked->Glyphs.index_from_ptr(fallback_glyph); // Storing index avoid need to update pointer on growth and simplify inner loop code + baked->FallbackAdvanceX = fallback_glyph->AdvanceX; +} + +static void ImFontAtlasBuildSetupFontBakedBlanks(ImFontAtlas* atlas, ImFontBaked* baked) +{ + // Mark space as always hidden (not strictly correct/necessary. but some e.g. icons fonts don't have a space. it tends to look neater in previews) + ImFontGlyph* space_glyph = baked->FindGlyphNoFallback((ImWchar)' '); + if (space_glyph != NULL) + space_glyph->Visible = false; + + // Setup Tab character. + // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) + if (baked->FindGlyphNoFallback('\t') == NULL && space_glyph != NULL) + { + ImFontGlyph tab_glyph; + tab_glyph.Codepoint = '\t'; + tab_glyph.AdvanceX = space_glyph->AdvanceX * IM_TABSIZE; + ImFontAtlasBakedAddFontGlyph(atlas, baked, NULL, &tab_glyph); + } +} + +// Load/identify special glyphs +// (note that this is called again for fonts with MergeMode) +void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src) +{ + IM_UNUSED(atlas); + IM_ASSERT(font->Sources.contains(src)); + + // Find Fallback character. Actual glyph loaded in GetFontBaked(). + const ImWchar fallback_chars[] = { font->FallbackChar, (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; + if (font->FallbackChar == 0) + for (ImWchar candidate_char : fallback_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) { - if (dst_tmp.GlyphsSet.TestBit(codepoint)) // Don't overwrite existing glyphs. We could make this an option for MergeMode (e.g. MergeOverwrite==true) - continue; - if (!stbtt_FindGlyphIndex(&src_tmp.FontInfo, codepoint)) // It is actually in the font? - continue; + font->FallbackChar = (ImWchar)candidate_char; + break; + } - // Add to avail set/counters - src_tmp.GlyphsCount++; - dst_tmp.GlyphsCount++; - src_tmp.GlyphsSet.SetBit(codepoint); - dst_tmp.GlyphsSet.SetBit(codepoint); - total_glyphs_count++; + // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). + // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. + // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. + const ImWchar ellipsis_chars[] = { src->EllipsisChar, (ImWchar)0x2026, (ImWchar)0x0085 }; + if (font->EllipsisChar == 0) + for (ImWchar candidate_char : ellipsis_chars) + if (candidate_char != 0 && font->IsGlyphInFont(candidate_char)) + { + font->EllipsisChar = candidate_char; + break; } + if (font->EllipsisChar == 0) + { + font->EllipsisChar = 0x0085; + font->EllipsisAutoBake = true; } +} - // 3. Unpack our bit map into a flat list (we now have all the Unicode points that we know are requested _and_ available _and_ not overlapping another) - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - src_tmp.GlyphsList.reserve(src_tmp.GlyphsCount); - UnpackBitVectorToFlatIndexList(&src_tmp.GlyphsSet, &src_tmp.GlyphsList); - src_tmp.GlyphsSet.Clear(); - IM_ASSERT(src_tmp.GlyphsList.Size == src_tmp.GlyphsCount); - } - for (int dst_i = 0; dst_i < dst_tmp_array.Size; dst_i++) - dst_tmp_array[dst_i].GlyphsSet.Clear(); - dst_tmp_array.clear(); - - // Allocate packing character data and flag packed characters buffer as non-packed (x0=y0=x1=y1=0) - // (We technically don't need to zero-clear buf_rects, but let's do it for the sake of sanity) - ImVector buf_rects; - ImVector buf_packedchars; - buf_rects.resize(total_glyphs_count); - buf_packedchars.resize(total_glyphs_count); - memset(buf_rects.Data, 0, (size_t)buf_rects.size_in_bytes()); - memset(buf_packedchars.Data, 0, (size_t)buf_packedchars.size_in_bytes()); - - // 4. Gather glyphs sizes so we can pack them in our virtual canvas. - int total_surface = 0; - int buf_rects_out_n = 0; - int buf_packedchars_out_n = 0; - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) - { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; +void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph) +{ + if (glyph->PackId != ImFontAtlasRectId_Invalid) + { + ImFontAtlasPackDiscardRect(atlas, glyph->PackId); + glyph->PackId = ImFontAtlasRectId_Invalid; + } + ImWchar c = (ImWchar)glyph->Codepoint; + IM_ASSERT(font->FallbackChar != c && font->EllipsisChar != c); // Unsupported for simplicity + IM_ASSERT(glyph >= baked->Glyphs.Data && glyph < baked->Glyphs.Data + baked->Glyphs.Size); + IM_UNUSED(font); + baked->IndexLookup[c] = IM_FONTGLYPH_INDEX_UNUSED; + baked->IndexAdvanceX[c] = baked->FallbackAdvanceX; +} + +ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id) +{ + IMGUI_DEBUG_LOG_FONT("[font] Created baked %.2fpx\n", font_size); + ImFontBaked* baked = atlas->Builder->BakedPool.push_back(ImFontBaked()); + baked->Size = font_size; + baked->RasterizerDensity = font_rasterizer_density; + baked->BakedId = baked_id; + baked->OwnerFont = font; + baked->LastUsedFrame = atlas->Builder->FrameCount; + + // Initialize backend data + size_t loader_data_size = 0; + for (ImFontConfig* src : font->Sources) // Cannot easily be cached as we allow changing backend + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + loader_data_size += loader->FontBakedSrcLoaderDataSize; + } + baked->FontLoaderDatas = (loader_data_size > 0) ? IM_ALLOC(loader_data_size) : NULL; + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedInit) + loader->FontBakedInit(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + + ImFontAtlasBuildSetupFontBakedBlanks(atlas, baked); + return baked; +} - src_tmp.Rects = &buf_rects[buf_rects_out_n]; - src_tmp.PackedChars = &buf_packedchars[buf_packedchars_out_n]; - buf_rects_out_n += src_tmp.GlyphsCount; - buf_packedchars_out_n += src_tmp.GlyphsCount; - - // Convert our ranges in the format stb_truetype wants - ImFontConfig& cfg = atlas->ConfigData[src_i]; - src_tmp.PackRange.font_size = cfg.SizePixels * cfg.RasterizerDensity; - src_tmp.PackRange.first_unicode_codepoint_in_range = 0; - src_tmp.PackRange.array_of_unicode_codepoints = src_tmp.GlyphsList.Data; - src_tmp.PackRange.num_chars = src_tmp.GlyphsList.Size; - src_tmp.PackRange.chardata_for_range = src_tmp.PackedChars; - src_tmp.PackRange.h_oversample = (unsigned char)cfg.OversampleH; - src_tmp.PackRange.v_oversample = (unsigned char)cfg.OversampleV; - - // Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects) - const float scale = (cfg.SizePixels > 0.0f) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels * cfg.RasterizerDensity) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels * cfg.RasterizerDensity); - const int padding = atlas->TexGlyphPadding; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++) +// FIXME-OPT: This is not a fast query. Adding a BakedCount field in Font might allow to take a shortcut for the most common case. +ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int step_n = 0; step_n < 2; step_n++) + { + ImFontBaked* closest_larger_match = NULL; + ImFontBaked* closest_smaller_match = NULL; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) { - int x0, y0, x1, y1; - const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]); - IM_ASSERT(glyph_index_in_font != 0); - stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1); - src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1); - src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1); - total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h; + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->OwnerFont != font || baked->WantDestroy) + continue; + if (step_n == 0 && baked->RasterizerDensity != font_rasterizer_density) // First try with same density + continue; + if (baked->Size > font_size && (closest_larger_match == NULL || baked->Size < closest_larger_match->Size)) + closest_larger_match = baked; + if (baked->Size < font_size && (closest_smaller_match == NULL || baked->Size > closest_smaller_match->Size)) + closest_smaller_match = baked; } + if (closest_larger_match) + if (closest_smaller_match == NULL || (closest_larger_match->Size >= font_size * 2.0f && closest_smaller_match->Size > font_size * 0.5f)) + return closest_larger_match; + if (closest_smaller_match) + return closest_smaller_match; } + return NULL; +} - // We need a width for the skyline algorithm, any width! - // The exact width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. - // User can override TexDesiredWidth and TexGlyphPadding if they wish, otherwise we use a simple heuristic to select the width based on expected surface. - const int surface_sqrt = (int)ImSqrt((float)total_surface) + 1; - atlas->TexHeight = 0; - if (atlas->TexDesiredWidth > 0) - atlas->TexWidth = atlas->TexDesiredWidth; - else - atlas->TexWidth = (surface_sqrt >= 4096 * 0.7f) ? 4096 : (surface_sqrt >= 2048 * 0.7f) ? 2048 : (surface_sqrt >= 1024 * 0.7f) ? 1024 : 512; +void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + IMGUI_DEBUG_LOG_FONT("[font] Discard baked %.2f for \"%s\"\n", baked->Size, font->GetDebugName()); - // 5. Start packing - // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). - const int TEX_HEIGHT_MAX = 1024 * 32; - stbtt_pack_context spc = {}; - stbtt_PackBegin(&spc, NULL, atlas->TexWidth, TEX_HEIGHT_MAX, 0, atlas->TexGlyphPadding, NULL); - ImFontAtlasBuildPackCustomRects(atlas, spc.pack_info); + for (ImFontGlyph& glyph : baked->Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + ImFontAtlasPackDiscardRect(atlas, glyph.PackId); - // 6. Pack each source font. No rendering yet, we are working with rectangles in an infinitely tall texture at this point. - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + char* loader_data_p = (char*)baked->FontLoaderDatas; + for (ImFontConfig* src : font->Sources) { - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) - continue; + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontBakedDestroy) + loader->FontBakedDestroy(atlas, src, baked, loader_data_p); + loader_data_p += loader->FontBakedSrcLoaderDataSize; + } + if (baked->FontLoaderDatas) + { + IM_FREE(baked->FontLoaderDatas); + baked->FontLoaderDatas = NULL; + } + builder->BakedMap.SetVoidPtr(baked->BakedId, NULL); + builder->BakedDiscardedCount++; + baked->ClearOutputData(); + baked->WantDestroy = true; + font->LastBaked = NULL; +} - stbrp_pack_rects((stbrp_context*)spc.pack_info, src_tmp.Rects, src_tmp.GlyphsCount); +// use unused_frames==0 to discard everything. +void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames) +{ + if (ImFontAtlasBuilder* builder = atlas->Builder) // This can be called from font destructor + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->OwnerFont != font || baked->WantDestroy) + continue; + ImFontAtlasBakedDiscard(atlas, font, baked); + } +} - // Extend texture height and mark missing glyphs as non-packed so we won't render them. - // FIXME: We are not handling packing failure here (would happen if we got off TEX_HEIGHT_MAX or if a single if larger than TexWidth?) - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) - if (src_tmp.Rects[glyph_i].was_packed) - atlas->TexHeight = ImMax(atlas->TexHeight, src_tmp.Rects[glyph_i].y + src_tmp.Rects[glyph_i].h); +// use unused_frames==0 to discard everything. +void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + { + ImFontBaked* baked = &builder->BakedPool[baked_n]; + if (baked->LastUsedFrame + unused_frames > atlas->Builder->FrameCount) + continue; + if (baked->WantDestroy || (baked->OwnerFont->Flags & ImFontFlags_LockBakedSizes)) + continue; + ImFontAtlasBakedDiscard(atlas, baked->OwnerFont, baked); } +} + +// Those functions are designed to facilitate changing the underlying structures for ImFontAtlas to store an array of ImDrawListSharedData* +void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(!atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.push_back(data); +} - // 7. Allocate texture - atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); - atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); - atlas->TexPixelsAlpha8 = (unsigned char*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight); - memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); - spc.pixels = atlas->TexPixelsAlpha8; - spc.height = atlas->TexHeight; +void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data) +{ + IM_ASSERT(atlas->DrawListSharedDatas.contains(data)); + atlas->DrawListSharedDatas.find_erase(data); +} - // 8. Render/rasterize font characters into the texture - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) +// Update texture identifier in all active draw lists +void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) { - ImFontConfig& cfg = atlas->ConfigData[src_i]; - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - if (src_tmp.GlyphsCount == 0) + // If Context 2 uses font owned by Context 1 which already called EndFrame()/Render(), we don't want to mess with draw commands for Context 1 + if (shared_data->Context && !shared_data->Context->WithinFrameScope) continue; - stbtt_PackFontRangesRenderIntoRects(&spc, &src_tmp.FontInfo, &src_tmp.PackRange, 1, src_tmp.Rects); + for (ImDrawList* draw_list : shared_data->DrawLists) + { + // Replace in command-buffer + // (there is not need to replace in ImDrawListSplitter: current channel is in ImDrawList's CmdBuffer[], + // other channels will be on SetCurrentChannel() which already needs to compare CmdHeader anyhow) + if (draw_list->CmdBuffer.Size > 0 && draw_list->_CmdHeader.TexRef == old_tex) + draw_list->_SetTexture(new_tex); + + // Replace in stack + for (ImTextureRef& stacked_tex : draw_list->_TextureStack) + if (stacked_tex == old_tex) + stacked_tex = new_tex; + } + } +} + +// Update texture coordinates in all draw list shared context +// FIXME-NEWATLAS FIXME-OPT: Doesn't seem necessary to update for all, only one bound to current context? +void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) +{ + for (ImDrawListSharedData* shared_data : atlas->DrawListSharedDatas) + if (shared_data->FontAtlas == atlas) + { + shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; + shared_data->TexUvLines = atlas->TexUvLines; + } +} + +// Set current texture. This is mostly called from AddTexture() + to handle a failed resize. +static void ImFontAtlasBuildSetTexture(ImFontAtlas* atlas, ImTextureData* tex) +{ + ImTextureRef old_tex_ref = atlas->TexRef; + atlas->TexData = tex; + atlas->TexUvScale = ImVec2(1.0f / tex->Width, 1.0f / tex->Height); + atlas->TexRef._TexData = tex; + //atlas->TexRef._TexID = tex->TexID; // <-- We intentionally don't do that. It would be misleading and betray promise that both fields aren't set. + ImFontAtlasUpdateDrawListsTextures(atlas, old_tex_ref, atlas->TexRef); +} + +// Create a new texture, discard previous one +ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h) +{ + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex; + + // FIXME: Cannot reuse texture because old UV may have been used already (unless we remap UV). + /*if (old_tex != NULL && old_tex->Status == ImTextureStatus_WantCreate) + { + // Reuse texture not yet used by backend. + IM_ASSERT(old_tex->TexID == ImTextureID_Invalid && old_tex->BackendUserData == NULL); + old_tex->DestroyPixels(); + old_tex->Updates.clear(); + new_tex = old_tex; + old_tex = NULL; + } + else*/ + { + // Add new + new_tex = IM_NEW(ImTextureData)(); + new_tex->UniqueID = atlas->TexNextUniqueID++; + atlas->TexList.push_back(new_tex); + } + if (old_tex != NULL) + { + // Queue old as to destroy next frame + old_tex->WantDestroyNextFrame = true; + IM_ASSERT(old_tex->Status == ImTextureStatus_OK || old_tex->Status == ImTextureStatus_WantCreate || old_tex->Status == ImTextureStatus_WantUpdates); + } + + new_tex->Create(atlas->TexDesiredFormat, w, h); + atlas->TexIsBuilt = false; + + ImFontAtlasBuildSetTexture(atlas, new_tex); + + return new_tex; +} + +#if 0 +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../stb/stb_image_write.h" +static void ImFontAtlasDebugWriteTexToDisk(ImTextureData* tex, const char* description) +{ + ImGuiContext& g = *GImGui; + char buf[128]; + ImFormatString(buf, IM_ARRAYSIZE(buf), "[%05d] Texture #%03d - %s.png", g.FrameCount, tex->UniqueID, description); + stbi_write_png(buf, tex->Width, tex->Height, tex->BytesPerPixel, tex->Pixels, tex->GetPitch()); // tex->BytesPerPixel is technically not component, but ok for the formats we support. +} +#endif - // Apply multiply operator - if (cfg.RasterizerMultiply != 1.0f) +void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + builder->LockDisableResize = true; + + ImTextureData* old_tex = atlas->TexData; + ImTextureData* new_tex = ImFontAtlasTextureAdd(atlas, w, h); + new_tex->UseColors = old_tex->UseColors; + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize+repack %dx%d => Texture #%03d: %dx%d\n", old_tex->UniqueID, old_tex->Width, old_tex->Height, new_tex->UniqueID, new_tex->Width, new_tex->Height); + //for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + // IMGUI_DEBUG_LOG_FONT("[font] - Baked %.2fpx, %d glyphs, want_destroy=%d\n", builder->BakedPool[baked_n].FontSize, builder->BakedPool[baked_n].Glyphs.Size, builder->BakedPool[baked_n].WantDestroy); + //IMGUI_DEBUG_LOG_FONT("[font] - Old packed rects: %d, area %d px\n", builder->RectsPackedCount, builder->RectsPackedSurface); + //ImFontAtlasDebugWriteTexToDisk(old_tex, "Before Pack"); + + // Repack, lose discarded rectangle, copy pixels + // FIXME-NEWATLAS: This is unstable because packing order is based on RectsIndex + // FIXME-NEWATLAS-V2: Repacking in batch would be beneficial to packing heuristic, and fix stability. + // FIXME-NEWATLAS-TESTS: Test calling RepackTexture with size too small to fits existing rects. + ImFontAtlasPackInit(atlas); + ImVector old_rects; + ImVector old_index = builder->RectsIndex; + old_rects.swap(builder->Rects); + + for (ImFontAtlasRectEntry& index_entry : builder->RectsIndex) + { + if (index_entry.IsUsed == false) + continue; + ImTextureRect& old_r = old_rects[index_entry.TargetIndex]; + if (old_r.w == 0 && old_r.h == 0) + continue; + ImFontAtlasRectId new_r_id = ImFontAtlasPackAddRect(atlas, old_r.w, old_r.h, &index_entry); + if (new_r_id == ImFontAtlasRectId_Invalid) { - unsigned char multiply_table[256]; - ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); - stbrp_rect* r = &src_tmp.Rects[0]; - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++, r++) - if (r->was_packed) - ImFontAtlasBuildMultiplyRectAlpha8(multiply_table, atlas->TexPixelsAlpha8, r->x, r->y, r->w, r->h, atlas->TexWidth * 1); + // Undo, grow texture and try repacking again. + // FIXME-NEWATLAS-TESTS: This is a very rarely exercised path! It needs to be automatically tested properly. + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: resize failed. Will grow.\n", new_tex->UniqueID); + new_tex->WantDestroyNextFrame = true; + builder->Rects.swap(old_rects); + builder->RectsIndex = old_index; + ImFontAtlasBuildSetTexture(atlas, old_tex); + ImFontAtlasTextureGrow(atlas, w, h); // Recurse + return; } - src_tmp.Rects = NULL; + IM_ASSERT(ImFontAtlasRectId_GetIndex(new_r_id) == builder->RectsIndex.index_from_ptr(&index_entry)); + ImTextureRect* new_r = ImFontAtlasPackGetRect(atlas, new_r_id); + ImFontAtlasTextureBlockCopy(old_tex, old_r.x, old_r.y, new_tex, new_r->x, new_r->y, new_r->w, new_r->h); } + IM_ASSERT(old_rects.Size == builder->Rects.Size + builder->RectsDiscardedCount); + builder->RectsDiscardedCount = 0; + builder->RectsDiscardedSurface = 0; + + // Patch glyphs UV + for (int baked_n = 0; baked_n < builder->BakedPool.Size; baked_n++) + for (ImFontGlyph& glyph : builder->BakedPool[baked_n].Glyphs) + if (glyph.PackId != ImFontAtlasRectId_Invalid) + { + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph.PackId); + glyph.U0 = (r->x) * atlas->TexUvScale.x; + glyph.V0 = (r->y) * atlas->TexUvScale.y; + glyph.U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph.V1 = (r->y + r->h) * atlas->TexUvScale.y; + } - // End packing - stbtt_PackEnd(&spc); - buf_rects.clear(); + // Update other cached UV + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + builder->LockDisableResize = false; + ImFontAtlasUpdateDrawListsSharedData(atlas); + //ImFontAtlasDebugWriteTexToDisk(new_tex, "After Pack"); +} + +void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_tex_w, int old_tex_h) +{ + //ImFontAtlasDebugWriteTexToDisk(atlas->TexData, "Before Grow"); + ImFontAtlasBuilder* builder = atlas->Builder; + if (old_tex_w == -1) + old_tex_w = atlas->TexData->Width; + if (old_tex_h == -1) + old_tex_h = atlas->TexData->Height; + + // FIXME-NEWATLAS-V2: What to do when reaching limits exposed by backend? + // FIXME-NEWATLAS-V2: Does ImFontAtlasFlags_NoPowerOfTwoHeight makes sense now? Allow 'lock' and 'compact' operations? + IM_ASSERT(ImIsPowerOfTwo(old_tex_w) && ImIsPowerOfTwo(old_tex_h)); + IM_ASSERT(ImIsPowerOfTwo(atlas->TexMinWidth) && ImIsPowerOfTwo(atlas->TexMaxWidth) && ImIsPowerOfTwo(atlas->TexMinHeight) && ImIsPowerOfTwo(atlas->TexMaxHeight)); + + // Grow texture so it follows roughly a square. + // - Grow height before width, as width imply more packing nodes. + // - Caller should be taking account of RectsDiscardedSurface and may not need to grow. + int new_tex_w = (old_tex_h <= old_tex_w) ? old_tex_w : old_tex_w * 2; + int new_tex_h = (old_tex_h <= old_tex_w) ? old_tex_h * 2 : old_tex_h; + + // Handle minimum size first (for pathologically large packed rects) + const int pack_padding = atlas->TexGlyphPadding; + new_tex_w = ImMax(new_tex_w, ImUpperPowerOfTwo(builder->MaxRectSize.x + pack_padding)); + new_tex_h = ImMax(new_tex_h, ImUpperPowerOfTwo(builder->MaxRectSize.y + pack_padding)); + new_tex_w = ImClamp(new_tex_w, atlas->TexMinWidth, atlas->TexMaxWidth); + new_tex_h = ImClamp(new_tex_h, atlas->TexMinHeight, atlas->TexMaxHeight); + if (new_tex_w == old_tex_w && new_tex_h == old_tex_h) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_w, new_tex_h); +} + +void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas) +{ + // Can some baked contents be ditched? + //IMGUI_DEBUG_LOG_FONT("[font] ImFontAtlasBuildMakeSpace()\n"); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 2); - // 9. Setup ImFont and glyphs for runtime - for (int src_i = 0; src_i < src_tmp_array.Size; src_i++) + // Currently using a heuristic for repack without growing. + if (builder->RectsDiscardedSurface < builder->RectsPackedSurface * 0.20f) + ImFontAtlasTextureGrow(atlas); + else + ImFontAtlasTextureRepack(atlas, atlas->TexData->Width, atlas->TexData->Height); +} + +ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas) +{ + int min_w = ImUpperPowerOfTwo(atlas->TexMinWidth); + int min_h = ImUpperPowerOfTwo(atlas->TexMinHeight); + if (atlas->Builder == NULL || atlas->TexData == NULL || atlas->TexData->Status == ImTextureStatus_WantDestroy) + return ImVec2i(min_w, min_h); + + ImFontAtlasBuilder* builder = atlas->Builder; + min_w = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.x), min_w); + min_h = ImMax(ImUpperPowerOfTwo(builder->MaxRectSize.y), min_h); + const int surface_approx = builder->RectsPackedSurface - builder->RectsDiscardedSurface; // Expected surface after repack + const int surface_sqrt = (int)sqrtf((float)surface_approx); + + int new_tex_w; + int new_tex_h; + if (min_w >= min_h) + { + new_tex_w = ImMax(min_w, ImUpperPowerOfTwo(surface_sqrt)); + new_tex_h = ImMax(min_h, (int)((surface_approx + new_tex_w - 1) / new_tex_w)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + } + else { - // When merging fonts with MergeMode=true: - // - We can have multiple input fonts writing into a same destination font. - // - dst_font->ConfigData is != from cfg which is our source configuration. - ImFontBuildSrcData& src_tmp = src_tmp_array[src_i]; - ImFontConfig& cfg = atlas->ConfigData[src_i]; - ImFont* dst_font = cfg.DstFont; + new_tex_h = ImMax(min_h, ImUpperPowerOfTwo(surface_sqrt)); + if ((atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) == 0) + new_tex_h = ImUpperPowerOfTwo(new_tex_h); + new_tex_w = ImMax(min_w, (int)((surface_approx + new_tex_h - 1) / new_tex_h)); + } - const float font_scale = stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels); - int unscaled_ascent, unscaled_descent, unscaled_line_gap; - stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + IM_ASSERT(ImIsPowerOfTwo(new_tex_w) && ImIsPowerOfTwo(new_tex_h)); + return ImVec2i(new_tex_w, new_tex_h); +} - const float ascent = ImCeil(unscaled_ascent * font_scale); - const float descent = ImFloor(unscaled_descent * font_scale); - ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); - const float font_off_x = cfg.GlyphOffset.x; - const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent); +// Clear all output. Invalidates all AddCustomRect() return values! +void ImFontAtlasBuildClear(ImFontAtlas* atlas) +{ + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + ImFontAtlasBuildDestroy(atlas); + ImFontAtlasTextureAdd(atlas, new_tex_size.x, new_tex_size.y); + ImFontAtlasBuildInit(atlas); + for (ImFontConfig& src : atlas->Sources) + ImFontAtlasFontSourceInit(atlas, &src); + for (ImFont* font : atlas->Fonts) + for (ImFontConfig* src : font->Sources) + ImFontAtlasFontSourceAddToFont(atlas, font, src); +} - const float inv_rasterization_scale = 1.0f / cfg.RasterizerDensity; +// You should not need to call this manually! +// If you think you do, let us know and we can advise about policies auto-compact. +void ImFontAtlasTextureCompact(ImFontAtlas* atlas) +{ + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontAtlasBuildDiscardBakes(atlas, 1); + + ImTextureData* old_tex = atlas->TexData; + ImVec2i old_tex_size = ImVec2i(old_tex->Width, old_tex->Height); + ImVec2i new_tex_size = ImFontAtlasTextureGetSizeEstimate(atlas); + if (builder->RectsDiscardedCount == 0 && new_tex_size.x == old_tex_size.x && new_tex_size.y == old_tex_size.y) + return; + + ImFontAtlasTextureRepack(atlas, new_tex_size.x, new_tex_size.y); +} + +// Start packing over current empty texture +void ImFontAtlasBuildInit(ImFontAtlas* atlas) +{ + // Select Backend + // - Note that we do not reassign to atlas->FontLoader, since it is likely to point to static data which + // may mess with some hot-reloading schemes. If you need to assign to this (for dynamic selection) AND are + // using a hot-reloading scheme that messes up static data, store your own instance of FontLoader somewhere + // and point to it instead of pointing directly to return value of the GetFontLoaderXXX functions. + if (atlas->FontLoader == NULL) + { +#ifdef IMGUI_ENABLE_FREETYPE + atlas->SetFontLoader(ImGuiFreeType::GetFontLoader()); +#elif defined(IMGUI_ENABLE_STB_TRUETYPE) + atlas->SetFontLoader(ImFontAtlasGetFontLoaderForStbTruetype()); +#else + IM_ASSERT(0); // Invalid Build function +#endif + } + + // Create initial texture size + if (atlas->TexData == NULL || atlas->TexData->Pixels == NULL) + ImFontAtlasTextureAdd(atlas, ImUpperPowerOfTwo(atlas->TexMinWidth), ImUpperPowerOfTwo(atlas->TexMinHeight)); + + atlas->Builder = IM_NEW(ImFontAtlasBuilder)(); + if (atlas->FontLoader->LoaderInit) + atlas->FontLoader->LoaderInit(atlas); + + ImFontAtlasBuildUpdateRendererHasTexturesFromContext(atlas); + + ImFontAtlasPackInit(atlas); + + // Add required texture data + ImFontAtlasBuildUpdateLinesTexData(atlas); + ImFontAtlasBuildUpdateBasicTexData(atlas); + + // Register fonts + ImFontAtlasBuildUpdatePointers(atlas); + + // Update UV coordinates etc. stored in bound ImDrawListSharedData instance + ImFontAtlasUpdateDrawListsSharedData(atlas); + + //atlas->TexIsBuilt = true; +} - for (int glyph_i = 0; glyph_i < src_tmp.GlyphsCount; glyph_i++) +// Destroy builder and all cached glyphs. Do not destroy actual fonts. +void ImFontAtlasBuildDestroy(ImFontAtlas* atlas) +{ + for (ImFont* font : atlas->Fonts) + ImFontAtlasFontDestroyOutput(atlas, font); + if (atlas->Builder && atlas->FontLoader && atlas->FontLoader->LoaderShutdown) + { + atlas->FontLoader->LoaderShutdown(atlas); + IM_ASSERT(atlas->FontLoaderData == NULL); + } + IM_DELETE(atlas->Builder); + atlas->Builder = NULL; +} + +void ImFontAtlasPackInit(ImFontAtlas * atlas) +{ + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + // In theory we could decide to reduce the number of nodes, e.g. halve them, and waste a little texture space, but it doesn't seem worth it. + const int pack_node_count = tex->Width / 2; + builder->PackNodes.resize(pack_node_count); + IM_STATIC_ASSERT(sizeof(stbrp_context) <= sizeof(stbrp_context_opaque)); + stbrp_init_target((stbrp_context*)(void*)&builder->PackContext, tex->Width, tex->Height, builder->PackNodes.Data, builder->PackNodes.Size); + builder->RectsPackedSurface = builder->RectsPackedCount = 0; + builder->MaxRectSize = ImVec2i(0, 0); + builder->MaxRectBounds = ImVec2i(0, 0); +} + +// This is essentially a free-list pattern, it may be nice to wrap it into a dedicated type. +static ImFontAtlasRectId ImFontAtlasPackAllocRectEntry(ImFontAtlas* atlas, int rect_idx) +{ + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + int index_idx; + ImFontAtlasRectEntry* index_entry; + if (builder->RectsIndexFreeListStart < 0) + { + builder->RectsIndex.resize(builder->RectsIndex.Size + 1); + index_idx = builder->RectsIndex.Size - 1; + index_entry = &builder->RectsIndex[index_idx]; + memset(index_entry, 0, sizeof(*index_entry)); + } + else + { + index_idx = builder->RectsIndexFreeListStart; + index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed == false && index_entry->Generation > 0); // Generation is incremented during DiscardRect + builder->RectsIndexFreeListStart = index_entry->TargetIndex; + } + index_entry->TargetIndex = rect_idx; + index_entry->IsUsed = 1; + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// Overwrite existing entry +static ImFontAtlasRectId ImFontAtlasPackReuseRectEntry(ImFontAtlas* atlas, ImFontAtlasRectEntry* index_entry) +{ + IM_ASSERT(index_entry->IsUsed); + index_entry->TargetIndex = atlas->Builder->Rects.Size - 1; + int index_idx = atlas->Builder->RectsIndex.index_from_ptr(index_entry); + return ImFontAtlasRectId_Make(index_idx, index_entry->Generation); +} + +// This is expected to be called in batches and followed by a repack +void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + + ImTextureRect* rect = ImFontAtlasPackGetRect(atlas, id); + if (rect == NULL) + return; + + ImFontAtlasBuilder* builder = atlas->Builder; + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->IsUsed && index_entry->TargetIndex >= 0); + index_entry->IsUsed = false; + index_entry->TargetIndex = builder->RectsIndexFreeListStart; + index_entry->Generation++; + if (index_entry->Generation == 0) + index_entry->Generation++; // Keep non-zero on overflow + + const int pack_padding = atlas->TexGlyphPadding; + builder->RectsIndexFreeListStart = index_idx; + builder->RectsDiscardedCount++; + builder->RectsDiscardedSurface += (rect->w + pack_padding) * (rect->h + pack_padding); + rect->w = rect->h = 0; // Clear rectangle so it won't be packed again +} + +// Important: Calling this may recreate a new texture and therefore change atlas->TexData +// FIXME-NEWFONTS: Expose other glyph padding settings for custom alteration (e.g. drop shadows). See #7962 +ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry) +{ + IM_ASSERT(w > 0 && w <= 0xFFFF); + IM_ASSERT(h > 0 && h <= 0xFFFF); + + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + const int pack_padding = atlas->TexGlyphPadding; + builder->MaxRectSize.x = ImMax(builder->MaxRectSize.x, w); + builder->MaxRectSize.y = ImMax(builder->MaxRectSize.y, h); + + // Pack + ImTextureRect r = { 0, 0, (unsigned short)w, (unsigned short)h }; + for (int attempts_remaining = 3; attempts_remaining >= 0; attempts_remaining--) + { + // Try packing + stbrp_rect pack_r = {}; + pack_r.w = w + pack_padding; + pack_r.h = h + pack_padding; + stbrp_pack_rects((stbrp_context*)(void*)&builder->PackContext, &pack_r, 1); + r.x = (unsigned short)pack_r.x; + r.y = (unsigned short)pack_r.y; + if (pack_r.was_packed) + break; + + // If we ran out of attempts, return fallback + if (attempts_remaining == 0 || builder->LockDisableResize) { - // Register glyph - const int codepoint = src_tmp.GlyphsList[glyph_i]; - const stbtt_packedchar& pc = src_tmp.PackedChars[glyph_i]; - stbtt_aligned_quad q; - float unused_x = 0.0f, unused_y = 0.0f; - stbtt_GetPackedQuad(src_tmp.PackedChars, atlas->TexWidth, atlas->TexHeight, glyph_i, &unused_x, &unused_y, &q, 0); - float x0 = q.x0 * inv_rasterization_scale + font_off_x; - float y0 = q.y0 * inv_rasterization_scale + font_off_y; - float x1 = q.x1 * inv_rasterization_scale + font_off_x; - float y1 = q.y1 * inv_rasterization_scale + font_off_y; - dst_font->AddGlyph(&cfg, (ImWchar)codepoint, x0, y0, x1, y1, q.s0, q.t0, q.s1, q.t1, pc.xadvance * inv_rasterization_scale); + IMGUI_DEBUG_LOG_FONT("[font] Failed packing %dx%d rectangle. Returning fallback.\n", w, h); + return ImFontAtlasRectId_Invalid; } + + // Resize or repack atlas! (this should be a rare event) + ImFontAtlasTextureMakeSpace(atlas); } - // Cleanup - src_tmp_array.clear_destruct(); + builder->MaxRectBounds.x = ImMax(builder->MaxRectBounds.x, r.x + r.w + pack_padding); + builder->MaxRectBounds.y = ImMax(builder->MaxRectBounds.y, r.y + r.h + pack_padding); + builder->RectsPackedCount++; + builder->RectsPackedSurface += (w + pack_padding) * (h + pack_padding); + + builder->Rects.push_back(r); + if (overwrite_entry != NULL) + return ImFontAtlasPackReuseRectEntry(atlas, overwrite_entry); // Write into an existing entry instead of adding one (used during repack) + else + return ImFontAtlasPackAllocRectEntry(atlas, builder->Rects.Size - 1); +} + +// Generally for non-user facing functions: assert on invalid ID. +ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + IM_ASSERT(id != ImFontAtlasRectId_Invalid); + int index_idx = ImFontAtlasRectId_GetIndex(id); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + IM_ASSERT(index_entry->Generation == ImFontAtlasRectId_GetGeneration(id)); + IM_ASSERT(index_entry->IsUsed); + return &builder->Rects[index_entry->TargetIndex]; +} + +// For user-facing functions: return NULL on invalid ID. +// Important: return pointer is valid until next call to AddRect(), e.g. FindGlyph(), CalcTextSize() can all potentially invalidate previous pointers. +ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id) +{ + if (id == ImFontAtlasRectId_Invalid) + return NULL; + int index_idx = ImFontAtlasRectId_GetIndex(id); + if (atlas->Builder == NULL) + ImFontAtlasBuildInit(atlas); + ImFontAtlasBuilder* builder = (ImFontAtlasBuilder*)atlas->Builder; + IM_MSVC_WARNING_SUPPRESS(28182); // Static Analysis false positive "warning C28182: Dereferencing NULL pointer 'builder'" + if (index_idx >= builder->RectsIndex.Size) + return NULL; + ImFontAtlasRectEntry* index_entry = &builder->RectsIndex[index_idx]; + if (index_entry->Generation != ImFontAtlasRectId_GetGeneration(id) || !index_entry->IsUsed) + return NULL; + return &builder->Rects[index_entry->TargetIndex]; +} - ImFontAtlasBuildFinish(atlas); +// Important! This assume by ImFontConfig::GlyphExcludeRanges[] is a SMALL ARRAY (e.g. <10 entries) +// Use "Input Glyphs Overlap Detection Tool" to display a list of glyphs provided by multiple sources in order to set this array up. +static bool ImFontAtlasBuildAcceptCodepointForSource(ImFontConfig* src, ImWchar codepoint) +{ + if (const ImWchar* exclude_list = src->GlyphExcludeRanges) + for (; exclude_list[0] != 0; exclude_list += 2) + if (codepoint >= exclude_list[0] && codepoint <= exclude_list[1]) + return false; return true; } -const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype() +static void ImFontBaked_BuildGrowIndex(ImFontBaked* baked, int new_size) { - static ImFontBuilderIO io; - io.FontBuilder_Build = ImFontAtlasBuildWithStbTruetype; - return &io; + IM_ASSERT(baked->IndexAdvanceX.Size == baked->IndexLookup.Size); + if (new_size <= baked->IndexLookup.Size) + return; + baked->IndexAdvanceX.resize(new_size, -1.0f); + baked->IndexLookup.resize(new_size, IM_FONTGLYPH_INDEX_UNUSED); } -#endif // IMGUI_ENABLE_STB_TRUETYPE +static void ImFontAtlas_FontHookRemapCodepoint(ImFontAtlas* atlas, ImFont* font, ImWchar* c) +{ + IM_UNUSED(atlas); + if (font->RemapPairs.Data.Size != 0) + *c = (ImWchar)font->RemapPairs.GetInt((ImGuiID)*c, (int)*c); +} -void ImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas) +static ImFontGlyph* ImFontBaked_BuildLoadGlyph(ImFontBaked* baked, ImWchar codepoint, float* only_load_advance_x) { - for (ImFontConfig& font_cfg : atlas->ConfigData) + ImFont* font = baked->OwnerFont; + ImFontAtlas* atlas = font->OwnerAtlas; + if (atlas->Locked || (font->Flags & ImFontFlags_NoLoadGlyphs)) { - ImFont* font = font_cfg.DstFont; - if (!font_cfg.MergeMode) + // Lazily load fallback glyph + if (baked->FallbackGlyphIndex == -1 && baked->LoadNoFallback == 0) + ImFontAtlasBuildSetupFontBakedFallback(baked); + return NULL; + } + + // User remapping hooks + ImWchar src_codepoint = codepoint; + ImFontAtlas_FontHookRemapCodepoint(atlas, font, &codepoint); + + //char utf8_buf[5]; + //IMGUI_DEBUG_LOG("[font] BuildLoadGlyph U+%04X (%s)\n", (unsigned int)codepoint, ImTextCharToUtf8(utf8_buf, (unsigned int)codepoint)); + + // Special hook + // FIXME-NEWATLAS: it would be nicer if this used a more standardized way of hooking + if (codepoint == font->EllipsisChar && font->EllipsisAutoBake) + if (ImFontGlyph* glyph = ImFontAtlasBuildSetupFontBakedEllipsis(atlas, baked)) + return glyph; + + // Call backend + char* loader_user_data_p = (char*)baked->FontLoaderDatas; + int src_n = 0; + for (ImFontConfig* src : font->Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (!src->GlyphExcludeRanges || ImFontAtlasBuildAcceptCodepointForSource(src, codepoint)) { - font->ConfigData = &font_cfg; - font->ConfigDataCount = 0; + if (only_load_advance_x == NULL) + { + ImFontGlyph glyph_buf; + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, &glyph_buf, NULL)) + { + // FIXME: Add hooks for e.g. #7962 + glyph_buf.Codepoint = src_codepoint; + glyph_buf.SourceIdx = src_n; + return ImFontAtlasBakedAddFontGlyph(atlas, baked, src, &glyph_buf); + } + } + else + { + // Special mode but only loading glyphs metrics. Will rasterize and pack later. + if (loader->FontBakedLoadGlyph(atlas, src, baked, loader_user_data_p, codepoint, NULL, only_load_advance_x)) + { + ImFontAtlasBakedAddFontGlyphAdvancedX(atlas, baked, src, codepoint, *only_load_advance_x); + return NULL; + } + } } - font->ConfigDataCount++; + loader_user_data_p += loader->FontBakedSrcLoaderDataSize; + src_n++; + } + + // Lazily load fallback glyph + if (baked->LoadNoFallback) + return NULL; + if (baked->FallbackGlyphIndex == -1) + ImFontAtlasBuildSetupFontBakedFallback(baked); + + // Mark index as not found, so we don't attempt the search twice + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = baked->FallbackAdvanceX; + baked->IndexLookup[codepoint] = IM_FONTGLYPH_INDEX_NOT_FOUND; + return NULL; +} + +static float ImFontBaked_BuildLoadGlyphAdvanceX(ImFontBaked* baked, ImWchar codepoint) +{ + if (baked->Size >= IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE || baked->LoadNoRenderOnLayout) + { + // First load AdvanceX value used by CalcTextSize() API then load the rest when loaded by drawing API. + float only_advance_x = 0.0f; + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, &only_advance_x); + return glyph ? glyph->AdvanceX : only_advance_x; + } + else + { + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(baked, (ImWchar)codepoint, NULL); + return glyph ? glyph->AdvanceX : baked->FallbackAdvanceX; } } -void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent) +// The point of this indirection is to not be inlined in debug mode in order to not bloat inner loop.b +IM_MSVC_RUNTIME_CHECKS_OFF +static float BuildLoadGlyphGetAdvanceOrFallback(ImFontBaked* baked, unsigned int codepoint) { - if (!font_config->MergeMode) - { - font->ClearOutputData(); - font->FontSize = font_config->SizePixels; - IM_ASSERT(font->ConfigData == font_config); - font->ContainerAtlas = atlas; - font->Ascent = ascent; - font->Descent = descent; - } + return ImFontBaked_BuildLoadGlyphAdvanceX(baked, (ImWchar)codepoint); } +IM_MSVC_RUNTIME_CHECKS_RESTORE -void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque) +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas) { - stbrp_context* pack_context = (stbrp_context*)stbrp_context_opaque; - IM_ASSERT(pack_context != NULL); - - ImVector& user_rects = atlas->CustomRects; - IM_ASSERT(user_rects.Size >= 1); // We expect at least the default custom rects to be registered, else something went wrong. -#ifdef __GNUC__ - if (user_rects.Size < 1) { __builtin_unreachable(); } // Workaround for GCC bug if IM_ASSERT() is defined to conditionally throw (see #5343) -#endif - - ImVector pack_rects; - pack_rects.resize(user_rects.Size); - memset(pack_rects.Data, 0, (size_t)pack_rects.size_in_bytes()); - for (int i = 0; i < user_rects.Size; i++) + // [DEBUG] Log texture update requests + ImGuiContext& g = *GImGui; + IM_UNUSED(g); + for (ImTextureData* tex : atlas->TexList) { - pack_rects[i].w = user_rects[i].Width; - pack_rects[i].h = user_rects[i].Height; - } - stbrp_pack_rects(pack_context, &pack_rects[0], pack_rects.Size); - for (int i = 0; i < pack_rects.Size; i++) - if (pack_rects[i].was_packed) + if ((g.IO.BackendFlags & ImGuiBackendFlags_RendererHasTextures) == 0) + IM_ASSERT(tex->Updates.Size == 0); + if (tex->Status == ImTextureStatus_WantCreate) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: create %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + else if (tex->Status == ImTextureStatus_WantDestroy) + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: destroy %dx%d, texid=0x%" IM_PRIX64 ", backend_data=%p\n", tex->UniqueID, tex->Width, tex->Height, IM_TEXTUREID_TO_U64(tex->TexID), tex->BackendUserData); + else if (tex->Status == ImTextureStatus_WantUpdates) { - user_rects[i].X = (unsigned short)pack_rects[i].x; - user_rects[i].Y = (unsigned short)pack_rects[i].y; - IM_ASSERT(pack_rects[i].w == user_rects[i].Width && pack_rects[i].h == user_rects[i].Height); - atlas->TexHeight = ImMax(atlas->TexHeight, pack_rects[i].y + pack_rects[i].h); + IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update %d regions, texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, tex->Updates.Size, IM_TEXTUREID_TO_U64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); + for (const ImTextureRect& r : tex->Updates) + { + IM_UNUSED(r); + IM_ASSERT(r.x >= 0 && r.y >= 0); + IM_ASSERT(r.x + r.w <= tex->Width && r.y + r.h <= tex->Height); // In theory should subtract PackPadding but it's currently part of atlas and mid-frame change would wreck assert. + //IMGUI_DEBUG_LOG_FONT("[font] Texture #%03d: update (% 4d..%-4d)->(% 4d..%-4d), texid=0x%" IM_PRIX64 ", backend_data=0x%" IM_PRIX64 "\n", tex->UniqueID, r.x, r.y, r.x + r.w, r.y + r.h, IM_TEXTUREID_TO_U64(tex->TexID), (ImU64)(intptr_t)tex->BackendUserData); + } } + } } +#endif -void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value) -{ - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned char* out_pixel = atlas->TexPixelsAlpha8 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : 0x00; -} +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: backend for stb_truetype +//------------------------------------------------------------------------- +// (imstb_truetype.h in included near the top of this file, when IMGUI_ENABLE_STB_TRUETYPE is set) +//------------------------------------------------------------------------- -void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value) +#ifdef IMGUI_ENABLE_STB_TRUETYPE + +// One for each ConfigData +struct ImGui_ImplStbTrueType_FontSrcData { - IM_ASSERT(x >= 0 && x + w <= atlas->TexWidth); - IM_ASSERT(y >= 0 && y + h <= atlas->TexHeight); - unsigned int* out_pixel = atlas->TexPixelsRGBA32 + x + (y * atlas->TexWidth); - for (int off_y = 0; off_y < h; off_y++, out_pixel += atlas->TexWidth, in_str += w) - for (int off_x = 0; off_x < w; off_x++) - out_pixel[off_x] = (in_str[off_x] == in_marker_char) ? in_marker_pixel_value : IM_COL32_BLACK_TRANS; -} + stbtt_fontinfo FontInfo; + float ScaleFactor; +}; -static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontSrcInit(ImFontAtlas* atlas, ImFontConfig* src) { - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdMouseCursors); - IM_ASSERT(r->IsPacked()); + IM_UNUSED(atlas); - const int w = atlas->TexWidth; - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = IM_NEW(ImGui_ImplStbTrueType_FontSrcData); + IM_ASSERT(src->FontLoaderData == NULL); + + // Initialize helper structure for font loading and verify that the TTF/OTF data is correct + const int font_offset = stbtt_GetFontOffsetForIndex((unsigned char*)src->FontData, src->FontNo); + if (font_offset < 0) { - // Render/copy pixels - IM_ASSERT(r->Width == FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1 && r->Height == FONT_ATLAS_DEFAULT_TEX_DATA_H); - const int x_for_white = r->X; - const int x_for_black = r->X + FONT_ATLAS_DEFAULT_TEX_DATA_W + 1; - if (atlas->TexPixelsAlpha8 != NULL) - { - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', 0xFF); - ImFontAtlasBuildRender8bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', 0xFF); - } - else - { - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_white, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, '.', IM_COL32_WHITE); - ImFontAtlasBuildRender32bppRectFromString(atlas, x_for_black, r->Y, FONT_ATLAS_DEFAULT_TEX_DATA_W, FONT_ATLAS_DEFAULT_TEX_DATA_H, FONT_ATLAS_DEFAULT_TEX_DATA_PIXELS, 'X', IM_COL32_WHITE); - } + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_GetFontOffsetForIndex(): FontData is incorrect, or FontNo cannot be found."); + return false; } - else + if (!stbtt_InitFont(&bd_font_data->FontInfo, (unsigned char*)src->FontData, font_offset)) { - // Render 4 white pixels - IM_ASSERT(r->Width == 2 && r->Height == 2); - const int offset = (int)r->X + (int)r->Y * w; - if (atlas->TexPixelsAlpha8 != NULL) - { - atlas->TexPixelsAlpha8[offset] = atlas->TexPixelsAlpha8[offset + 1] = atlas->TexPixelsAlpha8[offset + w] = atlas->TexPixelsAlpha8[offset + w + 1] = 0xFF; - } - else - { - atlas->TexPixelsRGBA32[offset] = atlas->TexPixelsRGBA32[offset + 1] = atlas->TexPixelsRGBA32[offset + w] = atlas->TexPixelsRGBA32[offset + w + 1] = IM_COL32_WHITE; - } + IM_DELETE(bd_font_data); + IM_ASSERT_USER_ERROR(0, "stbtt_InitFont(): failed to parse FontData. It is correct and complete? Check FontDataSize."); + return false; } - atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); -} - -static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) -{ - if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) - return; + src->FontLoaderData = bd_font_data; - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); - IM_ASSERT(r->IsPacked()); - for (unsigned int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row - { - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - unsigned int y = n; - unsigned int line_width = n; - unsigned int pad_left = (r->Width - line_width) / 2; - unsigned int pad_right = r->Width - (pad_left + line_width); + const float ref_size = src->DstFont->Sources[0]->SizePixels; + if (src->MergeMode && src->SizePixels == 0.0f) + src->SizePixels = ref_size; - // Write each slice - IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels - if (atlas->TexPixelsAlpha8 != NULL) - { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + if (src->SizePixels >= 0.0f) + bd_font_data->ScaleFactor = stbtt_ScaleForPixelHeight(&bd_font_data->FontInfo, 1.0f); + else + bd_font_data->ScaleFactor = stbtt_ScaleForMappingEmToPixels(&bd_font_data->FontInfo, 1.0f); + if (src->MergeMode && src->SizePixels != 0.0f && ref_size != 0.0f) + bd_font_data->ScaleFactor *= src->SizePixels / ref_size; // FIXME-NEWATLAS: Should tidy up that a bit - for (unsigned int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + return true; +} - for (unsigned int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; - } - else - { - unsigned int* write_ptr = &atlas->TexPixelsRGBA32[r->X + ((r->Y + y) * atlas->TexWidth)]; - for (unsigned int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); +static void ImGui_ImplStbTrueType_FontSrcDestroy(ImFontAtlas* atlas, ImFontConfig* src) +{ + IM_UNUSED(atlas); + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_DELETE(bd_font_data); + src->FontLoaderData = NULL; +} - for (unsigned int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; +static bool ImGui_ImplStbTrueType_FontSrcContainsGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint) +{ + IM_UNUSED(atlas); - for (unsigned int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); - } + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data != NULL); - // Calculate UVs for this line - ImVec2 uv0 = ImVec2((float)(r->X + pad_left - 1), (float)(r->Y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r->X + pad_left + line_width + 1), (float)(r->Y + y + 1)) * atlas->TexUvScale; - float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); - } + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + return glyph_index != 0; } -// Note: this is called / shared by both the stb_truetype and the FreeType builder -void ImFontAtlasBuildInit(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*) { - // Round font size - // - We started rounding in 1.90 WIP (18991) as our layout system currently doesn't support non-rounded font size well yet. - // - Note that using io.FontGlobalScale or SetWindowFontScale(), with are legacy-ish, partially supported features, can still lead to unrounded sizes. - // - We may support it better later and remove this rounding. - for (ImFontConfig& cfg : atlas->ConfigData) - cfg.SizePixels = ImTrunc(cfg.SizePixels); - - // Register texture region for mouse cursors or standard white pixels - if (atlas->PackIdMouseCursors < 0) - { - if (!(atlas->Flags & ImFontAtlasFlags_NoMouseCursors)) - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(FONT_ATLAS_DEFAULT_TEX_DATA_W * 2 + 1, FONT_ATLAS_DEFAULT_TEX_DATA_H); - else - atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); - } + IM_UNUSED(atlas); - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - if (atlas->PackIdLines < 0) + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + if (src->MergeMode == false) { - if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) - atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + // FIXME-NEWFONTS: reevaluate how to use sizing metrics + // FIXME-NEWFONTS: make use of line gap value + float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&bd_font_data->FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); + baked->Ascent = ImCeil(unscaled_ascent * scale_for_layout); + baked->Descent = ImFloor(unscaled_descent * scale_for_layout); } + return true; } -// This is called/shared by both the stb_truetype and the FreeType builder. -void ImFontAtlasBuildFinish(ImFontAtlas* atlas) +static bool ImGui_ImplStbTrueType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void*, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x) { - // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL || atlas->TexPixelsRGBA32 != NULL); - ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderLinesTexData(atlas); + // Search for first font which has the glyph + ImGui_ImplStbTrueType_FontSrcData* bd_font_data = (ImGui_ImplStbTrueType_FontSrcData*)src->FontLoaderData; + IM_ASSERT(bd_font_data); + int glyph_index = stbtt_FindGlyphIndex(&bd_font_data->FontInfo, (int)codepoint); + if (glyph_index == 0) + return false; - // Register custom rectangle glyphs - for (int i = 0; i < atlas->CustomRects.Size; i++) + // Fonts unit to pixels + int oversample_h, oversample_v; + ImFontAtlasBuildGetOversampleFactors(src, baked, &oversample_h, &oversample_v); + const float scale_for_layout = bd_font_data->ScaleFactor * baked->Size; + const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity; + const float scale_for_raster_x = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_h; + const float scale_for_raster_y = bd_font_data->ScaleFactor * baked->Size * rasterizer_density * oversample_v; + + // Obtain size and advance + int x0, y0, x1, y1; + int advance, lsb; + stbtt_GetGlyphBitmapBoxSubpixel(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, 0, 0, &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&bd_font_data->FontInfo, glyph_index, &advance, &lsb); + + // Load metrics only mode + if (out_advance_x != NULL) { - const ImFontAtlasCustomRect* r = &atlas->CustomRects[i]; - if (r->Font == NULL || r->GlyphID == 0) - continue; + IM_ASSERT(out_glyph == NULL); + *out_advance_x = advance * scale_for_layout; + return true; + } - // Will ignore ImFontConfig settings: GlyphMinAdvanceX, GlyphMinAdvanceY, GlyphExtraSpacing, PixelSnapH - IM_ASSERT(r->Font->ContainerAtlas == atlas); - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(r, &uv0, &uv1); - r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX); + // Prepare glyph + out_glyph->Codepoint = codepoint; + out_glyph->AdvanceX = advance * scale_for_layout; + + // Pack and retrieve position inside texture atlas + // (generally based on stbtt_PackFontRangesRenderIntoRects) + const bool is_visible = (x0 != x1 && y0 != y1); + if (is_visible) + { + const int w = (x1 - x0 + oversample_h - 1); + const int h = (y1 - y0 + oversample_v - 1); + ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h); + if (pack_id == ImFontAtlasRectId_Invalid) + { + // Pathological out of memory case (TexMaxWidth/TexMaxHeight set too small?) + IM_ASSERT(pack_id != ImFontAtlasRectId_Invalid && "Out of texture memory."); + return false; + } + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id); + + // Render + stbtt_GetGlyphBitmapBox(&bd_font_data->FontInfo, glyph_index, scale_for_raster_x, scale_for_raster_y, &x0, &y0, &x1, &y1); + ImFontAtlasBuilder* builder = atlas->Builder; + builder->TempBuffer.resize(w * h * 1); + unsigned char* bitmap_pixels = builder->TempBuffer.Data; + memset(bitmap_pixels, 0, w * h * 1); + + // Render with oversampling + // (those functions conveniently assert if pixels are not cleared, which is another safety layer) + float sub_x, sub_y; + stbtt_MakeGlyphBitmapSubpixelPrefilter(&bd_font_data->FontInfo, bitmap_pixels, w, h, w, + scale_for_raster_x, scale_for_raster_y, 0, 0, oversample_h, oversample_v, &sub_x, &sub_y, glyph_index); + + const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float font_off_x = (src->GlyphOffset.x * offsets_scale); + float font_off_y = (src->GlyphOffset.y * offsets_scale); + if (src->PixelSnapH) // Snap scaled offset. This is to mitigate backward compatibility issues for GlyphOffset, but a better design would be welcome. + font_off_x = IM_ROUND(font_off_x); + if (src->PixelSnapV) + font_off_y = IM_ROUND(font_off_y); + font_off_x += sub_x; + font_off_y += sub_y + IM_ROUND(baked->Ascent); + float recip_h = 1.0f / (oversample_h * rasterizer_density); + float recip_v = 1.0f / (oversample_v * rasterizer_density); + + // Register glyph + // r->x r->y are coordinates inside texture (in pixels) + // glyph.X0, glyph.Y0 are drawing coordinates from base text position, and accounting for oversampling. + out_glyph->X0 = x0 * recip_h + font_off_x; + out_glyph->Y0 = y0 * recip_v + font_off_y; + out_glyph->X1 = (x0 + (int)r->w) * recip_h + font_off_x; + out_glyph->Y1 = (y0 + (int)r->h) * recip_v + font_off_y; + out_glyph->Visible = true; + out_glyph->PackId = pack_id; + ImFontAtlasBakedSetFontGlyphBitmap(atlas, baked, src, out_glyph, r, bitmap_pixels, ImTextureFormat_Alpha8, w); } - // Build all fonts lookup tables - for (ImFont* font : atlas->Fonts) - if (font->DirtyLookupTables) - font->BuildLookupTable(); + return true; +} - atlas->TexReady = true; +const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype() +{ + static ImFontLoader loader; + loader.Name = "stb_truetype"; + loader.FontSrcInit = ImGui_ImplStbTrueType_FontSrcInit; + loader.FontSrcDestroy = ImGui_ImplStbTrueType_FontSrcDestroy; + loader.FontSrcContainsGlyph = ImGui_ImplStbTrueType_FontSrcContainsGlyph; + loader.FontBakedInit = ImGui_ImplStbTrueType_FontBakedInit; + loader.FontBakedDestroy = NULL; + loader.FontBakedLoadGlyph = ImGui_ImplStbTrueType_FontBakedLoadGlyph; + return &loader; } +#endif // IMGUI_ENABLE_STB_TRUETYPE + +//------------------------------------------------------------------------- +// [SECTION] ImFontAtlas: glyph ranges helpers +//------------------------------------------------------------------------- +// - GetGlyphRangesDefault() +// Obsolete functions since 1.92: +// - GetGlyphRangesGreek() +// - GetGlyphRangesKorean() +// - GetGlyphRangesChineseFull() +// - GetGlyphRangesChineseSimplifiedCommon() +// - GetGlyphRangesJapanese() +// - GetGlyphRangesCyrillic() +// - GetGlyphRangesThai() +// - GetGlyphRangesVietnamese() +//----------------------------------------------------------------------------- + // Retrieve list of range (2 int per range, values are inclusive) const ImWchar* ImFontAtlas::GetGlyphRangesDefault() { @@ -3280,6 +4782,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() return &ranges[0]; } +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS const ImWchar* ImFontAtlas::GetGlyphRangesGreek() { static const ImWchar ranges[] = @@ -3330,10 +4833,6 @@ static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* out_ranges[0] = 0; } -//------------------------------------------------------------------------- -// [SECTION] ImFontAtlas glyph ranges helpers -//------------------------------------------------------------------------- - const ImWchar* ImFontAtlas::GetGlyphRangesChineseSimplifiedCommon() { // Store 2500 regularly used characters for Simplified Chinese. @@ -3533,6 +5032,7 @@ const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() }; return &ranges[0]; } +#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS //----------------------------------------------------------------------------- // [SECTION] ImFontGlyphRangesBuilder @@ -3540,7 +5040,9 @@ const ImWchar* ImFontAtlas::GetGlyphRangesVietnamese() void ImFontGlyphRangesBuilder::AddText(const char* text, const char* text_end) { - while (text_end ? (text < text_end) : *text) + if (text_end == NULL) + text_end = text + strlen(text); + while (text < text_end) { unsigned int c = 0; int c_len = ImTextCharFromUtf8(&c, text, text_end); @@ -3576,251 +5078,301 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector* out_ranges) // [SECTION] ImFont //----------------------------------------------------------------------------- -ImFont::ImFont() -{ - FontSize = 0.0f; - FallbackAdvanceX = 0.0f; - FallbackChar = (ImWchar)-1; - EllipsisChar = (ImWchar)-1; - EllipsisWidth = EllipsisCharStep = 0.0f; - EllipsisCharCount = 0; - FallbackGlyph = NULL; - ContainerAtlas = NULL; - ConfigData = NULL; - ConfigDataCount = 0; - DirtyLookupTables = false; - Scale = 1.0f; - Ascent = Descent = 0.0f; - MetricsTotalSurface = 0; - memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); -} - -ImFont::~ImFont() +ImFontBaked::ImFontBaked() { - ClearOutputData(); + memset(this, 0, sizeof(*this)); + FallbackGlyphIndex = -1; } -void ImFont::ClearOutputData() +void ImFontBaked::ClearOutputData() { - FontSize = 0.0f; FallbackAdvanceX = 0.0f; Glyphs.clear(); IndexAdvanceX.clear(); IndexLookup.clear(); - FallbackGlyph = NULL; - ContainerAtlas = NULL; - DirtyLookupTables = true; + FallbackGlyphIndex = -1; Ascent = Descent = 0.0f; MetricsTotalSurface = 0; } -static ImWchar FindFirstExistingGlyph(ImFont* font, const ImWchar* candidate_chars, int candidate_chars_count) +ImFont::ImFont() { - for (int n = 0; n < candidate_chars_count; n++) - if (font->FindGlyphNoFallback(candidate_chars[n]) != NULL) - return candidate_chars[n]; - return (ImWchar)-1; + memset(this, 0, sizeof(*this)); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + Scale = 1.0f; +#endif } -void ImFont::BuildLookupTable() +ImFont::~ImFont() { - int max_codepoint = 0; - for (int i = 0; i != Glyphs.Size; i++) - max_codepoint = ImMax(max_codepoint, (int)Glyphs[i].Codepoint); + ClearOutputData(); +} - // Build lookup table - IM_ASSERT(Glyphs.Size > 0 && "Font has not loaded glyph!"); - IM_ASSERT(Glyphs.Size < 0xFFFF); // -1 is reserved - IndexAdvanceX.clear(); - IndexLookup.clear(); - DirtyLookupTables = false; - memset(Used4kPagesMap, 0, sizeof(Used4kPagesMap)); - GrowIndex(max_codepoint + 1); - for (int i = 0; i < Glyphs.Size; i++) - { - int codepoint = (int)Glyphs[i].Codepoint; - IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (ImWchar)i; +void ImFont::ClearOutputData() +{ + if (ImFontAtlas* atlas = OwnerAtlas) + ImFontAtlasFontDiscardBakes(atlas, this, 0); + FallbackChar = EllipsisChar = 0; + memset(Used8kPagesMap, 0, sizeof(Used8kPagesMap)); + LastBaked = NULL; +} - // Mark 4K page as used - const int page_n = codepoint / 4096; - Used4kPagesMap[page_n >> 3] |= 1 << (page_n & 7); - } +// API is designed this way to avoid exposing the 8K page size +// e.g. use with IsGlyphRangeUnused(0, 255) +bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) +{ + unsigned int page_begin = (c_begin / 8192); + unsigned int page_last = (c_last / 8192); + for (unsigned int page_n = page_begin; page_n <= page_last; page_n++) + if ((page_n >> 3) < sizeof(Used8kPagesMap)) + if (Used8kPagesMap[page_n >> 3] & (1 << (page_n & 7))) + return false; + return true; +} - // Create a glyph to handle TAB - // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((ImWchar)' ')) +// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. +// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). +// - 'src' is not necessarily == 'this->Sources' because multiple source fonts+configs can be used to build one target font. +ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph) +{ + int glyph_idx = baked->Glyphs.Size; + baked->Glyphs.push_back(*in_glyph); + ImFontGlyph* glyph = &baked->Glyphs[glyph_idx]; + IM_ASSERT(baked->Glyphs.Size < 0xFFFE); // IndexLookup[] hold 16-bit values and -1/-2 are reserved. + + // Set UV from packed rectangle + if (glyph->PackId != ImFontAtlasRectId_Invalid) { - if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times (FIXME: Flaky) - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((ImWchar)' '); - tab_glyph.Codepoint = '\t'; - tab_glyph.AdvanceX *= IM_TABSIZE; - IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (ImWchar)(Glyphs.Size - 1); + ImTextureRect* r = ImFontAtlasPackGetRect(atlas, glyph->PackId); + IM_ASSERT(glyph->U0 == 0.0f && glyph->V0 == 0.0f && glyph->U1 == 0.0f && glyph->V1 == 0.0f); + glyph->U0 = (r->x) * atlas->TexUvScale.x; + glyph->V0 = (r->y) * atlas->TexUvScale.y; + glyph->U1 = (r->x + r->w) * atlas->TexUvScale.x; + glyph->V1 = (r->y + r->h) * atlas->TexUvScale.y; + baked->MetricsTotalSurface += r->w * r->h; } - // Mark special glyphs as not visible (note that AddGlyph already mark as non-visible glyphs with zero-size polygons) - SetGlyphVisible((ImWchar)' ', false); - SetGlyphVisible((ImWchar)'\t', false); - - // Setup Fallback character - const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' }; - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) + if (src != NULL) { - FallbackChar = FindFirstExistingGlyph(this, fallback_chars, IM_ARRAYSIZE(fallback_chars)); - FallbackGlyph = FindGlyphNoFallback(FallbackChar); - if (FallbackGlyph == NULL) + // Clamp & recenter if needed + const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + float advance_x = ImClamp(glyph->AdvanceX, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + if (advance_x != glyph->AdvanceX) { - FallbackGlyph = &Glyphs.back(); - FallbackChar = (ImWchar)FallbackGlyph->Codepoint; + float char_off_x = src->PixelSnapH ? ImTrunc((advance_x - glyph->AdvanceX) * 0.5f) : (advance_x - glyph->AdvanceX) * 0.5f; + glyph->X0 += char_off_x; + glyph->X1 += char_off_x; } - } - FallbackAdvanceX = FallbackGlyph->AdvanceX; - for (int i = 0; i < max_codepoint + 1; i++) - if (IndexAdvanceX[i] < 0.0f) - IndexAdvanceX[i] = FallbackAdvanceX; - // Setup Ellipsis character. It is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis). - // However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character. - // FIXME: Note that 0x2026 is rarely included in our font ranges. Because of this we are more likely to use three individual dots. - const ImWchar ellipsis_chars[] = { (ImWchar)0x2026, (ImWchar)0x0085 }; - const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E }; - if (EllipsisChar == (ImWchar)-1) - EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars)); - const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars)); - if (EllipsisChar != (ImWchar)-1) - { - EllipsisCharCount = 1; - EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1; + // Snap to pixel + if (src->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing + glyph->AdvanceX = advance_x + src->GlyphExtraAdvanceX; } - else if (dot_char != (ImWchar)-1) + if (glyph->Colored) + atlas->TexPixelsUseColors = atlas->TexData->UseColors = true; + + // Update lookup tables + const int codepoint = glyph->Codepoint; + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = glyph->AdvanceX; + baked->IndexLookup[codepoint] = (ImU16)glyph_idx; + const int page_n = codepoint / 8192; + baked->OwnerFont->Used8kPagesMap[page_n >> 3] |= 1 << (page_n & 7); + + return glyph; +} + +// FIXME: Code is duplicated with code above. +void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x) +{ + IM_UNUSED(atlas); + if (src != NULL) { - const ImFontGlyph* glyph = FindGlyph(dot_char); - EllipsisChar = dot_char; - EllipsisCharCount = 3; - EllipsisCharStep = (glyph->X1 - glyph->X0) + 1.0f; - EllipsisWidth = EllipsisCharStep * 3.0f - 1.0f; + // Clamp & recenter if needed + const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; + const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f; + advance_x = ImClamp(advance_x, src->GlyphMinAdvanceX * offsets_scale, src->GlyphMaxAdvanceX * offsets_scale); + + // Snap to pixel + if (src->PixelSnapH) + advance_x = IM_ROUND(advance_x); + + // Bake spacing + advance_x += src->GlyphExtraAdvanceX; } + + ImFontBaked_BuildGrowIndex(baked, codepoint + 1); + baked->IndexAdvanceX[codepoint] = advance_x; } -// API is designed this way to avoid exposing the 4K page size -// e.g. use with IsGlyphRangeUnused(0, 255) -bool ImFont::IsGlyphRangeUnused(unsigned int c_begin, unsigned int c_last) +// Copy to texture, post-process and queue update for backend +void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch) { - unsigned int page_begin = (c_begin / 4096); - unsigned int page_last = (c_last / 4096); - for (unsigned int page_n = page_begin; page_n <= page_last; page_n++) - if ((page_n >> 3) < sizeof(Used4kPagesMap)) - if (Used4kPagesMap[page_n >> 3] & (1 << (page_n & 7))) - return false; - return true; + ImTextureData* tex = atlas->TexData; + IM_ASSERT(r->x + r->w <= tex->Width && r->y + r->h <= tex->Height); + ImFontAtlasTextureBlockConvert(src_pixels, src_fmt, src_pitch, (unsigned char*)tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h); + ImFontAtlasPostProcessData pp_data = { atlas, baked->OwnerFont, src, baked, glyph, tex->GetPixelsAt(r->x, r->y), tex->Format, tex->GetPitch(), r->w, r->h }; + ImFontAtlasTextureBlockPostProcess(&pp_data); + ImFontAtlasTextureBlockQueueUpload(atlas, tex, r->x, r->y, r->w, r->h); } -void ImFont::SetGlyphVisible(ImWchar c, bool visible) +void ImFont::AddRemapChar(ImWchar from_codepoint, ImWchar to_codepoint) { - if (ImFontGlyph* glyph = (ImFontGlyph*)(void*)FindGlyph((ImWchar)c)) - glyph->Visible = visible ? 1 : 0; + RemapPairs.SetInt((ImGuiID)from_codepoint, (int)to_codepoint); } -void ImFont::GrowIndex(int new_size) +// Find glyph, load if necessary, return fallback if missing +ImFontGlyph* ImFontBaked::FindGlyph(ImWchar c) { - IM_ASSERT(IndexAdvanceX.Size == IndexLookup.Size); - if (new_size <= IndexLookup.Size) - return; - IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (ImWchar)-1); + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return &Glyphs.Data[FallbackGlyphIndex]; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + return glyph ? glyph : &Glyphs.Data[FallbackGlyphIndex]; } -// x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. -// Not to be mistaken with texture coordinates, which are held by u0/v0/u1/v1 in normalized format (0.0..1.0 on each texture axis). -// 'cfg' is not necessarily == 'this->ConfigData' because multiple source fonts+configs can be used to build one target font. -void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, float y0, float x1, float y1, float u0, float v0, float u1, float v1, float advance_x) +// Attempt to load but when missing, return NULL instead of FallbackGlyph +ImFontGlyph* ImFontBaked::FindGlyphNoFallback(ImWchar c) { - if (cfg != NULL) + if (c < (size_t)IndexLookup.Size) IM_LIKELY { - // Clamp & recenter if needed - const float advance_x_original = advance_x; - advance_x = ImClamp(advance_x, cfg->GlyphMinAdvanceX, cfg->GlyphMaxAdvanceX); - if (advance_x != advance_x_original) - { - float char_off_x = cfg->PixelSnapH ? ImTrunc((advance_x - advance_x_original) * 0.5f) : (advance_x - advance_x_original) * 0.5f; - x0 += char_off_x; - x1 += char_off_x; - } - - // Snap to pixel - if (cfg->PixelSnapH) - advance_x = IM_ROUND(advance_x); - - // Bake spacing - advance_x += cfg->GlyphExtraSpacing.x; - } - - Glyphs.resize(Glyphs.Size + 1); - ImFontGlyph& glyph = Glyphs.back(); - glyph.Codepoint = (unsigned int)codepoint; - glyph.Visible = (x0 != x1) && (y0 != y1); - glyph.Colored = false; - glyph.X0 = x0; - glyph.Y0 = y0; - glyph.X1 = x1; - glyph.Y1 = y1; - glyph.U0 = u0; - glyph.V0 = v0; - glyph.U1 = u1; - glyph.V1 = v1; - glyph.AdvanceX = advance_x; - - // Compute rough surface usage metrics (+1 to account for average padding, +0.99 to round) - // We use (U1-U0)*TexWidth instead of X1-X0 to account for oversampling. - float pad = ContainerAtlas->TexGlyphPadding + 0.99f; - DirtyLookupTables = true; - MetricsTotalSurface += (int)((glyph.U1 - glyph.U0) * ContainerAtlas->TexWidth + pad) * (int)((glyph.V1 - glyph.V0) * ContainerAtlas->TexHeight + pad); + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return NULL; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return &Glyphs.Data[i]; + } + LoadNoFallback = true; // This is actually a rare call, not done in hot-loop, so we prioritize not adding extra cruft to ImFontBaked_BuildLoadGlyph() call sites. + ImFontGlyph* glyph = ImFontBaked_BuildLoadGlyph(this, c, NULL); + LoadNoFallback = false; + return glyph; } -void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) +bool ImFontBaked::IsGlyphLoaded(ImWchar c) { - IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. - unsigned int index_size = (unsigned int)IndexLookup.Size; + if (c < (size_t)IndexLookup.Size) IM_LIKELY + { + const int i = (int)IndexLookup.Data[c]; + if (i == IM_FONTGLYPH_INDEX_NOT_FOUND) + return false; + if (i != IM_FONTGLYPH_INDEX_UNUSED) + return true; + } + return false; +} - if (dst < index_size && IndexLookup.Data[dst] == (ImWchar)-1 && !overwrite_dst) // 'dst' already exists - return; - if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op - return; +// This is not fast query +bool ImFont::IsGlyphInFont(ImWchar c) +{ + ImFontAtlas* atlas = OwnerAtlas; + ImFontAtlas_FontHookRemapCodepoint(atlas, this, &c); + for (ImFontConfig* src : Sources) + { + const ImFontLoader* loader = src->FontLoader ? src->FontLoader : atlas->FontLoader; + if (loader->FontSrcContainsGlyph != NULL && loader->FontSrcContainsGlyph(atlas, src, c)) + return true; + } + return false; +} - GrowIndex(dst + 1); - IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImWchar)-1; - IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; +// This is manually inlined in CalcTextSizeA() and CalcWordWrapPosition(), with a non-inline call to BuildLoadGlyphGetAdvanceOrFallback(). +IM_MSVC_RUNTIME_CHECKS_OFF +float ImFontBaked::GetCharAdvance(ImWchar c) +{ + if ((int)c < IndexAdvanceX.Size) + { + // Missing glyphs fitting inside index will have stored FallbackAdvanceX already. + const float x = IndexAdvanceX.Data[c]; + if (x >= 0.0f) + return x; + } + return ImFontBaked_BuildLoadGlyphAdvanceX(this, c); } +IM_MSVC_RUNTIME_CHECKS_RESTORE -const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const +ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density) { - if (c >= (size_t)IndexLookup.Size) - return FallbackGlyph; - const ImWchar i = IndexLookup.Data[c]; - if (i == (ImWchar)-1) - return FallbackGlyph; - return &Glyphs.Data[i]; + struct { ImGuiID FontId; float BakedSize; float RasterizerDensity; } hashed_data; + hashed_data.FontId = font_id; + hashed_data.BakedSize = baked_size; + hashed_data.RasterizerDensity = rasterizer_density; + return ImHashData(&hashed_data, sizeof(hashed_data)); } -const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const +// ImFontBaked pointers are valid for the entire frame but shall never be kept between frames. +ImFontBaked* ImFont::GetFontBaked(float size, float density) { - if (c >= (size_t)IndexLookup.Size) - return NULL; - const ImWchar i = IndexLookup.Data[c]; - if (i == (ImWchar)-1) + ImFontBaked* baked = LastBaked; + + // Round font size + // - ImGui::PushFont() will already round, but other paths calling GetFontBaked() directly also needs it (e.g. ImFontAtlasBuildPreloadAllGlyphRanges) + size = ImGui::GetRoundedFontSize(size); + + if (density < 0.0f) + density = CurrentRasterizerDensity; + if (baked && baked->Size == size && baked->RasterizerDensity == density) + return baked; + + ImFontAtlas* atlas = OwnerAtlas; + ImFontAtlasBuilder* builder = atlas->Builder; + baked = ImFontAtlasBakedGetOrAdd(atlas, this, size, density); + if (baked == NULL) return NULL; - return &Glyphs.Data[i]; + baked->LastUsedFrame = builder->FrameCount; + LastBaked = baked; + return baked; } -// Wrapping skips upcoming blanks -static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) +ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density) { - while (text < text_end && ImCharIsBlankA(*text)) - text++; + // FIXME-NEWATLAS: Design for picking a nearest size based on some criteria? + // FIXME-NEWATLAS: Altering font density won't work right away. + IM_ASSERT(font_size > 0.0f && font_rasterizer_density > 0.0f); + ImGuiID baked_id = ImFontAtlasBakedGetId(font->FontId, font_size, font_rasterizer_density); + ImFontAtlasBuilder* builder = atlas->Builder; + ImFontBaked** p_baked_in_map = (ImFontBaked**)builder->BakedMap.GetVoidPtrRef(baked_id); + ImFontBaked* baked = *p_baked_in_map; + if (baked != NULL) + { + IM_ASSERT(baked->Size == font_size && baked->OwnerFont == font && baked->BakedId == baked_id); + return baked; + } + + // If atlas is locked, find closest match + // FIXME-OPT: This is not an optimal query. + if ((font->Flags & ImFontFlags_LockBakedSizes) || atlas->Locked) + { + baked = ImFontAtlasBakedGetClosestMatch(atlas, font, font_size, font_rasterizer_density); + if (baked != NULL) + return baked; + if (atlas->Locked) + { + IM_ASSERT(!atlas->Locked && "Cannot use dynamic font size with a locked ImFontAtlas!"); // Locked because rendering backend does not support ImGuiBackendFlags_RendererHasTextures! + return NULL; + } + } + + // Create new + baked = ImFontAtlasBakedAdd(atlas, font, font_size, font_rasterizer_density, baked_id); + *p_baked_in_map = baked; // To avoid 'builder->BakedMap.SetVoidPtr(baked_id, baked);' while we can. + return baked; +} + +// Trim trailing space and find beginning of next line +const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_end, ImDrawTextFlags flags) +{ + if ((flags & ImDrawTextFlags_WrapKeepBlanks) == 0) + while (text < text_end && ImCharIsBlankA(*text)) + text++; if (*text == '\n') text++; return text; @@ -3829,7 +5381,7 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* text, const char* text_end, float wrap_width, ImDrawTextFlags flags) { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" @@ -3842,6 +5394,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" + + ImFontBaked* baked = font->GetFontBaked(size); + const float scale = size / baked->Size; + float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; @@ -3865,12 +5421,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c if (c < 32) { if (c == '\n') - { - line_width = word_width = blank_width = 0.0f; - inside_word = true; - s = next_s; - continue; - } + return s; // Direct return, skip "Wrap_width is too small to fit anything" path. if (c == '\r') { s = next_s; @@ -3878,7 +5429,11 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } } - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX); + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + if (ImCharIsBlankW(c)) { if (inside_word) @@ -3901,11 +5456,13 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c { prev_word_end = word_end; line_width += word_width + blank_width; + if ((flags & ImDrawTextFlags_WrapKeepBlanks) && line_width <= wrap_width) + prev_word_end = s; word_width = blank_width = 0.0f; } // Allow wrapping after punctuation. - inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"'); + inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002); } // We ignore blank width at the end of the line (they can be skipped) @@ -3923,17 +5480,25 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol). if (s == text && text < text_end) - return s + 1; + return s + ImTextCountUtf8BytesFromChar(s, text_end); return s; } -ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const +const char* ImFont::CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width) +{ + return ImFontCalcWordWrapPositionEx(this, size, text, text_end, wrap_width, ImDrawTextFlags_None); +} + +ImVec2 ImFontCalcTextSizeEx(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags) { if (!text_end) - text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + text_end = text_begin + ImStrlen(text_begin); // FIXME-OPT: Need to avoid this. + if (!text_end_display) + text_end_display = text_end; + ImFontBaked* baked = font->GetFontBaked(size); const float line_height = size; - const float scale = size / FontSize; + const float scale = line_height / baked->Size; ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; @@ -3942,13 +5507,14 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons const char* word_wrap_eol = NULL; const char* s = text_begin; - while (s < text_end) + while (s < text_end_display) { + // Word-wrapping if (word_wrap_enabled) { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + word_wrap_eol = ImFontCalcWordWrapPositionEx(font, size, s, text_end, wrap_width - line_width, flags); if (s >= word_wrap_eol) { @@ -3956,8 +5522,10 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_size.x = line_width; text_size.y += line_height; line_width = 0.0f; + s = ImTextCalcWordWrapNextLineStart(s, text_end, flags); // Wrapping skips upcoming blanks + if (flags & ImDrawTextFlags_StopOnNewLine) + break; word_wrap_eol = NULL; - s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; } } @@ -3970,20 +5538,24 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons else s += ImTextCharFromUtf8(&c, s, text_end); - if (c < 32) + if (c == '\n') { - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - continue; - } - if (c == '\r') - continue; + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (flags & ImDrawTextFlags_StopOnNewLine) + break; + continue; } + if (c == '\r') + continue; + + // Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);' + float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f; + if (char_width < 0.0f) + char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); + char_width *= scale; - const float char_width = ((int)c < IndexAdvanceX.Size ? IndexAdvanceX.Data[c] : FallbackAdvanceX) * scale; if (line_width + char_width >= max_width) { s = prev_s; @@ -3996,45 +5568,81 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (text_size.x < line_width) text_size.x = line_width; - if (line_width > 0 || text_size.y == 0.0f) + if (out_offset != NULL) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n text_size.y += line_height; - if (remaining) - *remaining = s; + if (out_remaining != NULL) + *out_remaining = s; return text_size; } +ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** out_remaining) +{ + return ImFontCalcTextSizeEx(this, size, max_width, wrap_width, text_begin, text_end, text_end, out_remaining, NULL, ImDrawTextFlags_None); +} + // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const +void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c, const ImVec4* cpu_fine_clip) { - const ImFontGlyph* glyph = FindGlyph(c); + ImFontBaked* baked = GetFontBaked(size); + const ImFontGlyph* glyph = baked->FindGlyph(c); if (!glyph || !glyph->Visible) return; if (glyph->Colored) col |= ~IM_COL32_A_MASK; - float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; + float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); + + float x1 = x + glyph->X0 * scale; + float x2 = x + glyph->X1 * scale; + if (cpu_fine_clip && (x1 > cpu_fine_clip->z || x2 < cpu_fine_clip->x)) + return; + float y1 = y + glyph->Y0 * scale; + float y2 = y + glyph->Y1 * scale; + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // Always CPU fine clip. Code extracted from RenderText(). + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. + if (cpu_fine_clip != NULL) + { + if (x1 < cpu_fine_clip->x) { u1 = u1 + (1.0f - (x2 - cpu_fine_clip->x) / (x2 - x1)) * (u2 - u1); x1 = cpu_fine_clip->x; } + if (y1 < cpu_fine_clip->y) { v1 = v1 + (1.0f - (y2 - cpu_fine_clip->y) / (y2 - y1)) * (v2 - v1); y1 = cpu_fine_clip->y; } + if (x2 > cpu_fine_clip->z) { u2 = u1 + ((cpu_fine_clip->z - x1) / (x2 - x1)) * (u2 - u1); x2 = cpu_fine_clip->z; } + if (y2 > cpu_fine_clip->w) { v2 = v1 + ((cpu_fine_clip->w - y1) / (y2 - y1)) * (v2 - v1); y2 = cpu_fine_clip->w; } + if (y1 >= y2) + return; + } draw_list->PrimReserve(6, 4); - draw_list->PrimRectUV(ImVec2(x + glyph->X0 * scale, y + glyph->Y0 * scale), ImVec2(x + glyph->X1 * scale, y + glyph->Y1 * scale), ImVec2(glyph->U0, glyph->V0), ImVec2(glyph->U1, glyph->V1), col); + draw_list->PrimRectUV(ImVec2(x1, y1), ImVec2(x2, y2), ImVec2(u1, v1), ImVec2(u2, v2), col); } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +// DO NOT CALL DIRECTLY THIS WILL CHANGE WILDLY IN 2025-2025. Use ImDrawList::AddText(). +void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, ImDrawTextFlags flags) { - if (!text_end) - text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. - // Align to be pixel perfect +begin: float x = IM_TRUNC(pos.x); float y = IM_TRUNC(pos.y); if (y > clip_rect.w) return; - const float start_x = x; - const float scale = size / FontSize; - const float line_height = FontSize * scale; + if (!text_end) + text_end = text_begin + ImStrlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. + + const float line_height = size; + ImFontBaked* baked = GetFontBaked(size); + + const float scale = size / baked->Size; + const float origin_x = x; const bool word_wrap_enabled = (wrap_width > 0.0f); // Fast-forward to first visible line @@ -4042,14 +5650,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (y + line_height < clip_rect.y) while (y + line_height < clip_rect.y && s < text_end) { - const char* line_end = (const char*)memchr(s, '\n', text_end - s); + const char* line_end = (const char*)ImMemchr(s, '\n', text_end - s); if (word_wrap_enabled) { - // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA(). - // If the specs for CalcWordWrapPositionA() were reworked to optionally return on \n we could combine both. + // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPosition(). + // If the specs for CalcWordWrapPosition() were reworked to optionally return on \n we could combine both. // However it is still better than nothing performing the fast-forward! - s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width); - s = CalcWordWrapNextLineStartA(s, text_end); + s = ImFontCalcWordWrapPositionEx(this, size, s, line_end ? line_end : text_end, wrap_width, flags); + s = ImTextCalcWordWrapNextLineStart(s, text_end, flags); } else { @@ -4066,7 +5674,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im float y_end = y; while (y_end < clip_rect.w && s_end < text_end) { - s_end = (const char*)memchr(s_end, '\n', text_end - s_end); + s_end = (const char*)ImMemchr(s_end, '\n', text_end - s_end); s_end = s_end ? s_end + 1 : text_end; y_end += line_height; } @@ -4083,6 +5691,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im ImDrawVert* vtx_write = draw_list->_VtxWritePtr; ImDrawIdx* idx_write = draw_list->_IdxWritePtr; unsigned int vtx_index = draw_list->_VtxCurrentIdx; + const int cmd_count = draw_list->CmdBuffer.Size; + const bool cpu_fine_clip = (flags & ImDrawTextFlags_CpuFineClip) != 0; const ImU32 col_untinted = col | ~IM_COL32_A_MASK; const char* word_wrap_eol = NULL; @@ -4093,16 +5703,16 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); + word_wrap_eol = ImFontCalcWordWrapPositionEx(this, size, s, text_end, wrap_width - (x - origin_x), flags); if (s >= word_wrap_eol) { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop word_wrap_eol = NULL; - s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks + s = ImTextCalcWordWrapNextLineStart(s, text_end, flags); // Wrapping skips upcoming blanks continue; } } @@ -4118,7 +5728,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { if (c == '\n') { - x = start_x; + x = origin_x; y += line_height; if (y > clip_rect.w) break; // break out of main loop @@ -4128,9 +5738,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im continue; } - const ImFontGlyph* glyph = FindGlyph((ImWchar)c); - if (glyph == NULL) - continue; + const ImFontGlyph* glyph = baked->FindGlyph((ImWchar)c); + //if (glyph == NULL) + // continue; float char_width = glyph->AdvanceX * scale; if (glyph->Visible) @@ -4198,6 +5808,20 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im x += char_width; } + // Edge case: calling RenderText() with unloaded glyphs triggering texture change. It doesn't happen via ImGui:: calls because CalcTextSize() is always used. + if (cmd_count != draw_list->CmdBuffer.Size) //-V547 + { + IM_ASSERT(draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount == 0); + draw_list->CmdBuffer.pop_back(); + draw_list->PrimUnreserve(idx_count_max, vtx_count_max); + draw_list->AddDrawCmd(); + //IMGUI_DEBUG_LOG("RenderText: cancel and retry to missing glyphs.\n"); // [DEBUG] + //draw_list->AddRectFilled(pos, pos + ImVec2(10, 10), IM_COL32(255, 0, 0, 255)); // [DEBUG] + goto begin; + //RenderText(draw_list, size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip); // FIXME-OPT: Would a 'goto begin' be better for code-gen? + //return; + } + // Give back unused vertices (clipped ones, blanks) ~ this is essentially a PrimUnreserve() action. draw_list->VtxBuffer.Size = (int)(vtx_write - draw_list->VtxBuffer.Data); // Same as calling shrink() draw_list->IdxBuffer.Size = (int)(idx_write - draw_list->IdxBuffer.Data); @@ -4256,8 +5880,9 @@ void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir d void ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col) { - // FIXME-OPT: This should be baked in font. - draw_list->AddCircleFilled(pos, draw_list->_Data->FontSize * 0.20f, col, 8); + // FIXME-OPT: This should be baked in font now that it's easier. + float font_size = draw_list->_Data->FontSize; + draw_list->AddCircleFilled(pos, font_size * 0.20f, col, (font_size < 22) ? 8 : (font_size < 40) ? 12 : 0); // Hardcode optimal/nice tessellation threshold } void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float sz) @@ -4540,102 +6165,189 @@ static unsigned int stb_decompress(unsigned char *output, const unsigned char *i // Copyright (c) 2004, 2005 Tristan Grimmer // MIT license (see License.txt in http://www.proggyfonts.net/index.php?menu=download) // Download and more information at http://www.proggyfonts.net or http://upperboundsinteractive.com/fonts.php +// If you want a similar font which may be better scaled, consider using ProggyVector from the same author! //----------------------------------------------------------------------------- + +#ifndef IMGUI_DISABLE_DEFAULT_FONT + // File: 'ProggyClean.ttf' (41208 bytes) -// Exported using misc/fonts/binary_to_compressed_c.cpp (with compression + base85 string encoding). -// The purpose of encoding as base85 instead of "0x00,0x01,..." style is only save on _source code_ size. -//----------------------------------------------------------------------------- -static const char proggy_clean_ttf_compressed_data_base85[11980 + 1] = - "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" - "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" - "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." - "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" - "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" - "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" - "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" - "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" - "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" - "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" - "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" - "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" - "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" - "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" - "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" - "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" - "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" - "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" - "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" - "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" - "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" - ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" - "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" - "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" - "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" - "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" - "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" - "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" - "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" - "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" - "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" - "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" - "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" - "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" - "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" - "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" - "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" - ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" - "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" - "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" - "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" - "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" - "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" - "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" - ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" - "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" - "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" - "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" - "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" - "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; - -static const char* GetDefaultCompressedFontDataTTFBase85() -{ - return proggy_clean_ttf_compressed_data_base85; +// Exported using binary_to_compressed_c.exe -u8 "ProggyClean.ttf" proggy_clean_ttf +static const unsigned int proggy_clean_ttf_compressed_size = 9583; +static const unsigned char proggy_clean_ttf_compressed_data[9583] = +{ + 87,188,0,0,0,0,0,0,0,0,160,248,0,4,0,0,55,0,1,0,0,0,12,0,128,0,3,0,64,79,83,47,50,136,235,116,144,0,0,1,72,130,21,44,78,99,109,97,112,2,18,35,117,0,0,3,160,130,19,36,82,99,118,116, + 32,130,23,130,2,33,4,252,130,4,56,2,103,108,121,102,18,175,137,86,0,0,7,4,0,0,146,128,104,101,97,100,215,145,102,211,130,27,32,204,130,3,33,54,104,130,16,39,8,66,1,195,0,0,1,4,130, + 15,59,36,104,109,116,120,138,0,126,128,0,0,1,152,0,0,2,6,108,111,99,97,140,115,176,216,0,0,5,130,30,41,2,4,109,97,120,112,1,174,0,218,130,31,32,40,130,16,44,32,110,97,109,101,37,89, + 187,150,0,0,153,132,130,19,44,158,112,111,115,116,166,172,131,239,0,0,155,36,130,51,44,210,112,114,101,112,105,2,1,18,0,0,4,244,130,47,32,8,132,203,46,1,0,0,60,85,233,213,95,15,60, + 245,0,3,8,0,131,0,34,183,103,119,130,63,43,0,0,189,146,166,215,0,0,254,128,3,128,131,111,130,241,33,2,0,133,0,32,1,130,65,38,192,254,64,0,0,3,128,131,16,130,5,32,1,131,7,138,3,33,2, + 0,130,17,36,1,1,0,144,0,130,121,130,23,38,2,0,8,0,64,0,10,130,9,32,118,130,9,130,6,32,0,130,59,33,1,144,131,200,35,2,188,2,138,130,16,32,143,133,7,37,1,197,0,50,2,0,131,0,33,4,9,131, + 5,145,3,43,65,108,116,115,0,64,0,0,32,172,8,0,131,0,35,5,0,1,128,131,77,131,3,33,3,128,191,1,33,1,128,130,184,35,0,0,128,0,130,3,131,11,32,1,130,7,33,0,128,131,1,32,1,136,9,32,0,132, + 15,135,5,32,1,131,13,135,27,144,35,32,1,149,25,131,21,32,0,130,0,32,128,132,103,130,35,132,39,32,0,136,45,136,97,133,17,130,5,33,0,0,136,19,34,0,128,1,133,13,133,5,32,128,130,15,132, + 131,32,3,130,5,32,3,132,27,144,71,32,0,133,27,130,29,130,31,136,29,131,63,131,3,65,63,5,132,5,132,205,130,9,33,0,0,131,9,137,119,32,3,132,19,138,243,130,55,32,1,132,35,135,19,131,201, + 136,11,132,143,137,13,130,41,32,0,131,3,144,35,33,128,0,135,1,131,223,131,3,141,17,134,13,136,63,134,15,136,53,143,15,130,96,33,0,3,131,4,130,3,34,28,0,1,130,5,34,0,0,76,130,17,131, + 9,36,28,0,4,0,48,130,17,46,8,0,8,0,2,0,0,0,127,0,255,32,172,255,255,130,9,34,0,0,129,132,9,130,102,33,223,213,134,53,132,22,33,1,6,132,6,64,4,215,32,129,165,216,39,177,0,1,141,184, + 1,255,133,134,45,33,198,0,193,1,8,190,244,1,28,1,158,2,20,2,136,2,252,3,20,3,88,3,156,3,222,4,20,4,50,4,80,4,98,4,162,5,22,5,102,5,188,6,18,6,116,6,214,7,56,7,126,7,236,8,78,8,108, + 8,150,8,208,9,16,9,74,9,136,10,22,10,128,11,4,11,86,11,200,12,46,12,130,12,234,13,94,13,164,13,234,14,80,14,150,15,40,15,176,16,18,16,116,16,224,17,82,17,182,18,4,18,110,18,196,19, + 76,19,172,19,246,20,88,20,174,20,234,21,64,21,128,21,166,21,184,22,18,22,126,22,198,23,52,23,142,23,224,24,86,24,186,24,238,25,54,25,150,25,212,26,72,26,156,26,240,27,92,27,200,28, + 4,28,76,28,150,28,234,29,42,29,146,29,210,30,64,30,142,30,224,31,36,31,118,31,166,31,166,32,16,130,1,52,46,32,138,32,178,32,200,33,20,33,116,33,152,33,238,34,98,34,134,35,12,130,1, + 33,128,35,131,1,60,152,35,176,35,216,36,0,36,74,36,104,36,144,36,174,37,6,37,96,37,130,37,248,37,248,38,88,38,170,130,1,8,190,216,39,64,39,154,40,10,40,104,40,168,41,14,41,32,41,184, + 41,248,42,54,42,96,42,96,43,2,43,42,43,94,43,172,43,230,44,32,44,52,44,154,45,40,45,92,45,120,45,170,45,232,46,38,46,166,47,38,47,182,47,244,48,94,48,200,49,62,49,180,50,30,50,158, + 51,30,51,130,51,238,52,92,52,206,53,58,53,134,53,212,54,38,54,114,54,230,55,118,55,216,56,58,56,166,57,18,57,116,57,174,58,46,58,154,59,6,59,124,59,232,60,58,60,150,61,34,61,134,61, + 236,62,86,62,198,63,42,63,154,64,18,64,106,64,208,65,54,65,162,66,8,66,64,66,122,66,184,66,240,67,98,67,204,68,42,68,138,68,238,69,88,69,182,69,226,70,84,70,180,71,20,71,122,71,218, + 72,84,72,198,73,64,0,36,70,21,8,8,77,3,0,7,0,11,0,15,0,19,0,23,0,27,0,31,0,35,0,39,0,43,0,47,0,51,0,55,0,59,0,63,0,67,0,71,0,75,0,79,0,83,0,87,0,91,0,95,0,99,0,103,0,107,0,111,0,115, + 0,119,0,123,0,127,0,131,0,135,0,139,0,143,0,0,17,53,51,21,49,150,3,32,5,130,23,32,33,130,3,211,7,151,115,32,128,133,0,37,252,128,128,2,128,128,190,5,133,74,32,4,133,6,206,5,42,0,7, + 1,128,0,0,2,0,4,0,0,65,139,13,37,0,1,53,51,21,7,146,3,32,3,130,19,32,1,141,133,32,3,141,14,131,13,38,255,0,128,128,0,6,1,130,84,35,2,128,4,128,140,91,132,89,32,51,65,143,6,139,7,33, + 1,0,130,57,32,254,130,3,32,128,132,4,32,4,131,14,138,89,35,0,0,24,0,130,0,33,3,128,144,171,66,55,33,148,115,65,187,19,32,5,130,151,143,155,163,39,32,1,136,182,32,253,134,178,132,7, + 132,200,145,17,32,3,65,48,17,165,17,39,0,0,21,0,128,255,128,3,65,175,17,65,3,27,132,253,131,217,139,201,155,233,155,27,131,67,131,31,130,241,33,255,0,131,181,137,232,132,15,132,4,138, + 247,34,255,0,128,179,238,32,0,130,0,32,20,65,239,48,33,0,19,67,235,10,32,51,65,203,14,65,215,11,32,7,154,27,135,39,32,33,130,35,33,128,128,130,231,32,253,132,231,32,128,132,232,34, + 128,128,254,133,13,136,8,32,253,65,186,5,130,36,130,42,176,234,133,231,34,128,0,0,66,215,44,33,0,1,68,235,6,68,211,19,32,49,68,239,14,139,207,139,47,66,13,7,32,51,130,47,33,1,0,130, + 207,35,128,128,1,0,131,222,131,5,130,212,130,6,131,212,32,0,130,10,133,220,130,233,130,226,32,254,133,255,178,233,39,3,1,128,3,0,2,0,4,68,15,7,68,99,12,130,89,130,104,33,128,4,133, + 93,130,10,38,0,0,11,1,0,255,0,68,63,16,70,39,9,66,215,8,32,7,68,77,6,68,175,14,32,29,68,195,6,132,7,35,2,0,128,255,131,91,132,4,65,178,5,141,111,67,129,23,165,135,140,107,142,135,33, + 21,5,69,71,6,131,7,33,1,0,140,104,132,142,130,4,137,247,140,30,68,255,12,39,11,0,128,0,128,3,0,3,69,171,15,67,251,7,65,15,8,66,249,11,65,229,7,67,211,7,66,13,7,35,1,128,128,254,133, + 93,32,254,131,145,132,4,132,18,32,2,151,128,130,23,34,0,0,9,154,131,65,207,8,68,107,15,68,51,7,32,7,70,59,7,135,121,130,82,32,128,151,111,41,0,0,4,0,128,255,0,1,128,1,137,239,33,0, + 37,70,145,10,65,77,10,65,212,14,37,0,0,0,5,0,128,66,109,5,70,123,10,33,0,19,72,33,18,133,237,70,209,11,33,0,2,130,113,137,119,136,115,33,1,0,133,43,130,5,34,0,0,10,69,135,6,70,219, + 13,66,155,7,65,9,12,66,157,11,66,9,11,32,7,130,141,132,252,66,151,9,137,9,66,15,30,36,0,20,0,128,0,130,218,71,11,42,68,51,8,65,141,7,73,19,15,69,47,23,143,39,66,81,7,32,1,66,55,6,34, + 1,128,128,68,25,5,69,32,6,137,6,136,25,32,254,131,42,32,3,66,88,26,148,26,32,0,130,0,32,14,164,231,70,225,12,66,233,7,67,133,19,71,203,15,130,161,32,255,130,155,32,254,139,127,134, + 12,164,174,33,0,15,164,159,33,59,0,65,125,20,66,25,7,32,5,68,191,6,66,29,7,144,165,65,105,9,35,128,128,255,0,137,2,133,182,164,169,33,128,128,197,171,130,155,68,235,7,32,21,70,77,19, + 66,21,10,68,97,8,66,30,5,66,4,43,34,0,17,0,71,19,41,65,253,20,71,25,23,65,91,15,65,115,7,34,2,128,128,66,9,8,130,169,33,1,0,66,212,13,132,28,72,201,43,35,0,0,0,18,66,27,38,76,231,5, + 68,157,20,135,157,32,7,68,185,13,65,129,28,66,20,5,32,253,66,210,11,65,128,49,133,61,32,0,65,135,6,74,111,37,72,149,12,66,203,19,65,147,19,68,93,7,68,85,8,76,4,5,33,255,0,133,129,34, + 254,0,128,68,69,8,181,197,34,0,0,12,65,135,32,65,123,20,69,183,27,133,156,66,50,5,72,87,10,67,137,32,33,0,19,160,139,78,251,13,68,55,20,67,119,19,65,91,36,69,177,15,32,254,143,16,65, + 98,53,32,128,130,0,32,0,66,43,54,70,141,23,66,23,15,131,39,69,47,11,131,15,70,129,19,74,161,9,36,128,255,0,128,254,130,153,65,148,32,67,41,9,34,0,0,4,79,15,5,73,99,10,71,203,8,32,3, + 72,123,6,72,43,8,32,2,133,56,131,99,130,9,34,0,0,6,72,175,5,73,159,14,144,63,135,197,132,189,133,66,33,255,0,73,6,7,70,137,12,35,0,0,0,10,130,3,73,243,25,67,113,12,65,73,7,69,161,7, + 138,7,37,21,2,0,128,128,254,134,3,73,116,27,33,128,128,130,111,39,12,0,128,1,0,3,128,2,72,219,21,35,43,0,47,0,67,47,20,130,111,33,21,1,68,167,13,81,147,8,133,230,32,128,77,73,6,32, + 128,131,142,134,18,130,6,32,255,75,18,12,131,243,37,128,0,128,3,128,3,74,231,21,135,123,32,29,134,107,135,7,32,21,74,117,7,135,7,134,96,135,246,74,103,23,132,242,33,0,10,67,151,28, + 67,133,20,66,141,11,131,11,32,3,77,71,6,32,128,130,113,32,1,81,4,6,134,218,66,130,24,131,31,34,0,26,0,130,0,77,255,44,83,15,11,148,155,68,13,7,32,49,78,231,18,79,7,11,73,243,11,32, + 33,65,187,10,130,63,65,87,8,73,239,19,35,0,128,1,0,131,226,32,252,65,100,6,32,128,139,8,33,1,0,130,21,32,253,72,155,44,73,255,20,32,128,71,67,8,81,243,39,67,15,20,74,191,23,68,121, + 27,32,1,66,150,6,32,254,79,19,11,131,214,32,128,130,215,37,2,0,128,253,0,128,136,5,65,220,24,147,212,130,210,33,0,24,72,219,42,84,255,13,67,119,16,69,245,19,72,225,19,65,3,15,69,93, + 19,131,55,132,178,71,115,14,81,228,6,142,245,33,253,0,132,43,172,252,65,16,11,75,219,8,65,219,31,66,223,24,75,223,10,33,29,1,80,243,10,66,175,8,131,110,134,203,133,172,130,16,70,30, + 7,164,183,130,163,32,20,65,171,48,65,163,36,65,143,23,65,151,19,65,147,13,65,134,17,133,17,130,216,67,114,5,164,217,65,137,12,72,147,48,79,71,19,74,169,22,80,251,8,65,173,7,66,157, + 15,74,173,15,32,254,65,170,8,71,186,45,72,131,6,77,143,40,187,195,152,179,65,123,38,68,215,57,68,179,15,65,85,7,69,187,14,32,21,66,95,15,67,19,25,32,1,83,223,6,32,2,76,240,7,77,166, + 43,65,8,5,130,206,32,0,67,39,54,143,167,66,255,19,82,193,11,151,47,85,171,5,67,27,17,132,160,69,172,11,69,184,56,66,95,6,33,12,1,130,237,32,2,68,179,27,68,175,16,80,135,15,72,55,7, + 71,87,12,73,3,12,132,12,66,75,32,76,215,5,169,139,147,135,148,139,81,12,12,81,185,36,75,251,7,65,23,27,76,215,9,87,165,12,65,209,15,72,157,7,65,245,31,32,128,71,128,6,32,1,82,125,5, + 34,0,128,254,131,169,32,254,131,187,71,180,9,132,27,32,2,88,129,44,32,0,78,47,40,65,79,23,79,171,14,32,21,71,87,8,72,15,14,65,224,33,130,139,74,27,62,93,23,7,68,31,7,75,27,7,139,15, + 74,3,7,74,23,27,65,165,11,65,177,15,67,123,5,32,1,130,221,32,252,71,96,5,74,12,12,133,244,130,25,34,1,0,128,130,2,139,8,93,26,8,65,9,32,65,57,14,140,14,32,0,73,79,67,68,119,11,135, + 11,32,51,90,75,14,139,247,65,43,7,131,19,139,11,69,159,11,65,247,6,36,1,128,128,253,0,90,71,9,33,1,0,132,14,32,128,89,93,14,69,133,6,130,44,131,30,131,6,65,20,56,33,0,16,72,179,40, + 75,47,12,65,215,19,74,95,19,65,43,11,131,168,67,110,5,75,23,17,69,106,6,75,65,5,71,204,43,32,0,80,75,47,71,203,15,159,181,68,91,11,67,197,7,73,101,13,68,85,6,33,128,128,130,214,130, + 25,32,254,74,236,48,130,194,37,0,18,0,128,255,128,77,215,40,65,139,64,32,51,80,159,10,65,147,39,130,219,84,212,43,130,46,75,19,97,74,33,11,65,201,23,65,173,31,33,1,0,79,133,6,66,150, + 5,67,75,48,85,187,6,70,207,37,32,71,87,221,13,73,163,14,80,167,15,132,15,83,193,19,82,209,8,78,99,9,72,190,11,77,110,49,89,63,5,80,91,35,99,63,32,70,235,23,81,99,10,69,148,10,65,110, + 36,32,0,65,99,47,95,219,11,68,171,51,66,87,7,72,57,7,74,45,17,143,17,65,114,50,33,14,0,65,111,40,159,195,98,135,15,35,7,53,51,21,100,78,9,95,146,16,32,254,82,114,6,32,128,67,208,37, + 130,166,99,79,58,32,17,96,99,14,72,31,19,72,87,31,82,155,7,67,47,14,32,21,131,75,134,231,72,51,17,72,78,8,133,8,80,133,6,33,253,128,88,37,9,66,124,36,72,65,12,134,12,71,55,43,66,139, + 27,85,135,10,91,33,12,65,35,11,66,131,11,71,32,8,90,127,6,130,244,71,76,11,168,207,33,0,12,66,123,32,32,0,65,183,15,68,135,11,66,111,7,67,235,11,66,111,15,32,254,97,66,12,160,154,67, + 227,52,80,33,15,87,249,15,93,45,31,75,111,12,93,45,11,77,99,9,160,184,81,31,12,32,15,98,135,30,104,175,7,77,249,36,69,73,15,78,5,12,32,254,66,151,19,34,128,128,4,87,32,12,149,35,133, + 21,96,151,31,32,19,72,35,5,98,173,15,143,15,32,21,143,99,158,129,33,0,0,65,35,52,65,11,15,147,15,98,75,11,33,1,0,143,151,132,15,32,254,99,200,37,132,43,130,4,39,0,10,0,128,1,128,3, + 0,104,151,14,97,187,20,69,131,15,67,195,11,87,227,7,33,128,128,132,128,33,254,0,68,131,9,65,46,26,42,0,0,0,7,0,0,255,128,3,128,0,88,223,15,33,0,21,89,61,22,66,209,12,65,2,12,37,0,2, + 1,0,3,128,101,83,8,36,0,1,53,51,29,130,3,34,21,1,0,66,53,8,32,0,68,215,6,100,55,25,107,111,9,66,193,11,72,167,8,73,143,31,139,31,33,1,0,131,158,32,254,132,5,33,253,128,65,16,9,133, + 17,89,130,25,141,212,33,0,0,93,39,8,90,131,25,93,39,14,66,217,6,106,179,8,159,181,71,125,15,139,47,138,141,87,11,14,76,23,14,65,231,26,140,209,66,122,8,81,179,5,101,195,26,32,47,74, + 75,13,69,159,11,83,235,11,67,21,16,136,167,131,106,130,165,130,15,32,128,101,90,24,134,142,32,0,65,103,51,108,23,11,101,231,15,75,173,23,74,237,23,66,15,6,66,46,17,66,58,17,65,105, + 49,66,247,55,71,179,12,70,139,15,86,229,7,84,167,15,32,1,95,72,12,89,49,6,33,128,128,65,136,38,66,30,9,32,0,100,239,7,66,247,29,70,105,20,65,141,19,69,81,15,130,144,32,128,83,41,5, + 32,255,131,177,68,185,5,133,126,65,97,37,32,0,130,0,33,21,0,130,55,66,195,28,67,155,13,34,79,0,83,66,213,13,73,241,19,66,59,19,65,125,11,135,201,66,249,16,32,128,66,44,11,66,56,17, + 68,143,8,68,124,38,67,183,12,96,211,9,65,143,29,112,171,5,32,0,68,131,63,34,33,53,51,71,121,11,32,254,98,251,16,32,253,74,231,10,65,175,37,133,206,37,0,0,8,1,0,0,107,123,11,113,115, + 9,33,0,1,130,117,131,3,73,103,7,66,51,18,66,44,5,133,75,70,88,5,32,254,65,39,12,68,80,9,34,12,0,128,107,179,28,68,223,6,155,111,86,147,15,32,2,131,82,141,110,33,254,0,130,15,32,4,103, + 184,15,141,35,87,176,5,83,11,5,71,235,23,114,107,11,65,189,16,70,33,15,86,153,31,135,126,86,145,30,65,183,41,32,0,130,0,32,10,65,183,24,34,35,0,39,67,85,9,65,179,15,143,15,33,1,0,65, + 28,17,157,136,130,123,32,20,130,3,32,0,97,135,24,115,167,19,80,71,12,32,51,110,163,14,78,35,19,131,19,155,23,77,229,8,78,9,17,151,17,67,231,46,94,135,8,73,31,31,93,215,56,82,171,25, + 72,77,8,162,179,169,167,99,131,11,69,85,19,66,215,15,76,129,13,68,115,22,72,79,35,67,113,5,34,0,0,19,70,31,46,65,89,52,73,223,15,85,199,33,95,33,8,132,203,73,29,32,67,48,16,177,215, + 101,13,15,65,141,43,69,141,15,75,89,5,70,0,11,70,235,21,178,215,36,10,0,128,0,0,71,207,24,33,0,19,100,67,6,80,215,11,66,67,7,80,43,12,71,106,7,80,192,5,65,63,5,66,217,26,33,0,13,156, + 119,68,95,5,72,233,12,134,129,85,81,11,76,165,20,65,43,8,73,136,8,75,10,31,38,128,128,0,0,0,13,1,130,4,32,3,106,235,29,114,179,12,66,131,23,32,7,77,133,6,67,89,12,131,139,116,60,9, + 89,15,37,32,0,74,15,7,103,11,22,65,35,5,33,55,0,93,81,28,67,239,23,78,85,5,107,93,14,66,84,17,65,193,26,74,183,10,66,67,34,143,135,79,91,15,32,7,117,111,8,75,56,9,84,212,9,154,134, + 32,0,130,0,32,18,130,3,70,171,41,83,7,16,70,131,19,84,191,15,84,175,19,84,167,30,84,158,12,154,193,68,107,15,33,0,0,65,79,42,65,71,7,73,55,7,118,191,16,83,180,9,32,255,76,166,9,154, + 141,32,0,130,0,69,195,52,65,225,15,151,15,75,215,31,80,56,10,68,240,17,100,32,9,70,147,39,65,93,12,71,71,41,92,85,15,84,135,23,78,35,15,110,27,10,84,125,8,107,115,29,136,160,38,0,0, + 14,0,128,255,0,82,155,24,67,239,8,119,255,11,69,131,11,77,29,6,112,31,8,134,27,105,203,8,32,2,75,51,11,75,195,12,74,13,29,136,161,37,128,0,0,0,11,1,130,163,82,115,8,125,191,17,69,35, + 12,74,137,15,143,15,32,1,65,157,12,136,12,161,142,65,43,40,65,199,6,65,19,24,102,185,11,76,123,11,99,6,12,135,12,32,254,130,8,161,155,101,23,9,39,8,0,0,1,128,3,128,2,78,63,17,72,245, + 12,67,41,11,90,167,9,32,128,97,49,9,32,128,109,51,14,132,97,81,191,8,130,97,125,99,12,121,35,9,127,75,15,71,79,12,81,151,23,87,97,7,70,223,15,80,245,16,105,97,15,32,254,113,17,6,32, + 128,130,8,105,105,8,76,122,18,65,243,21,74,63,7,38,4,1,0,255,0,2,0,119,247,28,133,65,32,255,141,91,35,0,0,0,16,67,63,36,34,59,0,63,77,59,9,119,147,11,143,241,66,173,15,66,31,11,67, + 75,8,81,74,16,32,128,131,255,87,181,42,127,43,5,34,255,128,2,120,235,11,37,19,0,23,0,0,37,109,191,14,118,219,7,127,43,14,65,79,14,35,0,0,0,3,73,91,5,130,5,38,3,0,7,0,11,0,0,70,205, + 11,88,221,12,32,0,73,135,7,87,15,22,73,135,10,79,153,15,97,71,19,65,49,11,32,1,131,104,121,235,11,80,65,11,142,179,144,14,81,123,46,32,1,88,217,5,112,5,8,65,201,15,83,29,15,122,147, + 11,135,179,142,175,143,185,67,247,39,66,199,7,35,5,0,128,3,69,203,15,123,163,12,67,127,7,130,119,71,153,10,141,102,70,175,8,32,128,121,235,30,136,89,100,191,11,116,195,11,111,235,15, + 72,39,7,32,2,97,43,5,132,5,94,67,8,131,8,125,253,10,32,3,65,158,16,146,16,130,170,40,0,21,0,128,0,0,3,128,5,88,219,15,24,64,159,32,135,141,65,167,15,68,163,10,97,73,49,32,255,82,58, + 7,93,80,8,97,81,16,24,67,87,52,34,0,0,5,130,231,33,128,2,80,51,13,65,129,8,113,61,6,132,175,65,219,5,130,136,77,152,17,32,0,95,131,61,70,215,6,33,21,51,90,53,10,78,97,23,105,77,31, + 65,117,7,139,75,24,68,195,9,24,64,22,9,33,0,128,130,11,33,128,128,66,25,5,121,38,5,134,5,134,45,66,40,36,66,59,18,34,128,0,0,66,59,81,135,245,123,103,19,120,159,19,77,175,12,33,255, + 0,87,29,10,94,70,21,66,59,54,39,3,1,128,3,0,2,128,4,24,65,7,15,66,47,7,72,98,12,37,0,0,0,3,1,0,24,65,55,21,131,195,32,1,67,178,6,33,4,0,77,141,8,32,6,131,47,74,67,16,24,69,3,20,24, + 65,251,7,133,234,130,229,94,108,17,35,0,0,6,0,141,175,86,59,5,162,79,85,166,8,70,112,13,32,13,24,64,67,26,24,71,255,7,123,211,12,80,121,11,69,215,15,66,217,11,69,71,10,131,113,132, + 126,119,90,9,66,117,19,132,19,32,0,130,0,24,64,47,59,33,7,0,73,227,5,68,243,15,85,13,12,76,37,22,74,254,15,130,138,33,0,4,65,111,6,137,79,65,107,16,32,1,77,200,6,34,128,128,3,75,154, + 12,37,0,16,0,0,2,0,104,115,36,140,157,68,67,19,68,51,15,106,243,15,134,120,70,37,10,68,27,10,140,152,65,121,24,32,128,94,155,7,67,11,8,24,74,11,25,65,3,12,83,89,18,82,21,37,67,200, + 5,130,144,24,64,172,12,33,4,0,134,162,74,80,14,145,184,32,0,130,0,69,251,20,32,19,81,243,5,82,143,8,33,5,53,89,203,5,133,112,79,109,15,33,0,21,130,71,80,175,41,36,75,0,79,0,83,121, + 117,9,87,89,27,66,103,11,70,13,15,75,191,11,135,67,87,97,20,109,203,5,69,246,8,108,171,5,78,195,38,65,51,13,107,203,11,77,3,17,24,75,239,17,65,229,28,79,129,39,130,175,32,128,123,253, + 7,132,142,24,65,51,15,65,239,41,36,128,128,0,0,13,65,171,5,66,163,28,136,183,118,137,11,80,255,15,67,65,7,74,111,8,32,0,130,157,32,253,24,76,35,10,103,212,5,81,175,9,69,141,7,66,150, + 29,131,158,24,75,199,28,124,185,7,76,205,15,68,124,14,32,3,123,139,16,130,16,33,128,128,108,199,6,33,0,3,65,191,35,107,11,6,73,197,11,24,70,121,15,83,247,15,24,70,173,23,69,205,14, + 32,253,131,140,32,254,136,4,94,198,9,32,3,78,4,13,66,127,13,143,13,32,0,130,0,33,16,0,24,69,59,39,109,147,12,76,253,19,24,69,207,15,69,229,15,130,195,71,90,10,139,10,130,152,73,43, + 40,91,139,10,65,131,37,35,75,0,79,0,84,227,12,143,151,68,25,15,80,9,23,95,169,11,34,128,2,128,112,186,5,130,6,83,161,19,76,50,6,130,37,65,145,44,110,83,5,32,16,67,99,6,71,67,15,76, + 55,17,140,215,67,97,23,76,69,15,77,237,11,104,211,23,77,238,11,65,154,43,33,0,10,83,15,28,83,13,20,67,145,19,67,141,14,97,149,21,68,9,15,86,251,5,66,207,5,66,27,37,82,1,23,127,71,12, + 94,235,10,110,175,24,98,243,15,132,154,132,4,24,66,69,10,32,4,67,156,43,130,198,35,2,1,0,4,75,27,9,69,85,9,95,240,7,32,128,130,35,32,28,66,43,40,24,82,63,23,83,123,12,72,231,15,127, + 59,23,116,23,19,117,71,7,24,77,99,15,67,111,15,71,101,8,36,2,128,128,252,128,127,60,11,32,1,132,16,130,18,141,24,67,107,9,32,3,68,194,15,175,15,38,0,11,0,128,1,128,2,80,63,25,32,0, + 24,65,73,11,69,185,15,83,243,16,32,0,24,81,165,8,130,86,77,35,6,155,163,88,203,5,24,66,195,30,70,19,19,24,80,133,15,32,1,75,211,8,32,254,108,133,8,79,87,20,65,32,9,41,0,0,7,0,128,0, + 0,2,128,2,68,87,15,66,1,16,92,201,16,24,76,24,17,133,17,34,128,0,30,66,127,64,34,115,0,119,73,205,9,66,43,11,109,143,15,24,79,203,11,90,143,15,131,15,155,31,65,185,15,86,87,11,35,128, + 128,253,0,69,7,6,130,213,33,1,0,119,178,15,142,17,66,141,74,83,28,6,36,7,0,0,4,128,82,39,18,76,149,12,67,69,21,32,128,79,118,15,32,0,130,0,32,8,131,206,32,2,79,83,9,100,223,14,102, + 113,23,115,115,7,24,65,231,12,130,162,32,4,68,182,19,130,102,93,143,8,69,107,29,24,77,255,12,143,197,72,51,7,76,195,15,132,139,85,49,15,130,152,131,18,71,81,23,70,14,11,36,0,10,0,128, + 2,69,59,9,89,151,15,66,241,11,76,165,12,71,43,15,75,49,13,65,12,23,132,37,32,0,179,115,130,231,95,181,16,132,77,32,254,67,224,8,65,126,20,79,171,8,32,2,89,81,5,75,143,6,80,41,8,34, + 2,0,128,24,81,72,9,32,0,130,0,35,17,0,0,255,77,99,39,95,65,36,67,109,15,24,69,93,11,77,239,5,95,77,23,35,128,1,0,128,24,86,7,8,132,167,32,2,69,198,41,130,202,33,0,26,120,75,44,24,89, + 51,15,71,243,12,70,239,11,24,84,3,11,66,7,11,71,255,10,32,21,69,155,35,88,151,12,32,128,74,38,10,65,210,8,74,251,5,65,226,5,75,201,13,32,3,65,9,41,146,41,40,0,0,0,9,1,0,1,0,2,91,99, + 19,32,35,106,119,13,70,219,15,83,239,12,137,154,32,2,67,252,19,36,128,0,0,4,1,130,196,32,2,130,8,91,107,8,32,0,135,81,24,73,211,8,132,161,73,164,13,36,0,8,0,128,2,105,123,26,139,67, + 76,99,15,34,1,0,128,135,76,83,156,20,92,104,8,67,251,30,24,86,47,27,123,207,12,24,86,7,15,71,227,8,32,4,65,20,20,131,127,32,0,130,123,32,0,71,223,26,32,19,90,195,22,71,223,15,84,200, + 6,32,128,133,241,24,84,149,9,67,41,25,36,0,0,0,22,0,88,111,49,32,87,66,21,5,77,3,27,123,75,7,71,143,19,135,183,71,183,19,130,171,74,252,5,131,5,89,87,17,32,1,132,18,130,232,68,11,10, + 33,1,128,70,208,16,66,230,18,147,18,130,254,223,255,75,27,23,65,59,15,135,39,155,255,34,128,128,254,104,92,8,33,0,128,65,32,11,65,1,58,33,26,0,130,0,72,71,18,78,55,17,76,11,19,86,101, + 12,75,223,11,89,15,11,24,76,87,15,75,235,15,131,15,72,95,7,85,71,11,72,115,11,73,64,6,34,1,128,128,66,215,9,34,128,254,128,134,14,33,128,255,67,102,5,32,0,130,16,70,38,11,66,26,57, + 88,11,8,24,76,215,34,78,139,7,95,245,7,32,7,24,73,75,23,32,128,131,167,130,170,101,158,9,82,49,22,118,139,6,32,18,67,155,44,116,187,9,108,55,14,80,155,23,66,131,15,93,77,10,131,168, + 32,128,73,211,12,24,75,187,22,32,4,96,71,20,67,108,19,132,19,120,207,8,32,5,76,79,15,66,111,21,66,95,8,32,3,190,211,111,3,8,211,212,32,20,65,167,44,34,75,0,79,97,59,13,32,33,112,63, + 10,65,147,19,69,39,19,143,39,24,66,71,9,130,224,65,185,43,94,176,12,65,183,24,71,38,8,24,72,167,7,65,191,38,136,235,24,96,167,12,65,203,62,115,131,13,65,208,42,175,235,67,127,6,32, + 4,76,171,29,114,187,5,32,71,65,211,5,65,203,68,72,51,8,164,219,32,0,172,214,71,239,58,78,3,27,66,143,15,77,19,15,147,31,35,33,53,51,21,66,183,10,173,245,66,170,30,150,30,34,0,0,23, + 80,123,54,76,1,16,73,125,15,82,245,11,167,253,24,76,85,12,70,184,5,32,254,131,185,37,254,0,128,1,0,128,133,16,117,158,18,92,27,38,65,3,17,130,251,35,17,0,128,254,24,69,83,39,140,243, + 121,73,19,109,167,7,81,41,15,24,95,175,12,102,227,15,121,96,11,24,95,189,7,32,3,145,171,154,17,24,77,47,9,33,0,5,70,71,37,68,135,7,32,29,117,171,11,69,87,15,24,79,97,19,24,79,149,23, + 131,59,32,1,75,235,5,72,115,11,72,143,7,132,188,71,27,46,131,51,32,0,69,95,6,175,215,32,21,131,167,81,15,19,151,191,151,23,131,215,71,43,5,32,254,24,79,164,24,74,109,8,77,166,13,65, + 176,26,88,162,5,98,159,6,171,219,120,247,6,79,29,8,99,169,10,103,59,19,65,209,35,131,35,91,25,19,112,94,15,83,36,8,173,229,33,20,0,88,75,43,71,31,12,65,191,71,33,1,0,130,203,32,254, + 131,4,68,66,7,67,130,6,104,61,13,173,215,38,13,1,0,0,0,2,128,67,111,28,74,129,16,104,35,19,79,161,16,87,14,7,138,143,132,10,67,62,36,114,115,5,162,151,67,33,16,108,181,15,143,151,67, + 5,5,24,100,242,15,170,153,34,0,0,14,65,51,34,32,55,79,75,9,32,51,74,7,10,65,57,38,132,142,32,254,72,0,14,139,163,32,128,80,254,8,67,158,21,65,63,7,32,4,72,227,27,95,155,12,67,119,19, + 124,91,24,149,154,72,177,34,97,223,8,155,151,24,108,227,15,88,147,16,72,117,19,68,35,11,92,253,15,70,199,15,24,87,209,17,32,2,87,233,7,32,1,24,88,195,10,119,24,8,32,3,81,227,24,65, + 125,21,35,128,128,0,25,76,59,48,24,90,187,9,97,235,12,66,61,11,91,105,19,24,79,141,11,24,79,117,15,24,79,129,27,90,53,13,130,13,32,253,131,228,24,79,133,40,69,70,8,66,137,31,65,33, + 19,96,107,8,68,119,29,66,7,5,68,125,16,65,253,19,65,241,27,24,90,179,13,24,79,143,18,33,128,128,130,246,32,254,130,168,68,154,36,77,51,9,97,47,5,167,195,32,21,131,183,78,239,27,155, + 195,78,231,14,201,196,77,11,6,32,5,73,111,37,97,247,12,77,19,31,155,207,78,215,19,162,212,69,17,14,66,91,19,80,143,57,78,203,39,159,215,32,128,93,134,8,24,80,109,24,66,113,15,169,215, + 66,115,6,32,4,69,63,33,32,0,101,113,7,86,227,35,143,211,36,49,53,51,21,1,77,185,14,65,159,28,69,251,34,67,56,8,33,9,0,24,107,175,25,90,111,12,110,251,11,119,189,24,119,187,34,87,15, + 9,32,4,66,231,37,90,39,7,66,239,8,84,219,15,69,105,23,24,85,27,27,87,31,11,33,1,128,76,94,6,32,1,85,241,7,33,128,128,106,48,10,33,128,128,69,136,11,133,13,24,79,116,49,84,236,8,24, + 91,87,9,32,5,165,255,69,115,12,66,27,15,159,15,24,72,247,12,74,178,5,24,80,64,15,33,0,128,143,17,77,89,51,130,214,24,81,43,7,170,215,74,49,8,159,199,143,31,139,215,69,143,5,32,254, + 24,81,50,35,181,217,84,123,70,143,195,159,15,65,187,16,66,123,7,65,175,15,65,193,29,68,207,39,79,27,5,70,131,6,32,4,68,211,33,33,67,0,83,143,14,159,207,143,31,140,223,33,0,128,24,80, + 82,14,24,93,16,23,32,253,65,195,5,68,227,40,133,214,107,31,7,32,5,67,115,27,87,9,8,107,31,43,66,125,6,32,0,103,177,23,131,127,72,203,36,32,0,110,103,8,155,163,73,135,6,32,19,24,112, + 99,10,65,71,11,73,143,19,143,31,126,195,5,24,85,21,9,24,76,47,14,32,254,24,93,77,36,68,207,11,39,25,0,0,255,128,3,128,4,66,51,37,95,247,13,82,255,24,76,39,19,147,221,66,85,27,24,118, + 7,8,24,74,249,12,76,74,8,91,234,8,67,80,17,131,222,33,253,0,121,30,44,73,0,16,69,15,6,32,0,65,23,38,69,231,12,65,179,6,98,131,16,86,31,27,24,108,157,14,80,160,8,24,65,46,17,33,4,0, + 96,2,18,144,191,65,226,8,68,19,5,171,199,80,9,15,180,199,67,89,5,32,255,24,79,173,28,174,201,24,79,179,50,32,1,24,122,5,10,82,61,10,180,209,83,19,8,32,128,24,80,129,27,111,248,43,131, + 71,24,115,103,8,67,127,41,78,213,24,100,247,19,66,115,39,75,107,5,32,254,165,219,78,170,40,24,112,163,49,32,1,97,203,6,65,173,64,32,0,83,54,7,133,217,88,37,12,32,254,131,28,33,128, + 3,67,71,44,84,183,6,32,5,69,223,33,96,7,7,123,137,16,192,211,24,112,14,9,32,255,67,88,29,68,14,10,84,197,38,33,0,22,116,47,50,32,87,106,99,9,116,49,15,89,225,15,97,231,23,70,41,19, + 82,85,8,93,167,6,32,253,132,236,108,190,7,89,251,5,116,49,58,33,128,128,131,234,32,15,24,74,67,38,70,227,24,24,83,45,23,89,219,12,70,187,12,89,216,19,32,2,69,185,24,141,24,70,143,66, + 24,82,119,56,78,24,10,32,253,133,149,132,6,24,106,233,7,69,198,48,178,203,81,243,12,68,211,15,106,255,23,66,91,15,69,193,7,100,39,10,24,83,72,16,176,204,33,19,0,88,207,45,68,21,12, + 68,17,10,65,157,53,68,17,6,32,254,92,67,10,65,161,25,69,182,43,24,118,91,47,69,183,18,181,209,111,253,12,89,159,8,66,112,12,69,184,45,35,0,0,0,9,24,80,227,26,73,185,16,118,195,15,131, + 15,33,1,0,65,59,15,66,39,27,160,111,66,205,12,148,111,143,110,33,128,128,156,112,24,81,199,8,75,199,23,66,117,20,155,121,32,254,68,126,12,72,213,29,134,239,149,123,89,27,16,148,117, + 65,245,8,24,71,159,14,141,134,134,28,73,51,55,109,77,15,105,131,11,68,67,11,76,169,27,107,209,12,102,174,8,32,128,72,100,18,116,163,56,79,203,11,75,183,44,85,119,19,71,119,23,151,227, + 32,1,93,27,8,65,122,5,77,102,8,110,120,20,66,23,8,66,175,17,66,63,12,133,12,79,35,8,74,235,33,67,149,16,69,243,15,78,57,15,69,235,16,67,177,7,151,192,130,23,67,84,29,141,192,174,187, + 77,67,15,69,11,12,159,187,77,59,10,199,189,24,70,235,50,96,83,19,66,53,23,105,65,19,77,47,12,163,199,66,67,37,78,207,50,67,23,23,174,205,67,228,6,71,107,13,67,22,14,66,85,11,83,187, + 38,124,47,49,95,7,19,66,83,23,67,23,19,24,96,78,17,80,101,16,71,98,40,33,0,7,88,131,22,24,89,245,12,84,45,12,102,213,5,123,12,9,32,2,126,21,14,43,255,0,128,128,0,0,20,0,128,255,128, + 3,126,19,39,32,75,106,51,7,113,129,15,24,110,135,19,126,47,15,115,117,11,69,47,11,32,2,109,76,9,102,109,9,32,128,75,2,10,130,21,32,254,69,47,6,32,3,94,217,47,32,0,65,247,10,69,15,46, + 65,235,31,65,243,15,101,139,10,66,174,14,65,247,16,72,102,28,69,17,14,84,243,9,165,191,88,47,48,66,53,12,32,128,71,108,6,203,193,32,17,75,187,42,73,65,16,65,133,52,114,123,9,167,199, + 69,21,37,86,127,44,75,171,11,180,197,78,213,12,148,200,81,97,46,24,95,243,9,32,4,66,75,33,113,103,9,87,243,36,143,225,24,84,27,31,90,145,8,148,216,67,49,5,24,84,34,14,75,155,27,67, + 52,13,140,13,36,0,20,0,128,255,24,135,99,46,88,59,43,155,249,80,165,7,136,144,71,161,23,32,253,132,33,32,254,88,87,44,136,84,35,128,0,0,21,81,103,5,94,47,44,76,51,12,143,197,151,15, + 65,215,31,24,64,77,13,65,220,20,65,214,14,71,4,40,65,213,13,32,0,130,0,35,21,1,2,0,135,0,34,36,0,72,134,10,36,1,0,26,0,130,134,11,36,2,0,14,0,108,134,11,32,3,138,23,32,4,138,11,34, + 5,0,20,134,33,34,0,0,6,132,23,32,1,134,15,32,18,130,25,133,11,37,1,0,13,0,49,0,133,11,36,2,0,7,0,38,134,11,36,3,0,17,0,45,134,11,32,4,138,35,36,5,0,10,0,62,134,23,32,6,132,23,36,3, + 0,1,4,9,130,87,131,167,133,11,133,167,133,11,133,167,133,11,37,3,0,34,0,122,0,133,11,133,167,133,11,133,167,133,11,133,167,34,50,0,48,130,1,34,52,0,47,134,5,8,49,49,0,53,98,121,32, + 84,114,105,115,116,97,110,32,71,114,105,109,109,101,114,82,101,103,117,108,97,114,84,84,88,32,80,114,111,103,103,121,67,108,101,97,110,84,84,50,48,48,52,47,130,2,53,49,53,0,98,0,121, + 0,32,0,84,0,114,0,105,0,115,0,116,0,97,0,110,130,15,32,71,132,15,36,109,0,109,0,101,130,9,32,82,130,5,36,103,0,117,0,108,130,29,32,114,130,43,34,84,0,88,130,35,32,80,130,25,34,111, + 0,103,130,1,34,121,0,67,130,27,32,101,132,59,32,84,130,31,33,0,0,65,155,9,34,20,0,0,65,11,6,130,8,135,2,33,1,1,130,9,8,120,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,11,1,12,1,13,1,14, + 1,15,1,16,1,17,1,18,1,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,1,32,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0,10,0,11,0,12,0,13,0,14,0,15,0,16,0,17,0,18,0,19,0,20,0,21,0, + 22,0,23,0,24,0,25,0,26,0,27,0,28,0,29,0,30,0,31,130,187,8,66,33,0,34,0,35,0,36,0,37,0,38,0,39,0,40,0,41,0,42,0,43,0,44,0,45,0,46,0,47,0,48,0,49,0,50,0,51,0,52,0,53,0,54,0,55,0,56,0, + 57,0,58,0,59,0,60,0,61,0,62,0,63,0,64,0,65,0,66,130,243,9,75,68,0,69,0,70,0,71,0,72,0,73,0,74,0,75,0,76,0,77,0,78,0,79,0,80,0,81,0,82,0,83,0,84,0,85,0,86,0,87,0,88,0,89,0,90,0,91,0, + 92,0,93,0,94,0,95,0,96,0,97,1,33,1,34,1,35,1,36,1,37,1,38,1,39,1,40,1,41,1,42,1,43,1,44,1,45,1,46,1,47,1,48,1,49,1,50,1,51,1,52,1,53,1,54,1,55,1,56,1,57,1,58,1,59,1,60,1,61,1,62,1, + 63,1,64,1,65,0,172,0,163,0,132,0,133,0,189,0,150,0,232,0,134,0,142,0,139,0,157,0,169,0,164,0,239,0,138,0,218,0,131,0,147,0,242,0,243,0,141,0,151,0,136,0,195,0,222,0,241,0,158,0,170, + 0,245,0,244,0,246,0,162,0,173,0,201,0,199,0,174,0,98,0,99,0,144,0,100,0,203,0,101,0,200,0,202,0,207,0,204,0,205,0,206,0,233,0,102,0,211,0,208,0,209,0,175,0,103,0,240,0,145,0,214,0, + 212,0,213,0,104,0,235,0,237,0,137,0,106,0,105,0,107,0,109,0,108,0,110,0,160,0,111,0,113,0,112,0,114,0,115,0,117,0,116,0,118,0,119,0,234,0,120,0,122,0,121,0,123,0,125,0,124,0,184,0, + 161,0,127,0,126,0,128,0,129,0,236,0,238,0,186,14,117,110,105,99,111,100,101,35,48,120,48,48,48,49,141,14,32,50,141,14,32,51,141,14,32,52,141,14,32,53,141,14,32,54,141,14,32,55,141, + 14,32,56,141,14,32,57,141,14,32,97,141,14,32,98,141,14,32,99,141,14,32,100,141,14,32,101,141,14,32,102,140,14,33,49,48,141,14,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49, + 141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,32,49,141,239,45,49,102,6,100,101,108,101,116,101,4,69,117,114, + 111,140,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236,32,56,141,236, + 32,56,141,236,32,56,141,236,32,56,65,220,13,32,57,65,220,13,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141, + 239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,32,57,141,239,35,57,102,0,0,5,250,72,249,98,247, +}; + +static const char* GetDefaultCompressedFontDataTTF(int* out_size) +{ + *out_size = proggy_clean_ttf_compressed_size; + return (const char*)proggy_clean_ttf_compressed_data; } +#endif // #ifndef IMGUI_DISABLE_DEFAULT_FONT #endif // #ifndef IMGUI_DISABLE diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.cpp" index 3eff9ab5..37b5815c 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.cpp" @@ -2,8 +2,10 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -15,6 +17,9 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. +// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. +// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575) // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. // 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). @@ -42,15 +47,21 @@ // DirectX #include +// Clang/GCC warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#endif + // DirectX data struct ImGui_ImplDX9_Data { LPDIRECT3DDEVICE9 pd3dDevice; LPDIRECT3DVERTEXBUFFER9 pVB; LPDIRECT3DINDEXBUFFER9 pIB; - LPDIRECT3DTEXTURE9 FontTexture; int VertexBufferSize; int IndexBufferSize; + bool HasRgbaSupport; ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } }; @@ -88,41 +99,45 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) vp.Height = (DWORD)draw_data->DisplaySize.y; vp.MinZ = 0.0f; vp.MaxZ = 1.0f; - bd->pd3dDevice->SetViewport(&vp); + + LPDIRECT3DDEVICE9 device = bd->pd3dDevice; + device->SetViewport(&vp); // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling. - bd->pd3dDevice->SetPixelShader(nullptr); - bd->pd3dDevice->SetVertexShader(nullptr); - bd->pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); - bd->pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); - bd->pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); - bd->pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); - bd->pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); - bd->pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); - bd->pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); - bd->pd3dDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE); - bd->pd3dDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE); - bd->pd3dDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA); - bd->pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); - bd->pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); - bd->pd3dDevice->SetRenderState(D3DRS_CLIPPING, TRUE); - bd->pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); - bd->pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); - bd->pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); - bd->pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); - bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); - bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + device->SetPixelShader(nullptr); + device->SetVertexShader(nullptr); + device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); + device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE); + device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + device->SetRenderState(D3DRS_ZENABLE, FALSE); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE); + device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE); + device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA); + device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); + device->SetRenderState(D3DRS_FOGENABLE, FALSE); + device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE); + device->SetRenderState(D3DRS_SPECULARENABLE, FALSE); + device->SetRenderState(D3DRS_STENCILENABLE, FALSE); + device->SetRenderState(D3DRS_CLIPPING, TRUE); + device->SetRenderState(D3DRS_LIGHTING, FALSE); + device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); + device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); + device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); + device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); + device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE); + device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); + device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); // Setup orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. @@ -140,9 +155,9 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) 0.0f, 0.0f, 0.5f, 0.0f, (L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f } } }; - bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); - bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); - bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); + device->SetTransform(D3DTS_WORLD, &mat_identity); + device->SetTransform(D3DTS_VIEW, &mat_identity); + device->SetTransform(D3DTS_PROJECTION, &mat_projection); } } @@ -153,51 +168,60 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; - // Create and grow buffers if needed ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + LPDIRECT3DDEVICE9 device = bd->pd3dDevice; + + // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. + // (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates). + if (draw_data->Textures != nullptr) + for (ImTextureData* tex : *draw_data->Textures) + if (tex->Status != ImTextureStatus_OK) + ImGui_ImplDX9_UpdateTexture(tex); + + // Create and grow buffers if needed if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) { if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; - if (bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0) + if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0) return; } if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) { if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; - if (bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0) + if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0) return; } // Backup the DX9 state - IDirect3DStateBlock9* d3d9_state_block = nullptr; - if (bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) + IDirect3DStateBlock9* state_block = nullptr; + if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0) return; - if (d3d9_state_block->Capture() < 0) + if (state_block->Capture() < 0) { - d3d9_state_block->Release(); + state_block->Release(); return; } // Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to) D3DMATRIX last_world, last_view, last_projection; - bd->pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); - bd->pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); - bd->pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection); + device->GetTransform(D3DTS_WORLD, &last_world); + device->GetTransform(D3DTS_VIEW, &last_view); + device->GetTransform(D3DTS_PROJECTION, &last_projection); // Allocate buffers CUSTOMVERTEX* vtx_dst; ImDrawIdx* idx_dst; if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) { - d3d9_state_block->Release(); + state_block->Release(); return; } if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0) { bd->pVB->Unlock(); - d3d9_state_block->Release(); + state_block->Release(); return; } @@ -205,11 +229,10 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) // FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and // 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR // 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } - for (int n = 0; n < draw_data->CmdListsCount; n++) + for (const ImDrawList* draw_list : draw_data->CmdLists) { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data; - for (int i = 0; i < cmd_list->VtxBuffer.Size; i++) + const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data; + for (int i = 0; i < draw_list->VtxBuffer.Size; i++) { vtx_dst->pos[0] = vtx_src->pos.x; vtx_dst->pos[1] = vtx_src->pos.y; @@ -220,14 +243,14 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) vtx_dst++; vtx_src++; } - memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); - idx_dst += cmd_list->IdxBuffer.Size; + memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); + idx_dst += draw_list->IdxBuffer.Size; } bd->pVB->Unlock(); bd->pIB->Unlock(); - bd->pd3dDevice->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX)); - bd->pd3dDevice->SetIndices(bd->pIB); - bd->pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); + device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX)); + device->SetIndices(bd->pIB); + device->SetFVF(D3DFVF_CUSTOMVERTEX); // Setup desired DX state ImGui_ImplDX9_SetupRenderState(draw_data); @@ -237,12 +260,11 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) int global_vtx_offset = 0; int global_idx_offset = 0; ImVec2 clip_off = draw_data->DisplayPos; - for (int n = 0; n < draw_data->CmdListsCount; n++) + for (const ImDrawList* draw_list : draw_data->CmdLists) { - const ImDrawList* cmd_list = draw_data->CmdLists[n]; - for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) { - const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback != nullptr) { // User callback, registered via ImDrawList::AddCallback() @@ -250,7 +272,7 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) ImGui_ImplDX9_SetupRenderState(draw_data); else - pcmd->UserCallback(cmd_list, pcmd); + pcmd->UserCallback(draw_list, pcmd); } else { @@ -260,26 +282,46 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) continue; - // Apply Scissor/clipping rectangle, Bind texture, Draw + // Apply scissor/clipping rectangle const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y }; + device->SetScissorRect(&r); + + // Bind texture, Draw const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID(); - bd->pd3dDevice->SetTexture(0, texture); - bd->pd3dDevice->SetScissorRect(&r); - bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); + device->SetTexture(0, texture); + device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); } } - global_idx_offset += cmd_list->IdxBuffer.Size; - global_vtx_offset += cmd_list->VtxBuffer.Size; + global_idx_offset += draw_list->IdxBuffer.Size; + global_vtx_offset += draw_list->VtxBuffer.Size; } // Restore the DX9 transform - bd->pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); - bd->pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); - bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); + device->SetTransform(D3DTS_WORLD, &last_world); + device->SetTransform(D3DTS_VIEW, &last_view); + device->SetTransform(D3DTS_PROJECTION, &last_projection); // Restore the DX9 state - d3d9_state_block->Apply(); - d3d9_state_block->Release(); + state_block->Apply(); + state_block->Release(); +} + +static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format) +{ + LPDIRECT3D9 pd3d = nullptr; + if (pDevice->GetDirect3D(&pd3d) != D3D_OK) + return false; + D3DDEVICE_CREATION_PARAMETERS param = {}; + D3DDISPLAYMODE mode = {}; + if (pDevice->GetCreationParameters(¶m) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK) + { + pd3d->Release(); + return false; + } + // Font texture should support linear filter, color blend and write to render-target + bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK; + pd3d->Release(); + return support; } bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) @@ -293,9 +335,14 @@ bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) io.BackendRendererUserData = (void*)bd; io.BackendRendererName = "imgui_impl_dx9"; io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. + io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. + + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096; bd->pd3dDevice = device; bd->pd3dDevice->AddRef(); + bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); return true; } @@ -305,76 +352,95 @@ void ImGui_ImplDX9_Shutdown() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGui_ImplDX9_InvalidateDeviceObjects(); if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } + io.BackendRendererName = nullptr; io.BackendRendererUserData = nullptr; - io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures); + platform_io.ClearRendererHandlers(); IM_DELETE(bd); } -static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMAT format) +// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) +static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) { - IDirect3D9* pd3d = nullptr; - if (pDevice->GetDirect3D(&pd3d) != D3D_OK) - return false; - D3DDEVICE_CREATION_PARAMETERS param = {}; - D3DDISPLAYMODE mode = {}; - if (pDevice->GetCreationParameters(¶m) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK) +#ifndef IMGUI_USE_BGRA_PACKED_COLOR + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors); +#else + const bool convert_rgba_to_bgra = false; + IM_UNUSED(tex_use_colors); +#endif + for (int y = 0; y < h; y++) { - pd3d->Release(); - return false; + const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y); + ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y); + if (convert_rgba_to_bgra) + for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy + *dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p); + else + memcpy(dst_p, src_p, w * 4); // Raw copy } - // Font texture should support linear filter, color blend and write to render-target - bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK; - pd3d->Release(); - return support; } -static bool ImGui_ImplDX9_CreateFontsTexture() +void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex) { - // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); - unsigned char* pixels; - int width, height, bytes_per_pixel; - io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); - // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) -#ifndef IMGUI_USE_BGRA_PACKED_COLOR - const bool rgba_support = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); - if (!rgba_support && io.Fonts->TexPixelsUseColors) + if (tex->Status == ImTextureStatus_WantCreate) { - ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel); - for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++) - *dst = IMGUI_COL_TO_DX9_ARGB(*src); - pixels = (unsigned char*)dst_start; - } -#else - const bool rgba_support = false; -#endif - - // Upload texture to graphics system - bd->FontTexture = nullptr; - if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) - return false; - D3DLOCKED_RECT tex_locked_rect; - if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) - return false; - for (int y = 0; y < height; y++) - memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel); - bd->FontTexture->UnlockRect(0); + // Create and upload new texture to graphics system + //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height); + IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr); + IM_ASSERT(tex->Format == ImTextureFormat_RGBA32); + LPDIRECT3DTEXTURE9 dx_tex = nullptr; + HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr); + if (hr < 0) + { + IM_ASSERT(hr >= 0 && "Backend failed to create texture!"); + return; + } - // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->FontTexture); + D3DLOCKED_RECT locked_rect; + if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK) + { + ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height); + dx_tex->UnlockRect(0); + } -#ifndef IMGUI_USE_BGRA_PACKED_COLOR - if (!rgba_support && io.Fonts->TexPixelsUseColors) - ImGui::MemFree(pixels); -#endif + // Store identifiers + tex->SetTexID((ImTextureID)(intptr_t)dx_tex); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantUpdates) + { + // Update selected blocks. We only ever write to textures regions which have never been used before! + // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region. + LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID; + RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) }; + D3DLOCKED_RECT locked_rect; + if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK) + for (ImTextureRect& r : tex->Updates) + ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4, + (ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h); + backend_tex->UnlockRect(0); + tex->SetStatus(ImTextureStatus_OK); + } + else if (tex->Status == ImTextureStatus_WantDestroy) + { + if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID) + { + IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex); + backend_tex->Release(); - return true; + // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running) + tex->SetTexID(ImTextureID_Invalid); + } + tex->SetStatus(ImTextureStatus_Destroyed); + } } bool ImGui_ImplDX9_CreateDeviceObjects() @@ -382,8 +448,6 @@ bool ImGui_ImplDX9_CreateDeviceObjects() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); if (!bd || !bd->pd3dDevice) return false; - if (!ImGui_ImplDX9_CreateFontsTexture()) - return false; return true; } @@ -392,18 +456,23 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); if (!bd || !bd->pd3dDevice) return; + + // Destroy all textures + for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) + if (tex->RefCount == 1) + { + tex->SetStatus(ImTextureStatus_WantDestroy); + ImGui_ImplDX9_UpdateTexture(tex); + } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } - if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. } void ImGui_ImplDX9_NewFrame() { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?"); - - if (!bd->FontTexture) - ImGui_ImplDX9_CreateDeviceObjects(); + IM_UNUSED(bd); } //----------------------------------------------------------------------------- diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.h" index df6a0a01..600f0a75 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_dx9.h" @@ -2,8 +2,10 @@ // This needs to be used along with a Platform Backend (e.g. Win32) // Implemented features: -// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! -// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. +// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef! +// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). +// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). +// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -29,4 +31,7 @@ IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); +// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually. +IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex); + #endif // #ifndef IMGUI_DISABLE diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.cpp" index 0e9164ef..dc7e66f0 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.cpp" @@ -4,9 +4,9 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. -// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] -// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] +// [X] Platform: Gamepad support. +// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -21,6 +21,12 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-10-19: Inputs: Revert previous change to allow for io.ClearInputKeys() on focus-out not losing gamepad state. +// 2025-09-23: Inputs: Minor optimization not submitting gamepad input if packet number has not changed. +// 2025-09-18: Call platform_io.ClearPlatformHandlers() on shutdown. +// 2025-04-30: Inputs: Fixed an issue where externally losing mouse capture (due to e.g. focus loss) would fail to claim it again the next subsequent click. (#8594) +// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) +// 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support. // 2024-07-08: Inputs: Fixed ImGuiMod_Super being mapped to VK_APPS instead of VK_LWIN||VK_RWIN. (#7768) // 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys. // 2023-09-25: Inputs: Synthesize key-down event on key-up for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit it (same behavior as GLFW/SDL). @@ -133,12 +139,16 @@ static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData() { return ImGui::GetCurrentContext() ? (ImGui_ImplWin32_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; } +static ImGui_ImplWin32_Data* ImGui_ImplWin32_GetBackendData(ImGuiIO& io) +{ + return (ImGui_ImplWin32_Data*)io.BackendPlatformUserData; +} // Functions -static void ImGui_ImplWin32_UpdateKeyboardCodePage() +static void ImGui_ImplWin32_UpdateKeyboardCodePage(ImGuiIO& io) { // Retrieve keyboard code page, required for handling of non-Unicode Windows. - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); HKL keyboard_layout = ::GetKeyboardLayout(0); LCID keyboard_lcid = MAKELCID(HIWORD(keyboard_layout), SORT_DEFAULT); if (::GetLocaleInfoA(keyboard_lcid, (LOCALE_RETURN_NUMBER | LOCALE_IDEFAULTANSICODEPAGE), (LPSTR)&bd->KeyboardCodePage, sizeof(bd->KeyboardCodePage)) == 0) @@ -168,7 +178,7 @@ static bool ImGui_ImplWin32_InitEx(void* hwnd, bool platform_has_own_dc) bd->TicksPerSecond = perf_frequency; bd->Time = perf_counter; bd->LastMouseCursor = ImGuiMouseCursor_COUNT; - ImGui_ImplWin32_UpdateKeyboardCodePage(); + ImGui_ImplWin32_UpdateKeyboardCodePage(io); // Set platform dependent data in viewport ImGuiViewport* main_viewport = ImGui::GetMainViewport(); @@ -215,6 +225,7 @@ void ImGui_ImplWin32_Shutdown() ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); // Unload XInput library #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD @@ -225,16 +236,15 @@ void ImGui_ImplWin32_Shutdown() io.BackendPlatformName = nullptr; io.BackendPlatformUserData = nullptr; io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad); + platform_io.ClearPlatformHandlers(); IM_DELETE(bd); } -static bool ImGui_ImplWin32_UpdateMouseCursor() +static bool ImGui_ImplWin32_UpdateMouseCursor(ImGuiIO& io, ImGuiMouseCursor imgui_cursor) { - ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) return false; - ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) { // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor @@ -254,6 +264,8 @@ static bool ImGui_ImplWin32_UpdateMouseCursor() case ImGuiMouseCursor_ResizeNESW: win32_cursor = IDC_SIZENESW; break; case ImGuiMouseCursor_ResizeNWSE: win32_cursor = IDC_SIZENWSE; break; case ImGuiMouseCursor_Hand: win32_cursor = IDC_HAND; break; + case ImGuiMouseCursor_Wait: win32_cursor = IDC_WAIT; break; + case ImGuiMouseCursor_Progress: win32_cursor = IDC_APPSTARTING; break; case ImGuiMouseCursor_NotAllowed: win32_cursor = IDC_NO; break; } ::SetCursor(::LoadCursor(nullptr, win32_cursor)); @@ -266,49 +278,46 @@ static bool IsVkDown(int vk) return (::GetKeyState(vk) & 0x8000) != 0; } -static void ImGui_ImplWin32_AddKeyEvent(ImGuiKey key, bool down, int native_keycode, int native_scancode = -1) +static void ImGui_ImplWin32_AddKeyEvent(ImGuiIO& io, ImGuiKey key, bool down, int native_keycode, int native_scancode = -1) { - ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(key, down); io.SetKeyEventNativeData(key, native_keycode, native_scancode); // To support legacy indexing (<1.87 user code) IM_UNUSED(native_scancode); } -static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds() +static void ImGui_ImplWin32_ProcessKeyEventsWorkarounds(ImGuiIO& io) { // Left & right Shift keys: when both are pressed together, Windows tend to not generate the WM_KEYUP event for the first released one. if (ImGui::IsKeyDown(ImGuiKey_LeftShift) && !IsVkDown(VK_LSHIFT)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, false, VK_LSHIFT); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, false, VK_LSHIFT); if (ImGui::IsKeyDown(ImGuiKey_RightShift) && !IsVkDown(VK_RSHIFT)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, false, VK_RSHIFT); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, false, VK_RSHIFT); // Sometimes WM_KEYUP for Win key is not passed down to the app (e.g. for Win+V on some setups, according to GLFW). if (ImGui::IsKeyDown(ImGuiKey_LeftSuper) && !IsVkDown(VK_LWIN)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftSuper, false, VK_LWIN); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftSuper, false, VK_LWIN); if (ImGui::IsKeyDown(ImGuiKey_RightSuper) && !IsVkDown(VK_RWIN)) - ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightSuper, false, VK_RWIN); + ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightSuper, false, VK_RWIN); } -static void ImGui_ImplWin32_UpdateKeyModifiers() +static void ImGui_ImplWin32_UpdateKeyModifiers(ImGuiIO& io) { - ImGuiIO& io = ImGui::GetIO(); io.AddKeyEvent(ImGuiMod_Ctrl, IsVkDown(VK_CONTROL)); io.AddKeyEvent(ImGuiMod_Shift, IsVkDown(VK_SHIFT)); io.AddKeyEvent(ImGuiMod_Alt, IsVkDown(VK_MENU)); io.AddKeyEvent(ImGuiMod_Super, IsVkDown(VK_LWIN) || IsVkDown(VK_RWIN)); } -static void ImGui_ImplWin32_UpdateMouseData() +static void ImGui_ImplWin32_UpdateMouseData(ImGuiIO& io) { - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); - ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); IM_ASSERT(bd->hWnd != 0); HWND focused_window = ::GetForegroundWindow(); const bool is_app_focused = (focused_window == bd->hWnd); if (is_app_focused) { - // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user) + // (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when io.ConfigNavMoveSetMousePos is enabled by user) if (io.WantSetMousePos) { POINT pos = { (int)io.MousePos.x, (int)io.MousePos.y }; @@ -328,13 +337,10 @@ static void ImGui_ImplWin32_UpdateMouseData() } // Gamepad navigation mapping -static void ImGui_ImplWin32_UpdateGamepads() +static void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io) { #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD - ImGuiIO& io = ImGui::GetIO(); - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); - //if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. - // return; + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow. // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE. @@ -381,7 +387,9 @@ static void ImGui_ImplWin32_UpdateGamepads() MAP_ANALOG(ImGuiKey_GamepadRStickDown, gamepad.sThumbRY, -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, -32768); #undef MAP_BUTTON #undef MAP_ANALOG -#endif // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD +#else // #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD + IM_UNUSED(io); +#endif } void ImGui_ImplWin32_NewFrame() @@ -402,31 +410,34 @@ void ImGui_ImplWin32_NewFrame() bd->Time = current_time; // Update OS mouse position - ImGui_ImplWin32_UpdateMouseData(); + ImGui_ImplWin32_UpdateMouseData(io); // Process workarounds for known Windows key handling issues - ImGui_ImplWin32_ProcessKeyEventsWorkarounds(); + ImGui_ImplWin32_ProcessKeyEventsWorkarounds(io); // Update OS mouse cursor with the cursor requested by imgui ImGuiMouseCursor mouse_cursor = io.MouseDrawCursor ? ImGuiMouseCursor_None : ImGui::GetMouseCursor(); if (bd->LastMouseCursor != mouse_cursor) { bd->LastMouseCursor = mouse_cursor; - ImGui_ImplWin32_UpdateMouseCursor(); + ImGui_ImplWin32_UpdateMouseCursor(io, mouse_cursor); } // Update game controllers (if enabled and available) - ImGui_ImplWin32_UpdateGamepads(); + ImGui_ImplWin32_UpdateGamepads(io); } // Map VK_xxx to ImGuiKey_xxx. // Not static to allow third-party code to use that if they want to (but undocumented) +ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam); ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) { // There is no distinct VK_xxx for keypad enter, instead it is VK_RETURN + KF_EXTENDED. if ((wParam == VK_RETURN) && (HIWORD(lParam) & KF_EXTENDED)) return ImGuiKey_KeypadEnter; + const int scancode = (int)LOBYTE(HIWORD(lParam)); + //IMGUI_DEBUG_LOG("scancode %3d, keycode = 0x%02X\n", scancode, wParam); switch (wParam) { case VK_TAB: return ImGuiKey_Tab; @@ -444,17 +455,17 @@ ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) case VK_SPACE: return ImGuiKey_Space; case VK_RETURN: return ImGuiKey_Enter; case VK_ESCAPE: return ImGuiKey_Escape; - case VK_OEM_7: return ImGuiKey_Apostrophe; + //case VK_OEM_7: return ImGuiKey_Apostrophe; case VK_OEM_COMMA: return ImGuiKey_Comma; - case VK_OEM_MINUS: return ImGuiKey_Minus; + //case VK_OEM_MINUS: return ImGuiKey_Minus; case VK_OEM_PERIOD: return ImGuiKey_Period; - case VK_OEM_2: return ImGuiKey_Slash; - case VK_OEM_1: return ImGuiKey_Semicolon; - case VK_OEM_PLUS: return ImGuiKey_Equal; - case VK_OEM_4: return ImGuiKey_LeftBracket; - case VK_OEM_5: return ImGuiKey_Backslash; - case VK_OEM_6: return ImGuiKey_RightBracket; - case VK_OEM_3: return ImGuiKey_GraveAccent; + //case VK_OEM_2: return ImGuiKey_Slash; + //case VK_OEM_1: return ImGuiKey_Semicolon; + //case VK_OEM_PLUS: return ImGuiKey_Equal; + //case VK_OEM_4: return ImGuiKey_LeftBracket; + //case VK_OEM_5: return ImGuiKey_Backslash; + //case VK_OEM_6: return ImGuiKey_RightBracket; + //case VK_OEM_3: return ImGuiKey_GraveAccent; case VK_CAPITAL: return ImGuiKey_CapsLock; case VK_SCROLL: return ImGuiKey_ScrollLock; case VK_NUMLOCK: return ImGuiKey_NumLock; @@ -546,8 +557,29 @@ ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) case VK_F24: return ImGuiKey_F24; case VK_BROWSER_BACK: return ImGuiKey_AppBack; case VK_BROWSER_FORWARD: return ImGuiKey_AppForward; - default: return ImGuiKey_None; + default: break; } + + // Fallback to scancode + // https://handmade.network/forums/t/2011-keyboard_inputs_-_scancodes,_raw_input,_text_input,_key_names + switch (scancode) + { + case 41: return ImGuiKey_GraveAccent; // VK_OEM_8 in EN-UK, VK_OEM_3 in EN-US, VK_OEM_7 in FR, VK_OEM_5 in DE, etc. + case 12: return ImGuiKey_Minus; + case 13: return ImGuiKey_Equal; + case 26: return ImGuiKey_LeftBracket; + case 27: return ImGuiKey_RightBracket; + case 86: return ImGuiKey_Oem102; + case 43: return ImGuiKey_Backslash; + case 39: return ImGuiKey_Semicolon; + case 40: return ImGuiKey_Apostrophe; + case 51: return ImGuiKey_Comma; + case 52: return ImGuiKey_Period; + case 53: return ImGuiKey_Slash; + default: break; + } + + return ImGuiKey_None; } // Allow compilation with old Windows SDK. MinGW doesn't have default _WIN32_WINNT/WINVER versions. @@ -558,22 +590,10 @@ ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam) #define DBT_DEVNODES_CHANGED 0x0007 #endif -// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) -// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. -// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. -// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. -// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. -// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. -// PS: In this Win32 handler, we use the capture API (GetCapture/SetCapture/ReleaseCapture) to be able to read mouse coordinates when dragging mouse outside of our window bounds. -// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. -#if 0 -// Copy this line into your .cpp file to forward declare the function. -extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); -#endif - +// Helper to obtain the source of mouse messages. // See https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages // Prefer to call this at the top of the message handler to avoid the possibility of other Win32 calls interfering with this. -static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo() +static ImGuiMouseSource ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo() { LPARAM extra_info = ::GetMessageExtraInfo(); if ((extra_info & 0xFFFFFF80) == 0xFF515700) @@ -583,22 +603,40 @@ static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo() return ImGuiMouseSource_Mouse; } +// Win32 message handler (process Win32 mouse/keyboard inputs, etc.) +// Call from your application's message handler. Keep calling your message handler unless this function returns TRUE. +// When implementing your own backend, you can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if Dear ImGui wants to use your inputs. +// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. +// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. +// Generally you may always pass all inputs to Dear ImGui, and hide them from your application based on those two flags. +// PS: We treat DBLCLK messages as regular mouse down messages, so this code will work on windows classes that have the CS_DBLCLKS flag set. Our own example app code doesn't set this flag. + +// Copy either line into your .cpp file to forward declare the function: +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Use ImGui::GetCurrentContext() +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext() + IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow(). // We silently allow both context or just only backend data to be nullptr. - ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(); - if (bd == nullptr) + if (ImGui::GetCurrentContext() == nullptr) return 0; - ImGuiIO& io = ImGui::GetIO(); + return ImGui_ImplWin32_WndProcHandlerEx(hwnd, msg, wParam, lParam, ImGui::GetIO()); +} +// This version is in theory thread-safe in the sense that no path should access ImGui::GetCurrentContext(). +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io) +{ + ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); + if (bd == nullptr) + return 0; switch (msg) { case WM_MOUSEMOVE: case WM_NCMOUSEMOVE: { // We need to call TrackMouseEvent in order to receive WM_MOUSELEAVE events - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); const int area = (msg == WM_MOUSEMOVE) ? 1 : 2; bd->MouseHwnd = hwnd; if (bd->MouseTrackedArea != area) @@ -645,14 +683,17 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: { - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { button = 0; } if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; } if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; } if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } - if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr) - ::SetCapture(hwnd); + HWND hwnd_with_capture = ::GetCapture(); + if (bd->MouseButtonsDown != 0 && hwnd_with_capture != hwnd) // Did we externally lost capture? + bd->MouseButtonsDown = 0; + if (bd->MouseButtonsDown == 0 && hwnd_with_capture == nullptr) + ::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds. bd->MouseButtonsDown |= 1 << button; io.AddMouseSourceEvent(mouse_source); io.AddMouseButtonEvent(button, true); @@ -663,7 +704,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA case WM_MBUTTONUP: case WM_XBUTTONUP: { - ImGuiMouseSource mouse_source = GetMouseSourceFromMessageExtraInfo(); + ImGuiMouseSource mouse_source = ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo(); int button = 0; if (msg == WM_LBUTTONUP) { button = 0; } if (msg == WM_RBUTTONUP) { button = 1; } @@ -691,7 +732,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA if (wParam < 256) { // Submit modifiers - ImGui_ImplWin32_UpdateKeyModifiers(); + ImGui_ImplWin32_UpdateKeyModifiers(io); // Obtain virtual key code and convert to ImGuiKey const ImGuiKey key = ImGui_ImplWin32_KeyEventToImGuiKey(wParam, lParam); @@ -700,28 +741,28 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA // Special behavior for VK_SNAPSHOT / ImGuiKey_PrintScreen as Windows doesn't emit the key down event. if (key == ImGuiKey_PrintScreen && !is_key_down) - ImGui_ImplWin32_AddKeyEvent(key, true, vk, scancode); + ImGui_ImplWin32_AddKeyEvent(io, key, true, vk, scancode); // Submit key event if (key != ImGuiKey_None) - ImGui_ImplWin32_AddKeyEvent(key, is_key_down, vk, scancode); + ImGui_ImplWin32_AddKeyEvent(io, key, is_key_down, vk, scancode); // Submit individual left/right modifier events if (vk == VK_SHIFT) { // Important: Shift keys tend to get stuck when pressed together, missing key-up events are corrected in ImGui_ImplWin32_ProcessKeyEventsWorkarounds() - if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); } - if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); } + if (IsVkDown(VK_LSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftShift, is_key_down, VK_LSHIFT, scancode); } + if (IsVkDown(VK_RSHIFT) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightShift, is_key_down, VK_RSHIFT, scancode); } } else if (vk == VK_CONTROL) { - if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); } - if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); } + if (IsVkDown(VK_LCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftCtrl, is_key_down, VK_LCONTROL, scancode); } + if (IsVkDown(VK_RCONTROL) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightCtrl, is_key_down, VK_RCONTROL, scancode); } } else if (vk == VK_MENU) { - if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); } - if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); } + if (IsVkDown(VK_LMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_LeftAlt, is_key_down, VK_LMENU, scancode); } + if (IsVkDown(VK_RMENU) == is_key_down) { ImGui_ImplWin32_AddKeyEvent(io, ImGuiKey_RightAlt, is_key_down, VK_RMENU, scancode); } } } return 0; @@ -731,7 +772,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA io.AddFocusEvent(msg == WM_SETFOCUS); return 0; case WM_INPUTLANGCHANGE: - ImGui_ImplWin32_UpdateKeyboardCodePage(); + ImGui_ImplWin32_UpdateKeyboardCodePage(io); return 0; case WM_CHAR: if (::IsWindowUnicode(hwnd)) @@ -749,7 +790,7 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA return 0; case WM_SETCURSOR: // This is required to restore cursor when transitioning from e.g resize borders to client area. - if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor()) + if (LOWORD(lParam) == HTCLIENT && ImGui_ImplWin32_UpdateMouseCursor(io, bd->LastMouseCursor)) return 1; return 0; case WM_DEVICECHANGE: @@ -783,9 +824,9 @@ static BOOL _IsWindowsVersionOrGreater(WORD major, WORD minor, WORD) { typedef LONG(WINAPI* PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*, ULONG, ULONGLONG); static PFN_RtlVerifyVersionInfo RtlVerifyVersionInfoFn = nullptr; - if (RtlVerifyVersionInfoFn == nullptr) - if (HMODULE ntdllModule = ::GetModuleHandleA("ntdll.dll")) - RtlVerifyVersionInfoFn = (PFN_RtlVerifyVersionInfo)GetProcAddress(ntdllModule, "RtlVerifyVersionInfo"); + if (RtlVerifyVersionInfoFn == nullptr) + if (HMODULE ntdllModule = ::GetModuleHandleA("ntdll.dll")) + RtlVerifyVersionInfoFn = (PFN_RtlVerifyVersionInfo)GetProcAddress(ntdllModule, "RtlVerifyVersionInfo"); if (RtlVerifyVersionInfoFn == nullptr) return FALSE; @@ -793,10 +834,10 @@ static BOOL _IsWindowsVersionOrGreater(WORD major, WORD minor, WORD) ULONGLONG conditionMask = 0; versionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); versionInfo.dwMajorVersion = major; - versionInfo.dwMinorVersion = minor; - VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); - return (RtlVerifyVersionInfoFn(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == 0) ? TRUE : FALSE; + versionInfo.dwMinorVersion = minor; + VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_GREATER_EQUAL); + return (RtlVerifyVersionInfoFn(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == 0) ? TRUE : FALSE; } #define _IsWindowsVistaOrGreater() _IsWindowsVersionOrGreater(HIBYTE(0x0600), LOBYTE(0x0600), 0) // _WIN32_WINNT_VISTA @@ -854,16 +895,16 @@ float ImGui_ImplWin32_GetDpiScaleForMonitor(void* monitor) UINT xdpi = 96, ydpi = 96; if (_IsWindows8Point1OrGreater()) { - static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process - static PFN_GetDpiForMonitor GetDpiForMonitorFn = nullptr; - if (GetDpiForMonitorFn == nullptr && shcore_dll != nullptr) + static HINSTANCE shcore_dll = ::LoadLibraryA("shcore.dll"); // Reference counted per-process + static PFN_GetDpiForMonitor GetDpiForMonitorFn = nullptr; + if (GetDpiForMonitorFn == nullptr && shcore_dll != nullptr) GetDpiForMonitorFn = (PFN_GetDpiForMonitor)::GetProcAddress(shcore_dll, "GetDpiForMonitor"); - if (GetDpiForMonitorFn != nullptr) - { - GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + if (GetDpiForMonitorFn != nullptr) + { + GetDpiForMonitorFn((HMONITOR)monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); IM_ASSERT(xdpi == ydpi); // Please contact me if you hit this assert! - return xdpi / 96.0f; - } + return xdpi / 96.0f; + } } #ifndef NOGDI const HDC dc = ::GetDC(nullptr); diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.h" index 3ad1a7ea..5ae399e0 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_impl_win32.h" @@ -4,9 +4,9 @@ // Implemented features: // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. -// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set] -// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. -// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. +// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] +// [X] Platform: Gamepad support. +// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_internal.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_internal.h" index 9f47ecd2..f577c170 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_internal.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_internal.h" @@ -1,4 +1,4 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -14,6 +14,7 @@ Index of this file: // [SECTION] Macros // [SECTION] Generic helpers // [SECTION] ImDrawList support +// [SECTION] Style support // [SECTION] Data types support // [SECTION] Widgets support: flags, enums, data structures // [SECTION] Popup support @@ -36,6 +37,7 @@ Index of this file: // [SECTION] Tab bar, Tab item support // [SECTION] Table support // [SECTION] ImGui internal API +// [SECTION] ImFontLoader // [SECTION] ImFontAtlas internal API // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -61,14 +63,22 @@ Index of this file: #if (defined __SSE__ || defined __x86_64__ || defined _M_X64 || (defined(_M_IX86_FP) && (_M_IX86_FP >= 1))) && !defined(IMGUI_DISABLE_SSE) #define IMGUI_ENABLE_SSE #include +#if (defined __AVX__ || defined __SSE4_2__) +#define IMGUI_ENABLE_SSE4_2 +#include +#endif +#endif +// Emscripten has partial SSE 4.2 support where _mm_crc32_u32 is not available. See https://emscripten.org/docs/porting/simd.html#id11 and #8213 +#if defined(IMGUI_ENABLE_SSE4_2) && !defined(IMGUI_USE_LEGACY_CRC32_ADLER) && !defined(__EMSCRIPTEN__) +#define IMGUI_ENABLE_SSE4_2_CRC #endif // Visual Studio warnings #ifdef _MSC_VER #pragma warning (push) #pragma warning (disable: 4251) // class 'xxx' needs to have dll-interface to be used by clients of struct 'xxx' // when IMGUI_API is set to__declspec(dllexport) -#pragma warning (disable: 26812) // The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer) #pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6). +#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types #endif @@ -82,19 +92,19 @@ Index of this file: #endif #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok, for ImFloor() -#pragma clang diagnostic ignored "-Wunused-function" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wmissing-prototypes" // for stb_textedit.h -#pragma clang diagnostic ignored "-Wold-style-cast" -#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant +#pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type #elif defined(__GNUC__) #pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #endif @@ -122,16 +132,26 @@ Index of this file: // [SECTION] Forward declarations //----------------------------------------------------------------------------- +// Utilities +// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImStableVector<>, ImPool<>, ImChunkStream<>) struct ImBitVector; // Store 1-bit per value struct ImRect; // An axis-aligned rectangle (2 points) +struct ImGuiTextIndex; // Maintain a line index for a text buffer. + +// ImDrawList/ImFontAtlas struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawListSharedData; // Data shared between all ImDrawList instances +struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions +struct ImFontAtlasRectEntry; // Packed rectangle lookup entry + +// ImGui struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine -struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum +struct ImGuiDeactivatedItemData; // Data for IsItemDeactivated()/IsItemDeactivatedAfterEdit() function. struct ImGuiErrorRecoveryState; // Storage of stack sizes for error handling and recovery struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box @@ -141,7 +161,7 @@ struct ImGuiLocEntry; // A localization entry. struct ImGuiMenuColumns; // Simple column measurement, currently used for MenuItem() only struct ImGuiMultiSelectState; // Multi-selection persistent state (for focused selection). struct ImGuiMultiSelectTempData; // Multi-selection temporary state (while traversing). -struct ImGuiNavItemData; // Result of a gamepad/keyboard directional navigation move query result +struct ImGuiNavItemData; // Result of a keyboard/gamepad directional navigation move query result struct ImGuiMetricsConfig; // Storage for ShowMetricsWindow() and DebugNodeXXX() functions struct ImGuiNextWindowData; // Storage for SetNextWindow** functions struct ImGuiNextItemData; // Storage for SetNextItem** functions @@ -150,6 +170,7 @@ struct ImGuiOldColumns; // Storage data for a columns set for legacy struct ImGuiPopupData; // Storage for current popup stack struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it +struct ImGuiStyleVarInfo; // Style variable information (e.g. to access style variables from an enum) struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) struct ImGuiTable; // Storage for a table @@ -172,12 +193,14 @@ enum ImGuiLocKey : int; // -> enum ImGuiLocKey // E typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // Enum: Horizontal or vertical // Flags +typedef int ImDrawTextFlags; // -> enum ImDrawTextFlags_ // Flags: for ImTextCalcWordWrapPositionEx() typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags typedef int ImGuiFocusRequestFlags; // -> enum ImGuiFocusRequestFlags_ // Flags: for FocusWindow() typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() -typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() +typedef int ImGuiLogFlags; // -> enum ImGuiLogFlags_ // Flags: for LogBegin() text capturing function +typedef int ImGuiNavRenderCursorFlags; // -> enum ImGuiNavRenderCursorFlags_//Flags: for RenderNavCursor() typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests typedef int ImGuiNextItemDataFlags; // -> enum ImGuiNextItemDataFlags_ // Flags: for SetNextItemXXX() functions typedef int ImGuiNextWindowDataFlags; // -> enum ImGuiNextWindowDataFlags_// Flags: for SetNextWindowXXX() functions @@ -186,8 +209,13 @@ typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // F typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() +typedef int ImGuiWindowBgClickFlags; // -> enum ImGuiWindowBgClickFlags_ // Flags: for overriding behavior of clicking on window background/void. typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() +// Table column indexing +typedef ImS16 ImGuiTableColumnIdx; +typedef ImU16 ImGuiTableDrawChannelIdx; + //----------------------------------------------------------------------------- // [SECTION] Context pointer // See implementation of this variable in imgui.cpp for comments and details. @@ -212,12 +240,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #endif // Debug Logging for ShowDebugLogWindow(). This is designed for relatively rare events so please don't spam. -#ifndef IMGUI_DISABLE_DEBUG_TOOLS -#define IMGUI_DEBUG_LOG(...) ImGui::DebugLog(__VA_ARGS__) -#else -#define IMGUI_DEBUG_LOG(...) ((void)0) -#endif -#define IMGUI_DEBUG_LOG_ERROR(...) do { ImGuiContext& g2 = *GImGui; if (g2.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g2.DebugLogSkippedErrors++; } while (0) +#define IMGUI_DEBUG_LOG_ERROR(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventError) IMGUI_DEBUG_LOG(__VA_ARGS__); else g.DebugLogSkippedErrors++; } while (0) #define IMGUI_DEBUG_LOG_ACTIVEID(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventActiveId) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FOCUS(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFocus) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_POPUP(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventPopup) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) @@ -225,6 +248,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts @@ -254,10 +278,17 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 #define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // ImTrunc() is not inlined in MSVC debug builds #define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // -#define IM_STRINGIFY_HELPER(_X) #_X -#define IM_STRINGIFY(_X) IM_STRINGIFY_HELPER(_X) // Preprocessor idiom to stringify e.g. an integer. #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS -#define IM_FLOOR IM_TRUNC +#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) +#endif + +// Hint for branch prediction +#if (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) +#define IM_LIKELY [[likely]] +#define IM_UNLIKELY [[unlikely]] +#else +#define IM_LIKELY +#define IM_UNLIKELY #endif // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall @@ -304,6 +335,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_PRIu64 "llu" #define IM_PRIX64 "llX" #endif +#define IM_TEXTUREID_TO_U64(_TEXID) ((ImU64)(intptr_t)(_TEXID)) //----------------------------------------------------------------------------- // [SECTION] Generic helpers @@ -325,6 +357,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // - Helper: ImBitArray // - Helper: ImBitVector // - Helper: ImSpan<>, ImSpanAllocator<> +// - Helper: ImStableVector<> // - Helper: ImPool<> // - Helper: ImChunkStream<> // - Helper: ImGuiTextIndex @@ -334,25 +367,30 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer // Helpers: Hashing IMGUI_API ImGuiID ImHashData(const void* data, size_t data_size, ImGuiID seed = 0); IMGUI_API ImGuiID ImHashStr(const char* data, size_t data_size = 0, ImGuiID seed = 0); +IMGUI_API const char* ImHashSkipUncontributingPrefix(const char* label); // Helpers: Sorting #ifndef ImQsort -static inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } +inline void ImQsort(void* base, size_t count, size_t size_of_element, int(IMGUI_CDECL *compare_func)(void const*, void const*)) { if (count > 1) qsort(base, count, size_of_element, compare_func); } #endif // Helpers: Color Blending IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); // Helpers: Bit manipulation -static inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } -static inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } -static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +inline bool ImIsPowerOfTwo(int v) { return v != 0 && (v & (v - 1)) == 0; } +inline bool ImIsPowerOfTwo(ImU64 v) { return v != 0 && (v & (v - 1)) == 0; } +inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; } +inline unsigned int ImCountSetBits(unsigned int v) { unsigned int count = 0; while (v > 0) { v = v & (v - 1); count++; } return count; } // Helpers: String +#define ImStrlen strlen +#define ImMemchr memchr IMGUI_API int ImStricmp(const char* str1, const char* str2); // Case insensitive compare. IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count. IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't). IMGUI_API char* ImStrdup(const char* str); // Duplicate a string. +IMGUI_API void* ImMemdup(const void* src, size_t size); // Duplicate a chunk of memory. IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed. IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range. IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line @@ -362,10 +400,10 @@ IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line IM_MSVC_RUNTIME_CHECKS_OFF -static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } -static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } -static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } -static inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } +inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } +inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } +inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting @@ -381,25 +419,38 @@ IMGUI_API const char* ImParseFormatSanitizeForScanning(const char* fmt_in, cha IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); // Helpers: UTF-8 <> wchar conversions -IMGUI_API const char* ImTextCharToUtf8(char out_buf[5], unsigned int c); // return out_buf +IMGUI_API int ImTextCharToUtf8(char out_buf[5], unsigned int c); // return output UTF-8 bytes count IMGUI_API int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count IMGUI_API int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // read one character. return input UTF-8 bytes count IMGUI_API int ImTextStrFromUtf8(ImWchar* out_buf, int out_buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 -IMGUI_API const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr); // return previous UTF-8 code-point. +IMGUI_API const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_p); // return previous UTF-8 code-point. +IMGUI_API const char* ImTextFindValidUtf8CodepointEnd(const char* in_text_start, const char* in_text_end, const char* in_p); // return previous UTF-8 code-point if 'in_p' is not the end of a valid one. IMGUI_API int ImTextCountLines(const char* in_text, const char* in_text_end); // return number of lines taken by text. trailing carriage return doesn't count as an extra line. +// Helpers: High-level text functions (DO NOT USE!!! THIS IS A MINIMAL SUBSET OF LARGER UPCOMING CHANGES) +enum ImDrawTextFlags_ +{ + ImDrawTextFlags_None = 0, + ImDrawTextFlags_CpuFineClip = 1 << 0, // Must be == 1/true for legacy with 'bool cpu_fine_clip' arg to RenderText() + ImDrawTextFlags_WrapKeepBlanks = 1 << 1, + ImDrawTextFlags_StopOnNewLine = 1 << 2, +}; +IMGUI_API ImVec2 ImFontCalcTextSizeEx(ImFont* font, float size, float max_width, float wrap_width, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags); +IMGUI_API const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* text, const char* text_end, float wrap_width, ImDrawTextFlags flags = 0); +IMGUI_API const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_end, ImDrawTextFlags flags = 0); // trim trailing space and find beginning of next line + // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS #define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef void* ImFileHandle; -static inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } -static inline bool ImFileClose(ImFileHandle) { return false; } -static inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } -static inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } -static inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImFileHandle ImFileOpen(const char*, const char*) { return NULL; } +inline bool ImFileClose(ImFileHandle) { return false; } +inline ImU64 ImFileGetSize(ImFileHandle) { return (ImU64)-1; } +inline ImU64 ImFileRead(void*, ImU64, ImU64, ImFileHandle) { return 0; } +inline ImU64 ImFileWrite(const void*, ImU64, ImU64, ImFileHandle) { return 0; } #endif #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS typedef FILE* ImFileHandle; @@ -426,54 +477,56 @@ IM_MSVC_RUNTIME_CHECKS_OFF #define ImAtan2(Y, X) atan2f((Y), (X)) #define ImAtof(STR) atof(STR) #define ImCeil(X) ceilf(X) -static inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision -static inline double ImPow(double x, double y) { return pow(x, y); } -static inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision -static inline double ImLog(double x) { return log(x); } -static inline int ImAbs(int x) { return x < 0 ? -x : x; } -static inline float ImAbs(float x) { return fabsf(x); } -static inline double ImAbs(double x) { return fabs(x); } -static inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument -static inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } +inline float ImPow(float x, float y) { return powf(x, y); } // DragBehaviorT/SliderBehaviorT uses ImPow with either float/double and need the precision +inline double ImPow(double x, double y) { return pow(x, y); } +inline float ImLog(float x) { return logf(x); } // DragBehaviorT/SliderBehaviorT uses ImLog with either float/double and need the precision +inline double ImLog(double x) { return log(x); } +inline int ImAbs(int x) { return x < 0 ? -x : x; } +inline float ImAbs(float x) { return fabsf(x); } +inline double ImAbs(double x) { return fabs(x); } +inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f) ? 1.0f : 0.0f; } // Sign operator - returns -1, 0 or 1 based on sign of argument +inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE -static inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } #else -static inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } #endif -static inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double // (Exceptionally using templates here but we could also redefine them for those types) -template static inline T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } -template static inline T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } -template static inline T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } -template static inline T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } -template static inline void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } -template static inline T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } -template static inline T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } +template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } +template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } +template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +template T ImLerp(T a, T b, float t) { return (T)(a + (b - a) * t); } +template void ImSwap(T& a, T& b) { T tmp = a; a = b; b = tmp; } +template T ImAddClampOverflow(T a, T b, T mn, T mx) { if (b < 0 && (a < mn - b)) return mn; if (b > 0 && (a > mx - b)) return mx; return a + b; } +template T ImSubClampOverflow(T a, T b, T mn, T mx) { if (b > 0 && (a < mn + b)) return mn; if (b < 0 && (a > mx + b)) return mx; return a - b; } // - Misc maths helpers -static inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } -static inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx) { return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } -static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } -static inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } -static inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } -static inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } -static inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } -static inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } -static inline float ImTrunc(float f) { return (float)(int)(f); } -static inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } -static inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() -static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } -static inline int ImModPositive(int a, int b) { return (a + b) % b; } -static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } -static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } -static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } -static inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } -static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } -static inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } -static inline float ImExponentialMovingAverage(float avg, float sample, int n) { avg -= avg / n; avg += sample / n; return avg; } +inline ImVec2 ImMin(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x < rhs.x ? lhs.x : rhs.x, lhs.y < rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImMax(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x >= rhs.x ? lhs.x : rhs.x, lhs.y >= rhs.y ? lhs.y : rhs.y); } +inline ImVec2 ImClamp(const ImVec2& v, const ImVec2&mn, const ImVec2&mx){ return ImVec2((v.x < mn.x) ? mn.x : (v.x > mx.x) ? mx.x : v.x, (v.y < mn.y) ? mn.y : (v.y > mx.y) ? mx.y : v.y); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t) { return ImVec2(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } +inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t) { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); } +inline ImVec4 ImLerp(const ImVec4& a, const ImVec4& b, float t) { return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t); } +inline float ImSaturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } +inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } +inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } +inline float ImTrunc(float f) { return (float)(int)(f); } +inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } +inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() +inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } +inline float ImTrunc64(float f) { return (float)(ImS64)(f); } +inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); } +inline int ImModPositive(int a, int b) { return (a + b) % b; } +inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } +inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } +inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } +inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x) { return ImSaturate((x - s0) / (s1 - s0)) * (d1 - d0) + d0; } +inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } +inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / n; avg += sample / n; return avg; } IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry @@ -498,6 +551,14 @@ struct ImVec1 constexpr ImVec1(float _x) : x(_x) { } }; +// Helper: ImVec2i (2D vector, integer) +struct ImVec2i +{ + int x, y; + constexpr ImVec2i() : x(0), y(0) {} + constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {} +}; + // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) struct ImVec2ih { @@ -544,6 +605,7 @@ struct IMGUI_API ImRect void Floor() { Min.x = IM_TRUNC(Min.x); Min.y = IM_TRUNC(Min.y); Max.x = IM_TRUNC(Max.x); Max.y = IM_TRUNC(Max.y); } bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } + const ImVec4& AsVec4() const { return *(const ImVec4*)&Min.x; } }; // Helper: ImBitArray @@ -649,6 +711,39 @@ struct ImSpanAllocator inline void GetSpan(int n, ImSpan* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } }; +// Helper: ImStableVector<> +// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear(). +// Important: does not destruct anything! +// Implemented only the minimum set of functions we need for it. +template +struct ImStableVector +{ + int Size = 0; + int Capacity = 0; + ImVector Blocks; + + // Functions + inline ~ImStableVector() { for (T* block : Blocks) IM_FREE(block); } + + inline void clear() { Size = Capacity = 0; Blocks.clear_delete(); } + inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; } + inline void reserve(int new_cap) + { + new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE); + int old_count = Capacity / BLOCK_SIZE; + int new_count = new_cap / BLOCK_SIZE; + if (new_count <= old_count) + return; + Blocks.resize(new_count); + for (int n = old_count; n < new_count; n++) + Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE); + Capacity = new_cap; + } + inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; } + inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; } +}; + // Helper: ImPool<> // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. @@ -709,18 +804,19 @@ struct ImChunkStream // Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API. struct ImGuiTextIndex { - ImVector LineOffsets; + ImVector Offsets; int EndOffset = 0; // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?) - void clear() { LineOffsets.clear(); EndOffset = 0; } - int size() { return LineOffsets.Size; } - const char* get_line_begin(const char* base, int n) { return base + LineOffsets[n]; } - const char* get_line_end(const char* base, int n) { return base + (n + 1 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); } + void clear() { Offsets.clear(); EndOffset = 0; } + int size() { return Offsets.Size; } + const char* get_line_begin(const char* base, int n) { return base + (Offsets.Size != 0 ? Offsets[n] : 0); } + const char* get_line_end(const char* base, int n) { return base + (n + 1 < Offsets.Size ? (Offsets[n + 1] - 1) : EndOffset); } void append(const char* base, int old_size, int new_size); }; // Helper: ImGuiStorage IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStoragePair* in_end, ImGuiID key); + //----------------------------------------------------------------------------- // [SECTION] ImDrawList support //----------------------------------------------------------------------------- @@ -751,28 +847,33 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. // Data shared between all ImDrawList instances -// You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. +// Conceptually this could have been called e.g. ImDrawListSharedContext +// Typically one ImGui context would create and maintain one of this. +// You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData { - ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas - ImFont* Font; // Current/default font (optional, for simplified AddText overload) - float FontSize; // Current/default font size (optional, for simplified AddText overload) - float FontScale; // Current/default font scale (== FontSize / Font->FontSize) + ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImFontAtlas* FontAtlas; // Current font atlas + ImFont* Font; // Current font (used for simplified AddText overload) + float FontSize; // Current font size (used for for simplified AddText overload) + float FontScale; // Current font scale (== FontSize / Font->FontSize) float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc - ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + float InitialFringeScale; // Initial scale to apply to AA fringe ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() + ImVector TempBuffer; // Temporary write buffer + ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData + ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case. - // [Internal] Temp write buffer - ImVector TempBuffer; - - // [Internal] Lookup tables + // Lookup tables ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo() ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); + ~ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); }; @@ -784,18 +885,46 @@ struct ImDrawDataBuilder ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } }; +struct ImFontStackData +{ + ImFont* Font; + float FontSizeBeforeScaling; // ~~ style.FontSizeBase + float FontSizeAfterScaling; // ~~ g.FontSize +}; + //----------------------------------------------------------------------------- -// [SECTION] Data types support +// [SECTION] Style support //----------------------------------------------------------------------------- -struct ImGuiDataVarInfo +struct ImGuiStyleVarInfo { - ImGuiDataType Type; - ImU32 Count; // 1+ - ImU32 Offset; // Offset in parent structure + ImU32 Count : 8; // 1+ + ImGuiDataType DataType : 8; + ImU32 Offset : 16; // Offset in parent structure void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } }; +// Stacked color modifier, backup of modified data so we can restore it +struct ImGuiColorMod +{ + ImGuiCol Col; + ImVec4 BackupValue; +}; + +// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. +struct ImGuiStyleMod +{ + ImGuiStyleVar VarIdx; + union { int BackupInt[2]; float BackupFloat[2]; }; + ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } + ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } +}; + +//----------------------------------------------------------------------------- +// [SECTION] Data types support +//----------------------------------------------------------------------------- + struct ImGuiDataTypeStorage { ImU8 Data[8]; // Opaque storage to fit any data up to ImGuiDataType_COUNT @@ -813,8 +942,7 @@ struct ImGuiDataTypeInfo // Extend ImGuiDataType_ enum ImGuiDataTypePrivate_ { - ImGuiDataType_String = ImGuiDataType_COUNT + 1, - ImGuiDataType_Pointer, + ImGuiDataType_Pointer = ImGuiDataType_COUNT, ImGuiDataType_ID, }; @@ -823,8 +951,8 @@ enum ImGuiDataTypePrivate_ //----------------------------------------------------------------------------- // Extend ImGuiItemFlags -// - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. -// - output: stored in g.LastItemData.InFlags +// - input: PushItemFlag() manipulates g.CurrentItemFlags, g.NextItemData.ItemFlags, ItemAdd() calls may add extra flags too. +// - output: stored in g.LastItemData.ItemFlags enum ImGuiItemFlagsPrivate_ { // Controlled by user @@ -833,6 +961,9 @@ enum ImGuiItemFlagsPrivate_ ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. + ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). + ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited() + ImGuiItemFlags_NoFocus = 1 << 17, // false // [EXPERIMENTAL: Not very well specced] Clicking doesn't take focus. Automatically sets ImGuiButtonFlags_NoFocus + ImGuiButtonFlags_NoNavFocus in ButtonBehavior(). // Controlled by widget code ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. @@ -861,6 +992,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_Visible = 1 << 8, // [WIP] Set when item is overlapping the current clipping rectangle (Used internally. Please don't use yet: API/system will change as we refactor Itemadd()). ImGuiItemStatusFlags_HasClipRect = 1 << 9, // g.LastItemData.ClipRect is valid. ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). + //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE @@ -885,9 +1017,8 @@ enum ImGuiInputTextFlagsPrivate_ { // [Internal] ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() - ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data - ImGuiInputTextFlags_MergedItem = 1 << 28, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. - ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 29, // For internal use by InputScalar() and TempInputScalar() + ImGuiInputTextFlags_MergedItem = 1 << 27, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. + ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 28, // For internal use by InputScalar() and TempInputScalar() }; // Extend ImGuiButtonFlags_ @@ -899,20 +1030,22 @@ enum ImGuiButtonFlagsPrivate_ ImGuiButtonFlags_PressedOnRelease = 1 << 7, // return true on release (default requires click+release) ImGuiButtonFlags_PressedOnDoubleClick = 1 << 8, // return true on double-click (default requires click+release) ImGuiButtonFlags_PressedOnDragDropHold = 1 << 9, // return true when held into while we are drag and dropping another item (used by e.g. tree nodes, collapsing headers) - ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat + //ImGuiButtonFlags_Repeat = 1 << 10, // hold to repeat -> use ImGuiItemFlags_ButtonRepeat instead. ImGuiButtonFlags_FlattenChildren = 1 << 11, // allow interactions even if a child window is overlapping ImGuiButtonFlags_AllowOverlap = 1 << 12, // require previous frame HoveredId to either match id or be null before being usable. - ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press // [UNUSED] + //ImGuiButtonFlags_DontClosePopups = 1 << 13, // disable automatically closing parent popup on press //ImGuiButtonFlags_Disabled = 1 << 14, // disable interactions -> use BeginDisabled() or ImGuiItemFlags_Disabled ImGuiButtonFlags_AlignTextBaseLine = 1 << 15, // vertically align button to match text baseline - ButtonEx() only // FIXME: Should be removed and handled by SmallButton(), not possible currently because of DC.CursorPosPrevLine - ImGuiButtonFlags_NoKeyModifiers = 1 << 16, // disable mouse interaction if a key modifier is held + ImGuiButtonFlags_NoKeyModsAllowed = 1 << 16, // disable mouse interaction if a key modifier is held ImGuiButtonFlags_NoHoldingActiveId = 1 << 17, // don't set ActiveId while holding the mouse (ImGuiButtonFlags_PressedOnClick only) - ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.InFlags) + ImGuiButtonFlags_NoNavFocus = 1 << 18, // don't override navigation focus when activated (FIXME: this is essentially used every time an item uses ImGuiItemFlags_NoNav, but because legacy specs don't requires LastItemData to be set ButtonBehavior(), we can't poll g.LastItemData.ItemFlags) ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) + ImGuiButtonFlags_NoFocus = 1 << 22, // [EXPERIMENTAL: Not very well specced]. Don't focus parent window when clicking. ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, + //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; // Extend ImGuiComboFlags_ @@ -933,7 +1066,6 @@ enum ImGuiSelectableFlagsPrivate_ { // NB: need to be in sync with last value of ImGuiSelectableFlags_ ImGuiSelectableFlags_NoHoldingActiveID = 1 << 20, - ImGuiSelectableFlags_SelectOnNav = 1 << 21, // (WIP) Auto-select when moved into. This is not exposed in public API as to handle multi-select and modifiers we will need user to explicitly control focus scope. May be replaced with a BeginSelection() API. ImGuiSelectableFlags_SelectOnClick = 1 << 22, // Override button behavior to react on Click (default is Click+Release) ImGuiSelectableFlags_SelectOnRelease = 1 << 23, // Override button behavior to react on Release (default is Click+Release) ImGuiSelectableFlags_SpanAvailWidth = 1 << 24, // Span all avail width even if we declared less for layout purpose. FIXME: We may be able to remove this (added in 6251d379, 2bcafc86 for menus) @@ -945,9 +1077,11 @@ enum ImGuiSelectableFlagsPrivate_ // Extend ImGuiTreeNodeFlags_ enum ImGuiTreeNodeFlagsPrivate_ { + ImGuiTreeNodeFlags_NoNavFocus = 1 << 27,// Don't claim nav focus when interacting with this item (#8551) ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader() - ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, but reversed trees (#6517) + ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517) ImGuiTreeNodeFlags_OpenOnMask_ = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow, + ImGuiTreeNodeFlags_DrawLinesMask_ = ImGuiTreeNodeFlags_DrawLinesNone | ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes, }; enum ImGuiSeparatorFlags_ @@ -988,13 +1122,16 @@ enum ImGuiLayoutType_ ImGuiLayoutType_Vertical = 1 }; -enum ImGuiLogType +// Flags for LogBegin() text capturing function +enum ImGuiLogFlags_ { - ImGuiLogType_None = 0, - ImGuiLogType_TTY, - ImGuiLogType_File, - ImGuiLogType_Buffer, - ImGuiLogType_Clipboard, + ImGuiLogFlags_None = 0, + + ImGuiLogFlags_OutputTTY = 1 << 0, + ImGuiLogFlags_OutputFile = 1 << 1, + ImGuiLogFlags_OutputBuffer = 1 << 2, + ImGuiLogFlags_OutputClipboard = 1 << 3, + ImGuiLogFlags_OutputMask_ = ImGuiLogFlags_OutputTTY | ImGuiLogFlags_OutputFile | ImGuiLogFlags_OutputBuffer | ImGuiLogFlags_OutputClipboard, }; // X/Y enums are fixed to 0/1 so they may be used to index ImVec2 @@ -1011,23 +1148,6 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; -// Stacked color modifier, backup of modified data so we can restore it -struct ImGuiColorMod -{ - ImGuiCol Col; - ImVec4 BackupValue; -}; - -// Stacked style modifier, backup of modified data so we can restore it. Data type inferred from the variable. -struct ImGuiStyleMod -{ - ImGuiStyleVar VarIdx; - union { int BackupInt[2]; float BackupFloat[2]; }; - ImGuiStyleMod(ImGuiStyleVar idx, int v) { VarIdx = idx; BackupInt[0] = v; } - ImGuiStyleMod(ImGuiStyleVar idx, float v) { VarIdx = idx; BackupFloat[0] = v; } - ImGuiStyleMod(ImGuiStyleVar idx, ImVec2 v) { VarIdx = idx; BackupFloat[0] = v.x; BackupFloat[1] = v.y; } -}; - // Storage data for BeginComboPreview()/EndComboPreview() struct IMGUI_API ImGuiComboPreviewData { @@ -1053,7 +1173,8 @@ struct IMGUI_API ImGuiGroupData ImVec2 BackupCurrLineSize; float BackupCurrLineTextBaseOffset; ImGuiID BackupActiveIdIsAlive; - bool BackupActiveIdPreviousFrameIsAlive; + bool BackupActiveIdHasBeenEditedThisFrame; + bool BackupDeactivatedIdIsAlive; bool BackupHoveredIdIsAlive; bool BackupIsSameLine; bool EmitItem; @@ -1104,28 +1225,34 @@ struct IMGUI_API ImGuiInputTextState { ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent). ImStbTexteditState* Stb; // State for stb_textedit.h + ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. ImGuiID ID; // widget id owning the text state - int CurLenA; // UTF-8 length of the string in TextA (in bytes) - ImVector TextA; // main UTF8 buffer. - ImVector InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) + int TextLen; // UTF-8 length of the string in TextA (in bytes) + const char* TextSrc; // == TextA.Data unless read-only, in which case == buf passed to InputText(). Field only set and valid _inside_ the call InputText() call. + ImVector TextA; // main UTF8 buffer. TextA.Size is a buffer size! Should always be >= buf_size passed by user (and of course >= CurLenA + 1). + ImVector TextToRevertTo; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) ImVector CallbackTextBackup; // temporary storage for callback to support automatic reconcile of undo-stack - int BufCapacityA; // end-user buffer capacity + int BufCapacity; // end-user buffer capacity (include zero terminator) ImVec2 Scroll; // horizontal offset (managed manually) + vertical scrolling (pulled from child window's own Scroll.y) + int LineCount; // last line count (solely for debugging) + float WrapWidth; // word-wrapping width float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) + bool CursorCenterY; // set when we want scrolling to be centered over the cursor position (while resizing a word-wrapping field) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame - ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. - bool ReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. - int ReloadSelectionStart; // POSITIONS ARE IN IMWCHAR units *NOT* UTF-8 this is why this is not exposed yet. + bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. + ImS8 LastMoveDirectionLR; // ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s + int ReloadSelectionStart; int ReloadSelectionEnd; ImGuiInputTextState(); ~ImGuiInputTextState(); - void ClearText() { CurLenA = 0; TextA[0] = 0; CursorClamp(); } - void ClearFreeMemory() { TextA.clear(); InitialTextA.clear(); } + void ClearText() { TextLen = 0; TextA[0] = 0; CursorClamp(); } + void ClearFreeMemory() { TextA.clear(); TextToRevertTo.clear(); } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation void OnCharPressed(unsigned int c); + float GetPreferredOffsetX() const; // Cursor & Selection void CursorAnimReset(); @@ -1156,6 +1283,12 @@ enum ImGuiWindowRefreshFlags_ // Refresh policy/frequency, Load Balancing etc. }; +enum ImGuiWindowBgClickFlags_ +{ + ImGuiWindowBgClickFlags_None = 0, + ImGuiWindowBgClickFlags_Move = 1 << 0, // Click on bg/void + drag to move window. Cleared by default when using io.ConfigWindowsMoveFromTitleBarOnly. +}; + enum ImGuiNextWindowDataFlags_ { ImGuiNextWindowDataFlags_None = 0, @@ -1167,14 +1300,17 @@ enum ImGuiNextWindowDataFlags_ ImGuiNextWindowDataFlags_HasFocus = 1 << 5, ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6, ImGuiNextWindowDataFlags_HasScroll = 1 << 7, - ImGuiNextWindowDataFlags_HasChildFlags = 1 << 8, - ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 9, + ImGuiNextWindowDataFlags_HasWindowFlags = 1 << 8, + ImGuiNextWindowDataFlags_HasChildFlags = 1 << 9, + ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 10, }; // Storage for SetNexWindow** functions struct ImGuiNextWindowData { - ImGuiNextWindowDataFlags Flags; + ImGuiNextWindowDataFlags HasFlags; + + // Members below are NOT cleared. Always rely on HasFlags. ImGuiCond PosCond; ImGuiCond SizeCond; ImGuiCond CollapsedCond; @@ -1183,6 +1319,7 @@ struct ImGuiNextWindowData ImVec2 SizeVal; ImVec2 ContentSizeVal; ImVec2 ScrollVal; + ImGuiWindowFlags WindowFlags; // Only honored by BeginTable() ImGuiChildFlags ChildFlags; bool CollapsedVal; ImRect SizeConstraintRect; @@ -1193,7 +1330,7 @@ struct ImGuiNextWindowData ImGuiWindowRefreshFlags RefreshFlagsVal; ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } - inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } + inline void ClearFlags() { HasFlags = ImGuiNextWindowDataFlags_None; } }; enum ImGuiNextItemDataFlags_ @@ -1208,9 +1345,10 @@ enum ImGuiNextItemDataFlags_ struct ImGuiNextItemData { - ImGuiNextItemDataFlags Flags; + ImGuiNextItemDataFlags HasFlags; // Called HasFlags instead of Flags to avoid mistaking this ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap and ImGuiItemFlags_HasSelectionUserData. - // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() + + // Members below are NOT cleared by ItemAdd() meaning they are still valid during e.g. NavProcessItem(). Always rely on HasFlags. ImGuiID FocusScopeId; // Set by SetNextItemSelectionUserData() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) float Width; // Set by SetNextItemWidth() @@ -1222,18 +1360,18 @@ struct ImGuiNextItemData ImGuiID StorageId; // Set by SetNextItemStorageID() ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } - inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! + inline void ClearFlags() { HasFlags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! }; // Status storage for the last submitted item struct ImGuiLastItemData { ImGuiID ID; - ImGuiItemFlags InFlags; // See ImGuiItemFlags_ + ImGuiItemFlags ItemFlags; // See ImGuiItemFlags_ (called 'InFlags' before v1.91.4). ImGuiItemStatusFlags StatusFlags; // See ImGuiItemStatusFlags_ ImRect Rect; // Full rectangle ImRect NavRect; // Navigation scoring rectangle (not displayed) - // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags ar set. + // Rarely used fields are not explicitly cleared, only valid when the corresponding ImGuiItemStatusFlags are set. ImRect DisplayRect; // Display rectangle. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) is set. ImRect ClipRect; // Clip rectangle at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasClipRect) is set.. ImGuiKeyChord Shortcut; // Shortcut at the time of submitting item. ONLY VALID IF (StatusFlags & ImGuiItemStatusFlags_HasShortcut) is set.. @@ -1242,15 +1380,18 @@ struct ImGuiLastItemData }; // Store data emitted by TreeNode() for usage by TreePop() -// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data +// - To implement ImGuiTreeNodeFlags_NavLeftJumpsToParent: store the minimum amount of data // which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult(). // Only stored when the node is a potential candidate for landing on a Left arrow jump. struct ImGuiTreeNodeStackData { ImGuiID ID; ImGuiTreeNodeFlags TreeFlags; - ImGuiItemFlags InFlags; // Used for nav landing - ImRect NavRect; // Used for nav landing + ImGuiItemFlags ItemFlags; // Used for nav landing + ImRect NavRect; // Used for nav landing + float DrawLinesX1; + float DrawLinesToNodesY2; + ImGuiTableColumnIdx DrawLinesTableColumn; }; // sizeof() = 20 @@ -1278,6 +1419,7 @@ struct ImGuiWindowStackData ImGuiLastItemData ParentLastItemDataBackup; ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting bool DisabledOverrideReenable; // Non-child window override disabled flag + float DisabledOverrideReenableAlphaBackup; }; struct ImGuiShrinkWidthItem @@ -1296,6 +1438,15 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +// Data used by IsItemDeactivated()/IsItemDeactivatedAfterEdit() functions +struct ImGuiDeactivatedItemData +{ + ImGuiID ID; + int ElapseFrame; + bool HasBeenEditedBefore; + bool IsAlive; +}; + //----------------------------------------------------------------------------- // [SECTION] Popup support //----------------------------------------------------------------------------- @@ -1363,7 +1514,7 @@ enum ImGuiInputEventType ImGuiInputEventType_COUNT }; -enum ImGuiInputSource +enum ImGuiInputSource : int { ImGuiInputSource_None = 0, ImGuiInputSource_Mouse, // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them. @@ -1412,12 +1563,12 @@ struct ImGuiKeyRoutingData { ImGuiKeyRoutingIndex NextEntryIndex; ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. - ImU8 RoutingCurrScore; // [DEBUG] For debug display - ImU8 RoutingNextScore; // Lower is better (0: perfect score) + ImU16 RoutingCurrScore; // [DEBUG] For debug display + ImU16 RoutingNextScore; // Lower is better (0: perfect score) ImGuiID RoutingCurr; ImGuiID RoutingNext; - ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_NoOwner; } + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 0; RoutingCurr = RoutingNext = ImGuiKeyOwner_NoOwner; } }; // Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. @@ -1526,8 +1677,9 @@ enum ImGuiActivateFlags_ ImGuiActivateFlags_PreferInput = 1 << 0, // Favor activation that requires keyboard text input (e.g. for Slider/Drag). Default for Enter key. ImGuiActivateFlags_PreferTweak = 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default for Space key and if keyboard is not used. ImGuiActivateFlags_TryToPreserveState = 1 << 2, // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection) - ImGuiActivateFlags_FromTabbing = 1 << 3, // Activation requested by a tabbing request + ImGuiActivateFlags_FromTabbing = 1 << 3, // Activation requested by a tabbing request (ImGuiNavMoveFlags_IsTabbing) ImGuiActivateFlags_FromShortcut = 1 << 4, // Activation requested by an item shortcut via SetNextItemShortcut() function. + ImGuiActivateFlags_FromFocusApi = 1 << 5, // Activation requested by an api request (ImGuiNavMoveFlags_FocusApi) }; // Early work-in-progress API for ScrollToItem() @@ -1545,12 +1697,19 @@ enum ImGuiScrollFlags_ ImGuiScrollFlags_MaskY_ = ImGuiScrollFlags_KeepVisibleEdgeY | ImGuiScrollFlags_KeepVisibleCenterY | ImGuiScrollFlags_AlwaysCenterY, }; -enum ImGuiNavHighlightFlags_ +enum ImGuiNavRenderCursorFlags_ { - ImGuiNavHighlightFlags_None = 0, - ImGuiNavHighlightFlags_Compact = 1 << 1, // Compact highlight, no padding - ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) _even_ when using the mouse. - ImGuiNavHighlightFlags_NoRounding = 1 << 3, + ImGuiNavRenderCursorFlags_None = 0, + ImGuiNavRenderCursorFlags_Compact = 1 << 1, // Compact highlight, no padding/distance from focused item + ImGuiNavRenderCursorFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) even when g.NavCursorVisible == false, aka even when using the mouse. + ImGuiNavRenderCursorFlags_NoRounding = 1 << 3, +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + ImGuiNavHighlightFlags_None = ImGuiNavRenderCursorFlags_None, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_Compact = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_AlwaysDraw = ImGuiNavRenderCursorFlags_AlwaysDraw, // Renamed in 1.91.4 + ImGuiNavHighlightFlags_NoRounding = ImGuiNavRenderCursorFlags_NoRounding, // Renamed in 1.91.4 + //ImGuiNavHighlightFlags_TypeThin = ImGuiNavRenderCursorFlags_Compact, // Renamed in 1.90.2 +#endif }; enum ImGuiNavMoveFlags_ @@ -1563,7 +1722,7 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_WrapMask_ = ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_WrapY, ImGuiNavMoveFlags_AllowCurrentNavId = 1 << 4, // Allow scoring and considering the current NavId as a move target candidate. This is used when the move source is offset (e.g. pressing PageDown actually needs to send a Up move request, if we are pressing PageDown from the bottom-most item we need to stay in place) ImGuiNavMoveFlags_AlsoScoreVisibleSet = 1 << 5, // Store alternate result in NavMoveResultLocalVisible that only comprise elements that are already fully visible (used by PageUp/PageDown) - ImGuiNavMoveFlags_ScrollToEdgeY = 1 << 6, // Force scrolling to min/max (used by Home/End) // FIXME-NAV: Aim to remove or reword, probably unnecessary + ImGuiNavMoveFlags_ScrollToEdgeY = 1 << 6, // Force scrolling to min/max (used by Home/End) // FIXME-NAV: Aim to remove or reword as ImGuiScrollFlags ImGuiNavMoveFlags_Forwarded = 1 << 7, ImGuiNavMoveFlags_DebugNoResult = 1 << 8, // Dummy scoring for debug purpose, don't apply result ImGuiNavMoveFlags_FocusApi = 1 << 9, // Requests from focus API can land/focus/activate items even if they are marked with _NoTabStop (see NavProcessItemForTabbingRequest() for details) @@ -1571,7 +1730,7 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_IsPageMove = 1 << 11, // Identify a PageDown/PageUp request. ImGuiNavMoveFlags_Activate = 1 << 12, // Activate/select target item. ImGuiNavMoveFlags_NoSelect = 1 << 13, // Don't trigger selection by not setting g.NavJustMovedTo - ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14, // Do not alter the visible state of keyboard vs mouse nav highlight + ImGuiNavMoveFlags_NoSetNavCursorVisible = 1 << 14, // Do not alter the nav cursor visible state ImGuiNavMoveFlags_NoClearActiveId = 1 << 15, // (Experimental) Do not clear active id when applying move result }; @@ -1589,17 +1748,17 @@ struct ImGuiNavItemData ImGuiID ID; // Init,Move // Best candidate item ID ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space - ImGuiItemFlags InFlags; // ????,Move // Best candidate item flags + ImGuiItemFlags ItemFlags; // ????,Move // Best candidate item flags float DistBox; // Move // Best candidate box distance to current NavId float DistCenter; // Move // Best candidate center distance to current NavId float DistAxial; // Move // Best candidate axial distance to current NavId - ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionUserData() value. Valid if (InFlags & ImGuiItemFlags_HasSelectionUserData) + ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionUserData() value. Valid if (ItemFlags & ImGuiItemFlags_HasSelectionUserData) ImGuiNavItemData() { Clear(); } - void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } + void Clear() { Window = NULL; ID = FocusScopeId = 0; ItemFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } }; -// Storage for PushFocusScope() +// Storage for PushFocusScope(), g.FocusScopeStack[], g.NavFocusRoute[] struct ImGuiFocusScopeData { ImGuiID ID; @@ -1900,6 +2059,7 @@ typedef void (*ImGuiErrorCallback)(ImGuiContext* ctx, void* user_data, const cha // [SECTION] Metrics, Debug Tools //----------------------------------------------------------------------------- +// See IMGUI_DEBUG_LOG() and IMGUI_DEBUG_LOG_XXX() macros. enum ImGuiDebugLogFlags_ { // Event types @@ -1912,11 +2072,12 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventClipper = 1 << 5, ImGuiDebugLogFlags_EventSelection = 1 << 6, ImGuiDebugLogFlags_EventIO = 1 << 7, - ImGuiDebugLogFlags_EventInputRouting = 1 << 8, - ImGuiDebugLogFlags_EventDocking = 1 << 9, // Unused in this branch - ImGuiDebugLogFlags_EventViewport = 1 << 10, // Unused in this branch + ImGuiDebugLogFlags_EventFont = 1 << 8, + ImGuiDebugLogFlags_EventInputRouting = 1 << 9, + ImGuiDebugLogFlags_EventDocking = 1 << 10, // Unused in this branch + ImGuiDebugLogFlags_EventViewport = 1 << 11, // Unused in this branch - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventError | ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventFont | ImGuiDebugLogFlags_EventInputRouting | ImGuiDebugLogFlags_EventDocking | ImGuiDebugLogFlags_EventViewport, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine }; @@ -1948,35 +2109,47 @@ struct ImGuiMetricsConfig bool ShowDrawCmdMesh = true; bool ShowDrawCmdBoundingBoxes = true; bool ShowTextEncodingViewer = false; - bool ShowAtlasTintedWithTextColor = false; + bool ShowTextureUsedRect = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; int HighlightMonitorIdx = -1; ImGuiID HighlightViewportID = 0; + bool ShowFontPreview = true; }; struct ImGuiStackLevelInfo { ImGuiID ID; - ImS8 QueryFrameCount; // >= 1: Query in progress - bool QuerySuccess; // Obtained result from DebugHookIdInfo() - ImGuiDataType DataType : 8; - char Desc[57]; // Arbitrarily sized buffer to hold a result (FIXME: could replace Results[] with a chunk stream?) FIXME: Now that we added CTRL+C this should be fixed. + ImS8 QueryFrameCount; // >= 1: Sub-query in progress + bool QuerySuccess; // Sub-query obtained result from DebugHookIdInfo() + ImS8 DataType; // ImGuiDataType + int DescOffset; // -1 or offset into parent's ResultsPathsBuf - ImGuiStackLevelInfo() { memset(this, 0, sizeof(*this)); } + ImGuiStackLevelInfo() { memset(this, 0, sizeof(*this)); DataType = -1; DescOffset = -1; } +}; + +struct ImGuiDebugItemPathQuery +{ + ImGuiID MainID; // ID to query details for. + bool Active; // Used to disambiguate the case when ID == 0 and e.g. some code calls PushOverrideID(0). + bool Complete; // All sub-queries are finished (some may have failed). + ImS8 Step; // -1: query stack + init Results, >= 0: filling individual stack level. + ImVector Results; + ImGuiTextBuffer ResultsDescBuf; + ImGuiTextBuffer ResultPathBuf; + + ImGuiDebugItemPathQuery() { memset(this, 0, sizeof(*this)); } }; // State for ID Stack tool queries struct ImGuiIDStackTool { + bool OptHexEncodeNonAsciiChars; + bool OptCopyToClipboardOnCtrlC; int LastActiveFrame; - int StackLevel; // -1: query stack and resize Results, >= 0: individual stack level - ImGuiID QueryId; // ID to query details for - ImVector Results; - bool CopyToClipboardOnCtrlC; float CopyToClipboardLastTime; - ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); CopyToClipboardLastTime = -FLT_MAX; } + ImGuiIDStackTool() { memset(this, 0, sizeof(*this)); LastActiveFrame = -1; OptHexEncodeNonAsciiChars = true; CopyToClipboardLastTime = -FLT_MAX; } }; //----------------------------------------------------------------------------- @@ -2004,27 +2177,28 @@ struct ImGuiContextHook struct ImGuiContext { bool Initialized; - bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it. + bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() + bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed + bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() + int FrameCount; + int FrameCountEnded; + int FrameCountRendered; + double Time; + char ContextName[16]; // Storage for a context name (to facilitate debugging multi-context setups) ImGuiIO IO; ImGuiPlatformIO PlatformIO; ImGuiStyle Style; - ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() - float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. - float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. - float FontScale; // == FontSize / Font->FontSize - float CurrentDpiScale; // Current window/viewport DpiScale + ImVector FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas) + ImFont* Font; // Currently bound font. (== FontStack.back().Font) + ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize)) + float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function). + float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified. + float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f. + float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked(). + float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale ImDrawListSharedData DrawListSharedData; - double Time; - int FrameCount; - int FrameCountEnded; - int FrameCountRendered; - bool WithinFrameScope; // Set by NewFrame(), cleared by EndFrame() - bool WithinFrameScopeWithImplicitWindow; // Set by NewFrame(), cleared by EndFrame() when the implicit debug window has been pushed - bool WithinEndChild; // Set within EndChild() - bool GcCompactAll; // Request full GC - bool TestEngineHookItems; // Will call test engine hooks: ImGuiTestEngineHook_ItemAdd(), ImGuiTestEngineHook_ItemInfo(), ImGuiTestEngineHook_Log() + ImGuiID WithinEndChildID; // Set within EndChild() void* TestEngine; // Test engine user data - char ContextName[16]; // Storage for a context name (to facilitate debugging multi-context setups) // Inputs ImVector InputEventsQueue; // Input events which will be trickled/written into IO structure. @@ -2039,7 +2213,7 @@ struct ImGuiContext ImVector CurrentWindowStack; ImGuiStorage WindowsById; // Map window's ImGuiID to ImGuiWindow* int WindowsActiveCount; // Number of unique windows submitted by frame - ImVec2 WindowsHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, WINDOWS_HOVER_PADDING). + float WindowsBorderHoverPadding; // Padding around resizable windows for which hovering on counts as hovering the window == ImMax(style.TouchExtraPadding, style.WindowBorderHoverPadding). This isn't so multi-dpi friendly. ImGuiID DebugBreakInWindow; // Set to break in Begin() call. ImGuiWindow* CurrentWindow; // Window being drawn into ImGuiWindow* HoveredWindow; // Window the mouse is hovering. Will typically catch mouse inputs. @@ -2055,8 +2229,8 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information - ImGuiID DebugDrawIdConflicts; // Set when we detect multiple items with the same identifier - ImGuiID DebugHookIdInfo; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] + ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier + ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame ImGuiID HoveredIdPreviousFrame; int HoveredIdPreviousFrameItemCount; // Count numbers of items using the same ID as last frame's hovered id @@ -2075,14 +2249,14 @@ struct ImGuiContext bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. bool ActiveIdHasBeenEditedThisFrame; bool ActiveIdFromShortcut; - int ActiveIdMouseButton : 8; + ImS8 ActiveIdMouseButton; + ImGuiID ActiveIdDisabledId; // When clicking a disabled item we set ActiveId=window->MoveId to avoid interference with widget code. Actual item ID is stored here. ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) - ImGuiWindow* ActiveIdWindow; ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad + ImGuiWindow* ActiveIdWindow; ImGuiID ActiveIdPreviousFrame; - bool ActiveIdPreviousFrameIsAlive; - bool ActiveIdPreviousFrameHasBeenEditedBefore; - ImGuiWindow* ActiveIdPreviousFrameWindow; + ImGuiDeactivatedItemData DeactivatedItemData; + ImGuiDataTypeStorage ActiveIdValueOnActivation; // Backup of initial value at the time of activation. ONLY SET BY SPECIFIC WIDGETS: DragXXX and SliderXXX. ImGuiID LastActiveId; // Store the last non-zero ActiveId, useful for animation. float LastActiveIdTimer; // Store the last non-zero ActiveId timer since the beginning of activation, useful for animation. @@ -2109,12 +2283,13 @@ struct ImGuiContext ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) ImGuiNextWindowData NextWindowData; // Storage for SetNextWindow** functions bool DebugShowGroupRects; + bool GcCompactAll; // Request full GC // Shared stacks ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() - ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() @@ -2125,26 +2300,30 @@ struct ImGuiContext // Viewports ImVector Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. - // Gamepad/keyboard Navigation - ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' + // Keyboard/Gamepad Navigation + bool NavCursorVisible; // Nav focus cursor/rectangle is visible? We hide it after a mouse click. We show it after a nav move. + bool NavHighlightItemUnderNav; // Disable mouse hovering highlight. Highlight navigation focused item instead of mouse hovered item. + //bool NavDisableHighlight; // Old name for !g.NavCursorVisible before 1.91.4 (2024/10/18). OPPOSITE VALUE (g.NavDisableHighlight == !g.NavCursorVisible) + //bool NavDisableMouseHover; // Old name for g.NavHighlightItemUnderNav before 1.91.1 (2024/10/18) this was called When user starts using keyboard/gamepad, we hide mouse hovering highlight until mouse is touched again. + bool NavMousePosDirty; // When set we will update mouse position if io.ConfigNavMoveSetMousePos is set (not enabled by default) + bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid ImGuiID NavId; // Focused item for navigation + ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) ImGuiNavLayer NavLayer; // Focused layer (main scrolling layer, or menu/title bar layer) - ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() + ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItemByID() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavHighlightActivatedId; float NavHighlightActivatedTimer; - ImGuiID NavNextActivateId; // Set by ActivateItem(), queued until next frame. + ImGuiID NavNextActivateId; // Set by ActivateItemByID(), queued until next frame. ImGuiActivateFlags NavNextActivateFlags; - ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Mouse + ImGuiInputSource NavInputSource; // Keyboard or Gamepad mode? THIS CAN ONLY BE ImGuiInputSource_Keyboard or ImGuiInputSource_Gamepad ImGuiSelectionUserData NavLastValidSelectionUserData; // Last valid data passed to SetNextItemSelectionUser(), or -1. For current window. Not reset when focusing an item that doesn't have selection data. - bool NavIdIsAlive; // Nav widget has been seen this frame ~~ NavRectRel is valid - bool NavMousePosDirty; // When set we will update mouse position if (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) if set (NB: this not enabled by default) - bool NavDisableHighlight; // When user starts using mouse, we hide gamepad/keyboard highlight (NB: but they are still available, which is why NavDisableHighlight isn't always != NavDisableMouseHover) - bool NavDisableMouseHover; // When user starts using gamepad/keyboard, we hide mouse hovering highlight until mouse is touched again. + ImS8 NavCursorHideFrames; + //ImGuiID NavActivateInputId; // Removed in 1.89.4 (July 2023). This is now part of g.NavActivateId and sets g.NavActivateFlags |= ImGuiActivateFlags_PreferInput. See commit c9a53aa74, issue #5606. // Navigation: Init & Move Requests bool NavAnyRequest; // ~~ NavMoveRequest || NavInitRequest this is to perform early out in ItemAdd() @@ -2176,23 +2355,25 @@ struct ImGuiContext ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyChord NavJustMovedToKeyMods; bool NavJustMovedToIsTabbing; // Copy of ImGuiNavMoveFlags_IsTabbing. Maybe we should store whole flags. - bool NavJustMovedToHasSelectionData; // Copy of move result's InFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. + bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. - // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) + // Navigation: Windowing (Ctrl+Tab for list, or Menu button + keys or directional pads to move/resize) + bool ConfigNavWindowingWithGamepad; // = true. Enable Ctrl+Tab by holding ImGuiKey_GamepadFaceLeft (== ImGuiKey_NavGamepadMenu). When false, the button may still be used to toggle Menu layer. ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) - ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! + ImGuiWindow* NavWindowingTarget; // Target window when doing Ctrl+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! ImGuiWindow* NavWindowingTargetAnim; // Record of last valid NavWindowingTarget until DimBgRatio and NavWindowingHighlightAlpha becomes 0.0f, so the fade-out can stay on it. - ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents + ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the Ctrl+Tab contents float NavWindowingTimer; float NavWindowingHighlightAlpha; - bool NavWindowingToggleLayer; - ImGuiKey NavWindowingToggleKey; + ImGuiInputSource NavWindowingInputSource; + bool NavWindowingToggleLayer; // Set while Alt or GamepadMenu is held, may be cleared by other operations, and processed when releasing the key. + ImGuiKey NavWindowingToggleKey; // Keyboard/gamepad key used when toggling to menu layer. ImVec2 NavWindowingAccumDeltaPos; ImVec2 NavWindowingAccumDeltaSize; // Render - float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) + float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and Ctrl+Tab list) // Drag and Drop bool DragDropActive; @@ -2205,7 +2386,9 @@ struct ImGuiContext ImRect DragDropTargetRect; // Store rectangle of current target candidate (we favor small targets when overlapping) ImRect DragDropTargetClipRect; // Store ClipRect at the time of item's drawing ImGuiID DragDropTargetId; - ImGuiDragDropFlags DragDropAcceptFlags; + ImGuiID DragDropTargetFullViewport; + ImGuiDragDropFlags DragDropAcceptFlagsCurr; + ImGuiDragDropFlags DragDropAcceptFlagsPrev; float DragDropAcceptIdCurrRectSurface; // Target item surface (we resolve overlapping targets by prioritizing the smaller surface) ImGuiID DragDropAcceptIdCurr; // Target item id (set at the time of accepting the payload) ImGuiID DragDropAcceptIdPrev; // Target item id from previous frame (we need to store this to allow for overlapping drag and drop targets) @@ -2255,9 +2438,11 @@ struct ImGuiContext // Widget state ImGuiInputTextState InputTextState; + ImGuiTextIndex InputTextLineIndex; // Temporary storage ImGuiInputTextDeactivatedState InputTextDeactivatedState; - ImFont InputTextPasswordFont; - ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + ImFontBaked InputTextPasswordFontBackupBaked; + ImFontFlags InputTextPasswordFontBackupFlags; + ImGuiID TempInputId; // Temporary text input when using Ctrl+Click on a slider, etc. ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types int BeginMenuDepth; int BeginComboDepth; @@ -2281,7 +2466,6 @@ struct ImGuiContext float DragSpeedDefaultRatio; // If speed == 0.0f, uses (max-min) * DragSpeedDefaultRatio float DisabledAlphaBackup; // Backup for style.Alpha for BeginDisabled() short DisabledStackSize; - short LockMarkEdited; short TooltipOverrideCount; ImGuiWindow* TooltipPreviousWindow; // Window of last tooltip submitted during the frame ImVector ClipboardHandlerData; // If no custom clipboard handler is defined @@ -2289,9 +2473,13 @@ struct ImGuiContext ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest() // Platform support - ImGuiPlatformImeData PlatformImeData; // Data updated by current frame + ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message. ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. + // Extensions + // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImVector UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[]. + // Settings bool SettingsLoaded; float SettingsDirtyTimer; // Save .ini Settings to memory when time reaches zero @@ -2307,13 +2495,14 @@ struct ImGuiContext // Capture/Logging bool LogEnabled; // Currently capturing - ImGuiLogType LogType; // Capture target + bool LogLineFirstItem; + ImGuiLogFlags LogFlags; // Capture flags/type + ImGuiWindow* LogWindow; ImFileHandle LogFile; // If != NULL log to stdout/ file ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators. - const char* LogNextPrefix; + const char* LogNextPrefix; // See comment in LogSetNextTextDecoration(): doesn't copy underlying data, use carefully! const char* LogNextSuffix; float LogLinePosY; - bool LogLineFirstItem; int LogDepthRef; int LogDepthToExpand; int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. @@ -2329,7 +2518,7 @@ struct ImGuiContext // Debug Tools // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) - int DebugDrawIdConflictsCount; // Locked count (preserved when holding CTRL) + int DebugDrawIdConflictsCount; // Locked count (preserved when holding Ctrl) ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; @@ -2346,8 +2535,13 @@ struct ImGuiContext float DebugFlashStyleColorTime; ImVec4 DebugFlashStyleColorBackup; ImGuiMetricsConfig DebugMetricsConfig; + ImGuiDebugItemPathQuery DebugItemPathQuery; ImGuiIDStackTool DebugIDStackTool; ImGuiDebugAllocInfo DebugAllocInfo; +#if defined(IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS) && !defined(IMGUI_DISABLE_DEBUG_TOOLS) + ImGuiStorage DebugDrawIdConflictsAliveCount; + ImGuiStorage DebugDrawIdConflictsHighlightSet; +#endif // Misc float FramerateSecPerFrame[60]; // Calculate estimate of framerate for user over the last 60 frames.. @@ -2356,11 +2550,12 @@ struct ImGuiContext float FramerateSecPerFrameAccum; int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. int WantCaptureKeyboardNextFrame; // " - int WantTextInputNextFrame; + int WantTextInputNextFrame; // Copied in EndFrame() from g.PlatformImeData.WantTextInput. Needs to be set for some backends (SDL3) to emit character inputs. ImVector TempBuffer; // Temporary text buffer char TempKeychordName[64]; ImGuiContext(ImFontAtlas* shared_font_atlas); + ~ImGuiContext(); }; //----------------------------------------------------------------------------- @@ -2402,7 +2597,8 @@ struct IMGUI_API ImGuiWindowTempData ImVec2 MenuBarOffset; // MenuBarOffset.x is sort of equivalent of a per-layer CursorPos.x, saved/restored as we switch to the menu bar. The only situation when MenuBarOffset.y is > 0 if when (SafeAreaPadding.y > FramePadding.y), often used on TVs. ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement int TreeDepth; // Current tree depth. - ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. + ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set. ImVector ChildWindows; ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiOldColumns* CurrentColumns; // Current columns set @@ -2410,6 +2606,8 @@ struct IMGUI_API ImGuiWindowTempData ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() ImU32 ModalDimBgColor; + ImGuiItemStatusFlags WindowItemStatusFlags; + ImGuiItemStatusFlags ChildItemStatusFlags; // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. @@ -2452,6 +2650,8 @@ struct IMGUI_API ImGuiWindow ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar. bool ScrollbarX, ScrollbarY; // Are scrollbars visible? + bool ScrollbarXStabilizeEnabled; // Was ScrollbarX previously auto-stabilized? + ImU8 ScrollbarXStabilizeToggledHistory; // Used to stabilize scrollbar visibility in case of feedback loops bool Active; // Set to true on Begin(), unless Collapsed bool WasActive; bool WriteAccessed; // Set to true when any widget access the current window @@ -2471,13 +2671,14 @@ struct IMGUI_API ImGuiWindow short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. + ImGuiDir AutoPosLastDirection; ImS8 AutoFitFramesX, AutoFitFramesY; bool AutoFitOnlyGrows; - ImGuiDir AutoPosLastDirection; ImS8 HiddenFramesCanSkipItems; // Hide the window for N frames ImS8 HiddenFramesCannotSkipItems; // Hide the window for N frames while allowing items to be submitted so we can measure their size ImS8 HiddenFramesForRenderOnly; // Hide the window until frame N at Render() time only ImS8 DisableInputsFrames; // Disable window interactions for N frames + ImGuiWindowBgClickFlags BgClickFlags : 8; // Configure behavior of click+dragging on window bg/void or over items. Default sets by io.ConfigWindowsMoveFromTitleBarOnly. If you use this please report in #3379. ImGuiCond SetWindowPosAllowFlags : 8; // store acceptable condition flags for SetNextWindowPos() use. ImGuiCond SetWindowSizeAllowFlags : 8; // store acceptable condition flags for SetNextWindowSize() use. ImGuiCond SetWindowCollapsedAllowFlags : 8; // store acceptable condition flags for SetNextWindowCollapsed() use. @@ -2505,6 +2706,8 @@ struct IMGUI_API ImGuiWindow ImGuiStorage StateStorage; ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() + float FontWindowScaleParents; + float FontRefSize; // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window. int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) @@ -2515,7 +2718,7 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* RootWindowPopupTree; // Point to ourself or first ancestor that is not a child window. Cross through popups parent<>child. ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. - ImGuiWindow* ParentWindowForFocusRoute; // Set to manual link a window to its logical parent so that Shortcut() chain are honoerd (e.g. Tool linked to Document) + ImGuiWindow* ParentWindowForFocusRoute; // Set to manual link a window to its logical parent so that Shortcut() chain are honored (e.g. Tool linked to Document) ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) @@ -2539,9 +2742,11 @@ struct IMGUI_API ImGuiWindow // We don't use g.FontSize because the window may be != g.CurrentWindow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } - float CalcFontSize() const { ImGuiContext& g = *Ctx; float scale = g.FontBaseSize * FontWindowScale; if (ParentWindow) scale *= ParentWindow->FontWindowScale; return scale; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } + + // [OBSOLETE] ImGuiWindow::CalcFontSize() was removed in 1.92.0 because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window. + //float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontSizeBase * FontWindowScale * FontWindowScaleParents; }; //----------------------------------------------------------------------------- @@ -2562,6 +2767,8 @@ enum ImGuiTabItemFlagsPrivate_ ImGuiTabItemFlags_SectionMask_ = ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing, ImGuiTabItemFlags_NoCloseButton = 1 << 20, // Track whether p_open was set or not (we'll need this info on the next frame to recompute ContentWidth during layout) ImGuiTabItemFlags_Button = 1 << 21, // Used by TabItemButton, change the tab item behavior to mimic a button + ImGuiTabItemFlags_Invisible = 1 << 22, // To reserve space e.g. with ImGuiTabItemFlags_Leading + //ImGuiTabItemFlags_Unsorted = 1 << 23, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. }; // Storage for one active tab item (sizeof() 40 bytes) @@ -2573,7 +2780,7 @@ struct ImGuiTabItem int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance float Offset; // Position relative to beginning of tab float Width; // Width currently displayed - float ContentWidth; // Width of label, stored during BeginTabItem() call + float ContentWidth; // Width of label + padding, stored during BeginTabItem() call (misnamed as "Content" would normally imply width of label only) float RequestedWidth; // Width optionally requested by caller, -1.0f is unused ImS32 NameOffset; // When Window==NULL, offset to name within parent ImGuiTabBar::TabsNames ImS16 BeginOrder; // BeginTabItem() order, used to re-order tabs after toggling ImGuiTabBarFlags_Reorderable @@ -2592,10 +2799,11 @@ struct IMGUI_API ImGuiTabBar ImGuiID ID; // Zero for tab-bars used by docking ImGuiID SelectedTabId; // Selected tab/window ImGuiID NextSelectedTabId; // Next selected tab/window. Will also trigger a scrolling animation - ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview) + ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for Ctrl+Tab preview) int CurrFrameVisible; int PrevFrameVisible; ImRect BarRect; + float BarRectPrevWidth; // Backup of previous width. When width change we enforce keep horizontal scroll on focused tab. float CurrTabsContentsHeight; float PrevTabsContentsHeight; // Record the height of contents submitted below the tab bar float WidthAllTabs; // Actual width of all tabs (locked during layout) @@ -2614,6 +2822,7 @@ struct IMGUI_API ImGuiTabBar bool WantLayout; bool VisibleTabWasSubmitted; bool TabsAddedNew; // Set to true when a new tab item or button has been added to the tab bar during last frame + bool ScrollButtonEnabled; ImS16 TabsActiveCount; // Number of tabs submitted this frame. ImS16 LastTabItemIdx; // Index of last BeginTabItem() tab for use by EndTabItem() float ItemSpacingY; @@ -2629,11 +2838,7 @@ struct IMGUI_API ImGuiTabBar //----------------------------------------------------------------------------- #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. -#define IMGUI_TABLE_MAX_COLUMNS 512 // May be further lifted - -// Our current column maximum is 64 but we may raise that in the future. -typedef ImS16 ImGuiTableColumnIdx; -typedef ImU16 ImGuiTableDrawChannelIdx; +#define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx. // [Internal] sizeof() ~ 112 // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. @@ -2735,7 +2940,7 @@ struct IMGUI_API ImGuiTable { ImGuiID ID; ImGuiTableFlags Flags; - void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[] and RowCellData[] + void* RawData; // Single allocation to hold Columns[], DisplayOrderToIndex[], and RowCellData[] ImGuiTableTempData* TempData; // Transient data while table is active. Point within g.CurrentTableStack[] ImSpan Columns; // Point within RawData[] ImSpan DisplayOrderToIndex; // Point within RawData[]. Store display order of columns (when not reordered, the values are 0...Count-1) @@ -2749,7 +2954,7 @@ struct IMGUI_API ImGuiTable int ColumnsCount; // Number of columns declared in BeginTable() int CurrentRow; int CurrentColumn; - ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple table with same ID look are multiple tables, they are just synched. + ImS16 InstanceCurrent; // Count of BeginTable() calls with same ID in the same frame (generally 0). This is a little bit similar to BeginCount for a window, but multiple tables with the same ID are multiple tables, they are just synced. ImS16 InstanceInteracted; // Mark which instance (generally 0) of the same ID is being interacted with float RowPosY1; float RowPosY2; @@ -2781,7 +2986,7 @@ struct IMGUI_API ImGuiTable float AngledHeadersHeight; // Set by TableAngledHeadersRow(), used in TableUpdateLayout() float AngledHeadersSlope; // Set by TableAngledHeadersRow(), used in TableUpdateLayout() ImRect OuterRect; // Note: for non-scrolling table, OuterRect.Max.y is often FLT_MAX until EndTable(), unless a height has been specified in BeginTable(). - ImRect InnerRect; // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is + ImRect InnerRect; // InnerRect but without decoration. As with OuterRect, for non-scrolling tables, InnerRect.Max.y is " ImRect WorkRect; ImRect InnerClipRect; ImRect BgClipRect; // We use this to cpu-clip cell background color fill, evolve during the frame as we cross frozen rows boundaries @@ -2825,15 +3030,16 @@ struct IMGUI_API ImGuiTable ImGuiTableDrawChannelIdx DummyDrawChannel; // Redirect non-visible columns here. ImGuiTableDrawChannelIdx Bg2DrawChannelCurrent; // For Selectable() and other widgets drawing across columns after the freezing line. Index within DrawSplitter.Channels[] ImGuiTableDrawChannelIdx Bg2DrawChannelUnfrozen; + ImS8 NavLayer; // ImGuiNavLayer at the time of BeginTable(). bool IsLayoutLocked; // Set by TableUpdateLayout() which is called when beginning the first row. bool IsInsideRow; // Set when inside TableBeginRow()/TableEndRow(). bool IsInitializing; bool IsSortSpecsDirty; bool IsUsingHeaders; // Set when the first row had the ImGuiTableRowFlags_Headers flag. bool IsContextPopupOpen; // Set when default context menu is open (also see: ContextPopupColumn, InstanceInteracted). - bool DisableDefaultContextMenu; // Disable default context menu contents. You may submit your own using TableBeginContextMenuPopup()/EndPopup() + bool DisableDefaultContextMenu; // Disable default context menu. You may submit your own using TableBeginContextMenuPopup()/EndPopup() bool IsSettingsRequestLoad; - bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSetttings data. + bool IsSettingsDirty; // Set when table settings have changed and needs to be reported into ImGuiTableSettings data. bool IsDefaultDisplayOrder; // Set when display order is unchanged from default (DisplayOrder contains 0...Count-1) bool IsResetAllRequest; bool IsResetDisplayOrderRequest; @@ -2857,6 +3063,7 @@ struct IMGUI_API ImGuiTable // sizeof() ~ 136 bytes. struct IMGUI_API ImGuiTableTempData { + ImGuiID WindowID; // Shortcut to g.Tables[TableIndex]->OuterWindow->ID. int TableIndex; // Index in g.Tables.Buf[] pool float LastTimeActive; // Last timestamp this structure was used float AngledHeadersExtraWidth; // Used in EndTable() @@ -2877,7 +3084,7 @@ struct IMGUI_API ImGuiTableTempData ImGuiTableTempData() { memset(this, 0, sizeof(*this)); LastTimeActive = -1.0f; } }; -// sizeof() ~ 12 +// sizeof() ~ 16 struct ImGuiTableColumnSettings { float WidthOrWeight; @@ -2886,7 +3093,7 @@ struct ImGuiTableColumnSettings ImGuiTableColumnIdx DisplayOrder; ImGuiTableColumnIdx SortOrder; ImU8 SortDirection : 2; - ImU8 IsEnabled : 1; // "Visible" in ini file + ImS8 IsEnabled : 2; // "Visible" in ini file ImU8 IsStretch : 1; ImGuiTableColumnSettings() @@ -2896,7 +3103,7 @@ struct ImGuiTableColumnSettings Index = -1; DisplayOrder = SortOrder = -1; SortDirection = ImGuiSortDirection_None; - IsEnabled = 1; + IsEnabled = -1; IsStretch = 0; } }; @@ -2927,6 +3134,8 @@ namespace ImGui // If this ever crashes because g.CurrentWindow is NULL, it means that either: // - ImGui::NewFrame() has never been called, which is illegal. // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. + IMGUI_API ImGuiIO& GetIO(ImGuiContext* ctx); + IMGUI_API ImGuiPlatformIO& GetPlatformIO(ImGuiContext* ctx); inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); @@ -2935,6 +3144,7 @@ namespace ImGui IMGUI_API void UpdateWindowSkipRefresh(ImGuiWindow* window); IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow* window); IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy); + IMGUI_API bool IsWindowInBeginStack(ImGuiWindow* window); IMGUI_API bool IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent); IMGUI_API bool IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below); IMGUI_API bool IsWindowNavFocusable(ImGuiWindow* window); @@ -2963,8 +3173,18 @@ namespace ImGui IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); // Fonts, drawing - IMGUI_API void SetCurrentFont(ImFont* font); - inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture. EXPERIMENTAL: DO NOT USE YET. + IMGUI_API void UnregisterUserTexture(ImTextureData* tex); + IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas); + IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling); + IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling); + IMGUI_API void SetFontRasterizerDensity(float rasterizer_density); + inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; } + inline float GetRoundedFontSize(float size) { return IM_ROUND(size); } + IMGUI_API ImFont* GetDefaultFont(); + IMGUI_API void PushPasswordFont(); + IMGUI_API void PopPasswordFont(); inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { IM_UNUSED(window); return GetForegroundDrawList(); } // This seemingly unnecessary wrapper simplifies compatibility between the 'master' and 'docking' branches. IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. @@ -2976,9 +3196,10 @@ namespace ImGui // NewFrame IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); - IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); + IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); + IMGUI_API void StopMouseMovingWindow(); IMGUI_API void UpdateMouseMovingWindowNewFrame(); IMGUI_API void UpdateMouseMovingWindowEndFrame(); @@ -2988,6 +3209,7 @@ namespace ImGui IMGUI_API void CallContextHooks(ImGuiContext* context, ImGuiContextHookType type); // Viewports + IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); IMGUI_API void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport); // Settings @@ -3024,7 +3246,7 @@ namespace ImGui // Basic Accessors inline ImGuiItemStatusFlags GetItemStatusFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.StatusFlags; } - inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.InFlags; } + inline ImGuiItemFlags GetItemFlags() { ImGuiContext& g = *GImGui; return g.LastItemData.ItemFlags; } inline ImGuiID GetActiveID() { ImGuiContext& g = *GImGui; return g.ActiveId; } inline ImGuiID GetFocusID() { ImGuiContext& g = *GImGui; return g.NavId; } IMGUI_API void SetActiveID(ImGuiID id, ImGuiWindow* window); @@ -3045,19 +3267,20 @@ namespace ImGui IMGUI_API bool ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flags); IMGUI_API bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags = 0); IMGUI_API bool IsClippedEx(const ImRect& bb, ImGuiID id); - IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); + IMGUI_API void SetLastItemData(ImGuiID item_id, ImGuiItemFlags item_flags, ImGuiItemStatusFlags status_flags, const ImRect& item_rect); IMGUI_API ImVec2 CalcItemSize(ImVec2 size, float default_w, float default_h); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); - IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); + IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min); + IMGUI_API void CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end); // Parameter stacks (shared) - IMGUI_API const ImGuiDataVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); + IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); IMGUI_API void BeginDisabledOverrideReenable(); IMGUI_API void EndDisabledOverrideReenable(); // Logging/Capture - IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. + IMGUI_API void LogBegin(ImGuiLogFlags flags, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name. IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix); @@ -3067,6 +3290,7 @@ namespace ImGui // Popups, Modals IMGUI_API bool BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_window_flags); + IMGUI_API bool BeginPopupMenuEx(ImGuiID id, const char* label, ImGuiWindowFlags extra_window_flags); IMGUI_API void OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_None); IMGUI_API void ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup); IMGUI_API void ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup); @@ -3093,20 +3317,20 @@ namespace ImGui IMGUI_API bool BeginComboPreview(); IMGUI_API void EndComboPreview(); - // Gamepad/Keyboard Navigation + // Keyboard/Gamepad Navigation IMGUI_API void NavInitWindow(ImGuiWindow* window, bool force_reinit); IMGUI_API void NavInitRequestApplyResult(); IMGUI_API bool NavMoveRequestButNoResultYet(); IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); - IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data); + IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, const ImGuiTreeNodeStackData* tree_node_data); IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); IMGUI_API void NavHighlightActivated(ImGuiID id); IMGUI_API void NavClearPreferredPosForAxis(ImGuiAxis axis); - IMGUI_API void NavRestoreHighlightAfterMove(); + IMGUI_API void SetNavCursorVisibleAfterMove(); IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); @@ -3116,7 +3340,7 @@ namespace ImGui // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are // much harder to design and implement than expected. I have a couple of private branches on this matter but it's not simple. For now implementing the easy ones. IMGUI_API void FocusItem(); // Focus last item (no selection/activation). - IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. + IMGUI_API void ActivateItemByID(ImGuiID id); // Activate an item by ID (button, checkbox, tree node etc.). Activation is queued and processed on the next frame when the item is encountered again. Was called 'ActivateItem()' before 1.89.7. // Inputs // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. @@ -3176,7 +3400,7 @@ namespace ImGui // Legacy functions use ImGuiKeyOwner_Any meaning that they typically ignore ownership, unless a call to SetKeyOwner() explicitly used ImGuiInputFlags_LockThisFrame or ImGuiInputFlags_LockUntilRelease. // - Binding generators may want to ignore those for now, or suffix them with Ex() until we decide if this gets moved into public API. IMGUI_API bool IsKeyDown(ImGuiKey key, ImGuiID owner_id); - IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requiress explicit ImGuiInputFlags_Repeat. + IMGUI_API bool IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0); // Important: when transitioning from old to new IsKeyPressed(): old API has "bool repeat = true", so would default to repeat. New API requires explicit ImGuiInputFlags_Repeat. IMGUI_API bool IsKeyReleased(ImGuiKey key, ImGuiID owner_id); IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id = 0); IMGUI_API bool IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id); @@ -3218,11 +3442,15 @@ namespace ImGui // Drag and Drop IMGUI_API bool IsDragDropActive(); IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); + IMGUI_API bool BeginDragDropTargetViewport(ImGuiViewport* viewport, const ImRect* p_bb = NULL); IMGUI_API void ClearDragDrop(); IMGUI_API bool IsDragDropPayloadBeingAccepted(); - IMGUI_API void RenderDragDropTargetRect(const ImRect& bb, const ImRect& item_clip_rect); + IMGUI_API void RenderDragDropTargetRectForItem(const ImRect& bb); + IMGUI_API void RenderDragDropTargetRectEx(ImDrawList* draw_list, const ImRect& bb); // Typing-Select API + // (provide Windows Explorer style "select items by typing partial name" + "cycle through items by typing same letter" feature) + // (this is currently not documented nor used by main library, but should work. See "widgets_typingselect" in imgui_test_suite for usage code. Please let us know if you use this!) IMGUI_API ImGuiTypingSelectRequest* GetTypingSelectRequest(ImGuiTypingSelectFlags flags = ImGuiTypingSelectFlags_None); IMGUI_API int TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); IMGUI_API int TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx); @@ -3261,6 +3489,8 @@ namespace ImGui IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); + IMGUI_API void TablePushColumnChannel(int column_n); + IMGUI_API void TablePopColumnChannel(); IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); // Tables: Internals @@ -3310,6 +3540,8 @@ namespace ImGui // Tab Bars inline ImGuiTabBar* GetCurrentTabBar() { ImGuiContext& g = *GImGui; return g.CurrentTabBar; } + IMGUI_API ImGuiTabBar* TabBarFindByID(ImGuiID id); + IMGUI_API void TabBarRemove(ImGuiTabBar* tab_bar); IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags); IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); IMGUI_API ImGuiTabItem* TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order); @@ -3324,6 +3556,7 @@ namespace ImGui IMGUI_API void TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, ImVec2 mouse_pos); IMGUI_API bool TabBarProcessReorder(ImGuiTabBar* tab_bar); IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window); + IMGUI_API void TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width); IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker); IMGUI_API ImVec2 TabItemCalcSize(ImGuiWindow* window); IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); @@ -3336,11 +3569,14 @@ namespace ImGui IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); - IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); + IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); - IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_None); // Navigation highlight + IMGUI_API void RenderNavCursor(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None); // Navigation highlight +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + inline void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavRenderCursorFlags flags = ImGuiNavRenderCursorFlags_None) { RenderNavCursor(bb, id, flags); } // Renamed in 1.91.4 +#endif IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API void RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow); @@ -3352,11 +3588,15 @@ namespace ImGui IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); - // Widgets + // Widgets: Text IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); + IMGUI_API void TextAligned(float align_x, float size_x, const char* fmt, ...); // FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) + IMGUI_API void TextAlignedV(float align_x, float size_x, const char* fmt, va_list args); + + // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); - IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); + IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); @@ -3366,7 +3606,7 @@ namespace ImGui IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos); IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); IMGUI_API void Scrollbar(ImGuiAxis axis); - IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags flags); + IMGUI_API bool ScrollbarEx(const ImRect& bb, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 avail_v, ImS64 contents_v, ImDrawFlags draw_rounding_flags = 0); IMGUI_API ImRect GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API ImGuiID GetWindowResizeCornerID(ImGuiWindow* window, int n); // 0..3: corners @@ -3380,6 +3620,8 @@ namespace ImGui // Widgets: Tree Nodes IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); + IMGUI_API void TreeNodeDrawLineToChildNode(const ImVec2& target_pos); + IMGUI_API void TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data); IMGUI_API void TreePushOverrideID(ImGuiID id); IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); @@ -3412,6 +3654,7 @@ namespace ImGui inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.ActiveId == id && g.TempInputId == id); } inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data); + inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field. // Color IMGUI_API void ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags); @@ -3459,7 +3702,9 @@ namespace ImGui IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeFont(ImFont* font); + IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); + IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture. IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); @@ -3477,9 +3722,8 @@ namespace ImGui // Obsolete functions #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 - inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 - + //inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 + //inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 //inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): @@ -3494,28 +3738,194 @@ namespace ImGui //----------------------------------------------------------------------------- -// [SECTION] ImFontAtlas internal API +// [SECTION] ImFontLoader //----------------------------------------------------------------------------- -// This structure is likely to evolve as we add support for incremental atlas updates -struct ImFontBuilderIO +// Hooks and storage for a given font backend. +// This structure is likely to evolve as we add support for incremental atlas updates. +// Conceptually this could be public, but API is still going to be evolve. +struct ImFontLoader { - bool (*FontBuilder_Build)(ImFontAtlas* atlas); + const char* Name; + bool (*LoaderInit)(ImFontAtlas* atlas); + void (*LoaderShutdown)(ImFontAtlas* atlas); + bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src); + void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src); + bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint); + bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src); + bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph, float* out_advance_x); + + // Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations. + // FIXME: At this point the two other types of buffers may be managed by core to be consistent? + size_t FontBakedSrcLoaderDataSize; + + ImFontLoader() { memset(this, 0, sizeof(*this)); } }; -// Helper for font builder #ifdef IMGUI_ENABLE_STB_TRUETYPE -IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); +IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype(); +#endif +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper. +#endif + +//----------------------------------------------------------------------------- +// [SECTION] ImFontAtlas internal API +//----------------------------------------------------------------------------- + +#define IMGUI_FONT_SIZE_MAX (512.0f) +#define IMGUI_FONT_SIZE_THRESHOLD_FOR_LOADADVANCEXONLYMODE (128.0f) + +// Helpers: ImTextureRef ==/!= operators provided as convenience +// (note that _TexID and _TexData are never set simultaneously) +inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; } +inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; } + +// Refer to ImFontAtlasPackGetRect() to better understand how this works. +#define ImFontAtlasRectId_IndexMask_ (0x0007FFFF) // 20-bits signed: index to access builder->RectsIndex[]. +#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers. +#define ImFontAtlasRectId_GenerationShift_ (20) +inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_IndexMask_); } +inline unsigned int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (unsigned int)(id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; } +inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx >= 0 && index_idx <= ImFontAtlasRectId_IndexMask_ && gen_idx <= (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); } + +// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles) +// User are returned ImFontAtlasRectId values which are meant to be persistent. +// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId. +// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code. +// Having this also makes it easier to e.g. sort rectangles during repack. +struct ImFontAtlasRectEntry +{ + int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list. + unsigned int Generation : 10; // Increased each time the entry is reused for a new rectangle. + unsigned int IsUsed : 1; +}; + +// Data available to potential texture post-processing functions +struct ImFontAtlasPostProcessData +{ + ImFontAtlas* FontAtlas; + ImFont* Font; + ImFontConfig* FontSrc; + ImFontBaked* FontBaked; + ImFontGlyph* Glyph; + + // Pixel data + void* Pixels; + ImTextureFormat Format; + int Pitch; + int Width; + int Height; +}; + +// We avoid dragging imstb_rectpack.h into public header (partly because binding generators are having issues with it) +#ifdef IMGUI_STB_NAMESPACE +namespace IMGUI_STB_NAMESPACE { struct stbrp_node; } +typedef IMGUI_STB_NAMESPACE::stbrp_node stbrp_node_im; +#else +struct stbrp_node; +typedef stbrp_node stbrp_node_im; #endif -IMGUI_API void ImFontAtlasUpdateConfigDataPointers(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); -IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); -IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); -IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); -IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); +struct stbrp_context_opaque { char data[80]; }; + +// Internal storage for incrementally packing and building a ImFontAtlas +struct ImFontAtlasBuilder +{ + stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file. + ImVector PackNodes; + ImVector Rects; + ImVector RectsIndex; // ImFontAtlasRectId -> index into Rects[] + ImVector TempBuffer; // Misc scratch buffer + int RectsIndexFreeListStart;// First unused entry + int RectsPackedCount; // Number of packed rectangles. + int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size. + int RectsDiscardedCount; + int RectsDiscardedSurface; + int FrameCount; // Current frame count + ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size") + ImVec2i MaxRectBounds; // Bottom-right most used pixels + bool LockDisableResize; // Disable resizing texture + bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything. + + // Cache of all ImFontBaked + ImStableVector BakedPool; + ImGuiStorage BakedMap; // BakedId --> ImFontBaked* + int BakedDiscardedCount; + + // Custom rectangle identifiers + ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. + ImFontAtlasRectId PackIdLinesTexData; + + ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } +}; + +IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader); +IMGUI_API void ImFontAtlasBuildNotifySetFont(ImFontAtlas* atlas, ImFont* old_font, ImFont* new_font); +IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char); +IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects + +IMGUI_API ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h); +IMGUI_API void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_w = -1, int old_h = -1); +IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas); +IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy +IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v); +IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames); + +IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src); +IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src); +IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed +IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font); +IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames); + +IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density); +IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id); +IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked); +IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph); +IMGUI_API void ImFontAtlasBakedAddFontGlyphAdvancedX(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImWchar codepoint, float advance_x); +IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph); +IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch); + +IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas); +IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id); +IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id); + +IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures); +IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data); +IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex); +IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas); + +IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data); +IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor); +IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col); +IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h); +IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h); + +IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format); +IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status); +IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format); + +#ifndef IMGUI_DISABLE_DEBUG_TOOLS +IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas); +#endif + +IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); //----------------------------------------------------------------------------- // [SECTION] Test Engine specific hooks (imgui_test_engine) @@ -3532,7 +3942,7 @@ extern const char* ImGuiTestEngine_FindItemDebugLabel(ImGuiContext* ctx, ImGuiI #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) if (g.TestEngineHookItems) ImGuiTestEngineHook_ItemInfo(&g, _ID, _LABEL, _FLAGS) // Register item label and status flags (optional) #define IMGUI_TEST_ENGINE_LOG(_FMT,...) ImGuiTestEngineHook_Log(&g, _FMT, __VA_ARGS__) // Custom log entry from user land into test log #else -#define IMGUI_TEST_ENGINE_ITEM_ADD(_BB,_ID) ((void)0) +#define IMGUI_TEST_ENGINE_ITEM_ADD(_ID,_BB,_ITEM_DATA) ((void)0) #define IMGUI_TEST_ENGINE_ITEM_INFO(_ID,_LABEL,_FLAGS) ((void)g) #endif diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_tables.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_tables.cpp" index 64126f41..41fdf6a4 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_tables.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_tables.cpp" @@ -1,4 +1,4 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (tables and columns code) /* @@ -24,9 +24,9 @@ Index of this file: */ // Navigating this file: -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. -// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. +// - In Visual Studio: Ctrl+Comma ("Edit.GoToAll") can follow symbols inside comments, whereas Ctrl+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. //----------------------------------------------------------------------------- // [SECTION] Commentary @@ -221,6 +221,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat" // warning: format specifies type 'int' but the argument has type 'unsigned int' #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 @@ -229,9 +230,15 @@ Index of this file: #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' +#pragma GCC diagnostic ignored "-Wstrict-overflow" #pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #endif @@ -335,6 +342,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG { ItemSize(outer_rect); ItemAdd(outer_rect, id); + g.NextWindowData.ClearFlags(); return false; } @@ -369,6 +377,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->ColumnsCount = columns_count; table->IsLayoutLocked = false; table->InnerWidth = inner_width; + table->NavLayer = (ImS8)outer_window->DC.NavLayerCurrent; temp_data->UserOuterSize = outer_size; // Instance data (for instance 0, TableID == TableInstanceID) @@ -409,11 +418,15 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Reset scroll if we are reactivating it if ((previous_flags & (ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY)) == 0) - SetNextWindowScroll(ImVec2(0.0f, 0.0f)); + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasScroll) == 0) + SetNextWindowScroll(ImVec2(0.0f, 0.0f)); // Create scrolling region (without border and zero window padding) - ImGuiWindowFlags child_window_flags = (flags & ImGuiTableFlags_ScrollX) ? ImGuiWindowFlags_HorizontalScrollbar : ImGuiWindowFlags_None; - BeginChildEx(name, instance_id, outer_rect.GetSize(), ImGuiChildFlags_None, child_window_flags); + ImGuiChildFlags child_child_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : ImGuiChildFlags_None; + ImGuiWindowFlags child_window_flags = (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasWindowFlags) ? g.NextWindowData.WindowFlags : ImGuiWindowFlags_None; + if (flags & ImGuiTableFlags_ScrollX) + child_window_flags |= ImGuiWindowFlags_HorizontalScrollbar; + BeginChildEx(name, instance_id, outer_rect.GetSize(), child_child_flags, child_window_flags); table->InnerWindow = g.CurrentWindow; table->WorkRect = table->InnerWindow->WorkRect; table->OuterRect = table->InnerWindow->Rect(); @@ -424,7 +437,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG if (table->InnerWindow->SkipItems && outer_window_is_measuring_size) table->InnerWindow->SkipItems = false; - // When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned) + // When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned if (instance_no == 0) { table->HasScrollbarYPrev = table->HasScrollbarYCurr; @@ -438,6 +451,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable(). table->WorkRect = table->OuterRect = table->InnerRect = outer_rect; table->HasScrollbarYPrev = table->HasScrollbarYCurr = false; + table->InnerWindow->DC.TreeDepth++; // This is designed to always linking ImGuiTreeNodeFlags_DrawLines linking across a table } // Push a standardized ID for both child-using and not-child-using tables @@ -450,6 +464,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->HostIndentX = inner_window->DC.Indent.x; table->HostClipRect = inner_window->ClipRect; table->HostSkipItems = inner_window->SkipItems; + temp_data->WindowID = inner_window->ID; temp_data->HostBackupWorkRect = inner_window->WorkRect; temp_data->HostBackupParentWorkRect = inner_window->ParentWorkRect; temp_data->HostBackupColumnsOffset = outer_window->DC.ColumnsOffset; @@ -528,7 +543,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Make table current g.CurrentTable = table; - outer_window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); + inner_window->DC.NavIsScrollPushableX = false; // Shortcut for NavUpdateCurrentWindowIsScrollPushableX(); outer_window->DC.CurrentTableIdx = table_idx; if (inner_window != outer_window) // So EndChild() within the inner window can restore the table properly. inner_window->DC.CurrentTableIdx = table_idx; @@ -566,6 +581,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // Initialize table->SettingsOffset = -1; table->IsSortSpecsDirty = true; + table->IsSettingsDirty = true; // Records itself into .ini file even when in default state (#7934) table->InstanceInteracted = -1; table->ContextPopupColumn = -1; table->ReorderColumn = table->ResizedColumn = table->LastResizedColumn = -1; @@ -931,7 +947,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // (e.g. TextWrapped) too much. Otherwise what tends to happen is that TextWrapped would output a very // large height (= first frame scrollbar display very off + clipper would skip lots of items). // This is merely making the side-effect less extreme, but doesn't properly fixes it. - // FIXME: Move this to ->WidthGiven to avoid temporary lossyless? + // FIXME: Move this to ->WidthGiven to avoid temporary lossyness? // FIXME: This break IsPreserveWidthAuto from not flickering if the stored WidthAuto was smaller. if (column->AutoFitQueue > 0x01 && table->IsInitializing && !column->IsPreserveWidthAuto) column->WidthRequest = ImMax(column->WidthRequest, table->MinColumnWidth * 4.0f); // FIXME-TABLE: Another constant/scale? @@ -965,7 +981,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 4] Apply final widths based on requested widths const ImRect work_rect = table->WorkRect; const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1); - const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synched tables with mismatching scrollbar state (#5920) + const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synced tables with mismatching scrollbar state (#5920) const float width_avail = ImMax(1.0f, (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed); const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests; float width_remaining_for_stretched_columns = width_avail_for_stretched_columns; @@ -1044,7 +1060,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) const int column_n = table->DisplayOrderToIndex[order_n]; ImGuiTableColumn* column = &table->Columns[column_n]; - column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); // Use Count NOT request so Header line changes layer when frozen + // Initial nav layer: using FreezeRowsCount, NOT FreezeRowsRequest, so Header line changes layer when frozen + column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : (ImGuiNavLayer)table->NavLayer); if (offset_x_frozen && table->FreezeColumnsCount == visible_n) { @@ -1160,7 +1177,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // Don't decrement auto-fit counters until container window got a chance to submit its items - if (table->HostSkipItems == false) + if (table->HostSkipItems == false && table->InstanceCurrent == 0) { column->AutoFitQueue >>= 1; column->CannotSkipItemsQueue >>= 1; @@ -1174,7 +1191,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } // In case the table is visible (e.g. decorations) but all columns clipped, we keep a column visible. - // Else if give no chance to a clipper-savy user to submit rows and therefore total contents height used by scrollbar. + // Else if give no chance to a clipper-savvy user to submit rows and therefore total contents height used by scrollbar. if (has_at_least_one_column_requesting_output == false) { table->Columns[table->LeftMostEnabledColumn].IsRequestOutput = true; @@ -1235,7 +1252,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // [Part 11] Default context menu // - To append to this menu: you can call TableBeginContextMenuPopup()/.../EndPopup(). - // - To modify or replace this: set table->IsContextPopupNoDefaultContents = true, then call TableBeginContextMenuPopup()/.../EndPopup(). + // - To modify or replace this: set table->DisableDefaultContextMenu = true, then call TableBeginContextMenuPopup()/.../EndPopup(). // - You may call TableDrawDefaultContextMenu() with selected flags to display specific sections of the default menu, // e.g. TableDrawDefaultContextMenu(table, table->Flags & ~ImGuiTableFlags_Hideable) will display everything EXCEPT columns visibility options. if (table->DisableDefaultContextMenu == false && TableBeginContextMenuPopup(table)) @@ -1331,7 +1348,11 @@ void ImGui::EndTable() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Only call EndTable() if BeginTable() returns true!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "EndTable() call should only be done while in BeginTable() scope!"); + return; + } // This assert would be very useful to catch a common error... unfortunately it would probably trigger in some // cases, and for consistency user may sometimes output empty tables (and still benefit from e.g. outer border) @@ -1346,7 +1367,7 @@ void ImGui::EndTable() ImGuiWindow* inner_window = table->InnerWindow; ImGuiWindow* outer_window = table->OuterWindow; ImGuiTableTempData* temp_data = table->TempData; - IM_ASSERT(inner_window == g.CurrentWindow); + IM_ASSERT(inner_window == g.CurrentWindow && inner_window->ID == temp_data->WindowID); IM_ASSERT(outer_window == inner_window || outer_window == inner_window->ParentWindow); if (table->IsInsideRow) @@ -1373,7 +1394,7 @@ void ImGui::EndTable() // Setup inner scrolling range // FIXME: This ideally should be done earlier, in BeginTable() SetNextWindowContentSize call, just like writing to inner_window->DC.CursorMaxPos.y, - // but since the later is likely to be impossible to do we'd rather update both axises together. + // but since the later is likely to be impossible to do we'd rather update both axes together. if (table->Flags & ImGuiTableFlags_ScrollX) { const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; @@ -1483,7 +1504,7 @@ void ImGui::EndTable() if (inner_window != outer_window) { short backup_nav_layers_active_mask = inner_window->DC.NavLayersActiveMask; - inner_window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; // So empty table don't appear to navigate differently. + inner_window->DC.NavLayersActiveMask |= 1 << table->NavLayer; // So empty table don't appear to navigate differently. g.CurrentTable = NULL; // To avoid error recovery recursing EndChild(); g.CurrentTable = table; @@ -1491,6 +1512,7 @@ void ImGui::EndTable() } else { + table->InnerWindow->DC.TreeDepth--; ItemSize(table->OuterRect.GetSize()); ItemAdd(table->OuterRect, 0); } @@ -1538,7 +1560,7 @@ void ImGui::EndTable() IM_ASSERT(g.CurrentWindow == outer_window && g.CurrentTable == table); IM_ASSERT(g.TablesTempDataStacked > 0); temp_data = (--g.TablesTempDataStacked > 0) ? &g.TablesTempData[g.TablesTempDataStacked - 1] : NULL; - g.CurrentTable = temp_data ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; + g.CurrentTable = temp_data && (temp_data->WindowID == outer_window->ID) ? g.Tables.GetByIndex(temp_data->TableIndex) : NULL; if (g.CurrentTable) { g.CurrentTable->TempData = temp_data; @@ -1548,14 +1570,43 @@ void ImGui::EndTable() NavUpdateCurrentWindowIsScrollPushableX(); } +// Called in TableSetupColumn() when initializing and in TableLoadSettings() for defaults before applying stored settings. +// 'init_mask' specify which fields to initialize. +static void TableInitColumnDefaults(ImGuiTable* table, ImGuiTableColumn* column, ImGuiTableColumnFlags init_mask) +{ + ImGuiTableColumnFlags flags = column->Flags; + if (init_mask & ImGuiTableFlags_Resizable) + { + float init_width_or_weight = column->InitStretchWeightOrWidth; + column->WidthRequest = ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; + column->StretchWeight = (init_width_or_weight > 0.0f && (flags & ImGuiTableColumnFlags_WidthStretch)) ? init_width_or_weight : -1.0f; + if (init_width_or_weight > 0.0f) // Disable auto-fit if an explicit width/weight has been specified + column->AutoFitQueue = 0x00; + } + if (init_mask & ImGuiTableFlags_Reorderable) + column->DisplayOrder = (ImGuiTableColumnIdx)table->Columns.index_from_ptr(column); + if (init_mask & ImGuiTableFlags_Hideable) + column->IsUserEnabled = column->IsUserEnabledNextFrame = (flags & ImGuiTableColumnFlags_DefaultHide) ? 0 : 1; + if (init_mask & ImGuiTableFlags_Sortable) + { + // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. + column->SortOrder = (flags & ImGuiTableColumnFlags_DefaultSort) ? 0 : -1; + column->SortDirection = (flags & ImGuiTableColumnFlags_DefaultSort) ? ((flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending)) : (ImS8)ImGuiSortDirection_None; + } +} + // See "COLUMNS SIZING POLICIES" comments at the top of this file // If (init_width_or_weight <= 0.0f) it is ignored void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, float init_width_or_weight, ImGuiID user_id) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); - IM_ASSERT(table->IsLayoutLocked == false && "Need to call call TableSetupColumn() before first row!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); if (table->DeclColumnsCount >= table->ColumnsCount) { @@ -1572,7 +1623,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo IM_ASSERT(init_width_or_weight <= 0.0f && "Can only specify width/weight if sizing policy is set explicitly in either Table or Column."); // When passing a width automatically enforce WidthFixed policy - // (whereas TableSetupColumnFlags would default to WidthAuto if table is not Resizable) + // (whereas TableSetupColumnFlags would default to WidthAuto if table is not resizable) if ((flags & ImGuiTableColumnFlags_WidthMask_) == 0 && init_width_or_weight > 0.0f) if ((table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedFit || (table->Flags & ImGuiTableFlags_SizingMask_) == ImGuiTableFlags_SizingFixedSame) flags |= ImGuiTableColumnFlags_WidthFixed; @@ -1590,27 +1641,10 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo column->InitStretchWeightOrWidth = init_width_or_weight; if (table->IsInitializing) { - // Init width or weight + ImGuiTableFlags init_flags = ~table->SettingsLoadedFlags; if (column->WidthRequest < 0.0f && column->StretchWeight < 0.0f) - { - if ((flags & ImGuiTableColumnFlags_WidthFixed) && init_width_or_weight > 0.0f) - column->WidthRequest = init_width_or_weight; - if (flags & ImGuiTableColumnFlags_WidthStretch) - column->StretchWeight = (init_width_or_weight > 0.0f) ? init_width_or_weight : -1.0f; - - // Disable auto-fit if an explicit width/weight has been specified - if (init_width_or_weight > 0.0f) - column->AutoFitQueue = 0x00; - } - - // Init default visibility/sort state - if ((flags & ImGuiTableColumnFlags_DefaultHide) && (table->SettingsLoadedFlags & ImGuiTableFlags_Hideable) == 0) - column->IsUserEnabled = column->IsUserEnabledNextFrame = false; - if (flags & ImGuiTableColumnFlags_DefaultSort && (table->SettingsLoadedFlags & ImGuiTableFlags_Sortable) == 0) - { - column->SortOrder = 0; // Multiple columns using _DefaultSort will be reassigned unique SortOrder values when building the sort specs. - column->SortDirection = (column->Flags & ImGuiTableColumnFlags_PreferSortDescending) ? (ImS8)ImGuiSortDirection_Descending : (ImU8)(ImGuiSortDirection_Ascending); - } + init_flags |= ImGuiTableFlags_Resizable; + TableInitColumnDefaults(table, column, init_flags); } // Store name (append with zero-terminator in contiguous buffer) @@ -1619,7 +1653,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo if (label != NULL && label[0] != 0) { column->NameOffset = (ImS16)table->ColumnsNames.size(); - table->ColumnsNames.append(label, label + strlen(label) + 1); + table->ColumnsNames.append(label, label + ImStrlen(label) + 1); } } @@ -1628,7 +1662,11 @@ void ImGui::TableSetupScrollFreeze(int columns, int rows) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableSetupColumn() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->IsLayoutLocked == false && "Need to call TableSetupColumn() before first row!"); IM_ASSERT(columns >= 0 && columns < IMGUI_TABLE_MAX_COLUMNS); IM_ASSERT(rows >= 0 && rows < 128); // Arbitrary limit @@ -1705,9 +1743,11 @@ void ImGui::TableSetColumnEnabled(int column_n, bool enabled) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - if (!table) + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); return; + } IM_ASSERT(table->Flags & ImGuiTableFlags_Hideable); // See comments above if (column_n < 0) column_n = table->CurrentColumn; @@ -1786,6 +1826,11 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; IM_ASSERT(target != ImGuiTableBgTarget_None); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } if (color == IM_COL32_DISABLE) color = 0; @@ -1914,7 +1959,10 @@ void ImGui::TableEndRow(ImGuiTable* table) IM_ASSERT(table->IsInsideRow); if (table->CurrentColumn != -1) + { TableEndCell(table); + table->CurrentColumn = -1; + } // Logging if (g.LogEnabled) @@ -2007,12 +2055,13 @@ void ImGui::TableEndRow(ImGuiTable* table) } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) - // We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark end of row and - // get the new cursor position. + // - We need to do that in TableEndRow() instead of TableBeginRow() so the list clipper can mark + // end of row and get the new cursor position. if (unfreeze_rows_request) { + IM_ASSERT(table->FreezeRowsRequest > 0); for (int column_n = 0; column_n < table->ColumnsCount; column_n++) - table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main; + table->Columns[column_n].NavLayerCurrent = table->NavLayer; const float y0 = ImMax(table->RowPosY2 + 1, table->InnerClipRect.Min.y); table_instance->LastFrozenHeight = y0 - table->OuterRect.Min.y; @@ -2079,7 +2128,11 @@ bool ImGui::TableSetColumnIndex(int column_n) { if (table->CurrentColumn != -1) TableEndCell(table); - IM_ASSERT(column_n >= 0 && table->ColumnsCount); + if ((column_n >= 0 && column_n < table->ColumnsCount) == false) + { + IM_ASSERT_USER_ERROR(column_n >= 0 && column_n < table->ColumnsCount, "TableSetColumnIndex() invalid column index!"); + return false; + } TableBeginCell(table, column_n); } @@ -2150,6 +2203,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n) g.LastItemData.StatusFlags = 0; } + // Also see TablePushColumnChannel() if (table->Flags & ImGuiTableFlags_NoClip) { // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. @@ -2423,10 +2477,38 @@ void ImGui::TablePopBackgroundChannel() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; ImGuiTable* table = g.CurrentTable; - ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; // Optimization: avoid PopClipRect() + SetCurrentChannel() SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[table->CurrentColumn].DrawChannelCurrent); +} + +// Also see TableBeginCell() +void ImGui::TablePushColumnChannel(int column_n) +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid SetCurrentChannel() + PushClipRect() + if (table->Flags & ImGuiTableFlags_NoClip) + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[column_n]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); + table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); +} + +void ImGui::TablePopColumnChannel() +{ + ImGuiContext& g = *GImGui; + ImGuiTable* table = g.CurrentTable; + + // Optimization: avoid PopClipRect() + SetCurrentChannel() + if ((table->Flags & ImGuiTableFlags_NoClip) || (table->CurrentColumn == -1)) // Calling TreePop() after TableNextRow() is supported. + return; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiTableColumn* column = &table->Columns[table->CurrentColumn]; + SetWindowClipRectBeforeSetChannel(window, column->ClipRect); table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); } @@ -2444,7 +2526,7 @@ void ImGui::TablePopBackgroundChannel() // - NoClip --> 2+D+1 channels: bg0/1 + bg2 + foreground (same clip rect == always 1 draw call) // - Clip --> 2+D+N channels // - FreezeRows --> 2+D+N*2 (unless scrolling value is zero) -// - FreezeRows || FreezeColunns --> 3+D+N*2 (unless scrolling value is zero) +// - FreezeRows || FreezeColumns --> 3+D+N*2 (unless scrolling value is zero) // Where D is 1 if any column is clipped or hidden (dummy channel) otherwise 0. void ImGui::TableSetupDrawChannels(ImGuiTable* table) { @@ -2737,8 +2819,13 @@ void ImGui::TableDrawBorders(ImGuiTable* table) continue; // Draw in outer window so right-most column won't be clipped - // Always draw full height border when being resized/hovered, or on the delimitation of frozen column scrolling. - float draw_y2 = (is_hovered || is_resized || is_frozen_separator || (table->Flags & (ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_NoBordersInBodyUntilResize)) == 0) ? draw_y2_body : draw_y2_head; + float draw_y2 = draw_y2_head; + if (is_frozen_separator) + draw_y2 = draw_y2_body; + else if ((table->Flags & ImGuiTableFlags_NoBordersInBodyUntilResize) != 0 && (is_hovered || is_resized)) + draw_y2 = draw_y2_body; + else if ((table->Flags & (ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_NoBordersInBody)) == 0) + draw_y2 = draw_y2_body; if (draw_y2 > draw_y1) inner_drawlist->AddLine(ImVec2(column->MaxX, draw_y1), ImVec2(column->MaxX, draw_y2), TableGetColumnBorderCol(table, order_n, column_n), border_size); } @@ -2801,9 +2888,7 @@ ImGuiTableSortSpecs* ImGui::TableGetSortSpecs() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL); - - if (!(table->Flags & ImGuiTableFlags_Sortable)) + if (table == NULL || !(table->Flags & ImGuiTableFlags_Sortable)) return NULL; // Require layout (in case TableHeadersRow() hasn't been called) as it may alter IsSortSpecsDirty in some paths. @@ -3028,7 +3113,11 @@ void ImGui::TableHeadersRow() { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } // Call layout if not already done. This is automatically done by TableNextRow: we do it here _only_ to make // it easier to debug-step in TableUpdateLayout(). Your own version of this function doesn't need this. @@ -3073,7 +3162,12 @@ void ImGui::TableHeader(const char* label) return; ImGuiTable* table = g.CurrentTable; - IM_ASSERT(table != NULL && "Need to call TableHeader() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } + IM_ASSERT(table->CurrentColumn != -1); const int column_n = table->CurrentColumn; ImGuiTableColumn* column = &table->Columns[column_n]; @@ -3139,7 +3233,7 @@ void ImGui::TableHeader(const char* label) if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); } - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding); if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; @@ -3194,7 +3288,7 @@ void ImGui::TableHeader(const char* label) // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will // be merged into a single draw call. //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, bb.Max.y), ellipsis_max, label, label_end, &label_size); const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); if (text_clipped && hovered && g.ActiveId == 0) @@ -3248,7 +3342,11 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImGuiTable* table = g.CurrentTable; ImGuiWindow* window = g.CurrentWindow; ImDrawList* draw_list = window->DrawList; - IM_ASSERT(table != NULL && "Need to call TableHeadersRow() after BeginTable()!"); + if (table == NULL) + { + IM_ASSERT_USER_ERROR(table != NULL, "Call should only be done while in BeginTable() scope!"); + return; + } IM_ASSERT(table->CurrentRow == -1 && "Must be first row"); if (max_label_width == 0.0f) @@ -3287,13 +3385,13 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better + const float ascent_scaled = g.FontBaked->Ascent * g.FontBakedScale; // FIXME: Standardize those scaling factors better const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 align = g.Style.TableAngledHeadersTextAlign; // Draw background and labels in first pass, then all borders. - float max_x = 0.0f; + float max_x = -FLT_MAX; for (int pass = 0; pass < 2; pass++) for (int order_n = 0; order_n < data_count; order_n++) { @@ -3323,7 +3421,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label // Left<>Right alignment float line_off_curr_x = flip_label ? (label_lines - 1) * line_off_step_x : 0.0f; - float line_off_for_align_x = ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x; + float line_off_for_align_x = ImFloor(ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x); line_off_curr_x += line_off_for_align_x - line_off_for_ascent_x; // Register header width @@ -3342,7 +3440,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); int vtx_idx_begin = draw_list->_VtxCurrentIdx; PushStyleColor(ImGuiCol_Text, request->TextColor); - RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); + RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, label_name, label_name_eol, &label_size); PopStyleColor(); int vtx_idx_end = draw_list->_VtxCurrentIdx; @@ -3679,6 +3777,14 @@ void ImGui::TableLoadSettings(ImGuiTable* table) table->SettingsLoadedFlags = settings->SaveFlags; table->RefScale = settings->RefScale; + // Initialize default columns settings + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + TableInitColumnDefaults(table, column, ~0); + column->AutoFitQueue = 0x00; + } + // Serialize ImGuiTableSettings/ImGuiTableColumnSettings into ImGuiTable/ImGuiTableColumn ImGuiTableColumnSettings* column_settings = settings->GetColumnSettings(); ImU64 display_order_mask = 0; @@ -3695,14 +3801,12 @@ void ImGui::TableLoadSettings(ImGuiTable* table) column->StretchWeight = column_settings->WidthOrWeight; else column->WidthRequest = column_settings->WidthOrWeight; - column->AutoFitQueue = 0x00; } if (settings->SaveFlags & ImGuiTableFlags_Reorderable) column->DisplayOrder = column_settings->DisplayOrder; - else - column->DisplayOrder = (ImGuiTableColumnIdx)column_n; display_order_mask |= (ImU64)1 << column->DisplayOrder; - column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled; + if ((settings->SaveFlags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1) + column->IsUserEnabled = column->IsUserEnabledNextFrame = column_settings->IsEnabled == 1; column->SortOrder = column_settings->SortOrder; column->SortDirection = column_settings->SortDirection; } @@ -3798,8 +3902,7 @@ static void TableSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandle const bool save_visible = (settings->SaveFlags & ImGuiTableFlags_Hideable) != 0; const bool save_order = (settings->SaveFlags & ImGuiTableFlags_Reorderable) != 0; const bool save_sort = (settings->SaveFlags & ImGuiTableFlags_Sortable) != 0; - if (!save_size && !save_visible && !save_order && !save_sort) - continue; + // We need to save the [Table] entry even if all the bools are false, since this records a table with "default settings". buf->reserve(buf->size() + 30 + settings->ColumnsCount * 50); // ballpark reserve buf->appendf("[%s][0x%08X,%d]\n", handler->TypeName, settings->ID, settings->ColumnsCount); @@ -3846,7 +3949,7 @@ void ImGui::TableSettingsAddSettingsHandler() // - TableGcCompactSettings() [Internal] //------------------------------------------------------------------------- -// Remove Table (currently only used by TestEngine) +// Remove Table data (currently only used by TestEngine) void ImGui::TableRemove(ImGuiTable* table) { //IMGUI_DEBUG_PRINT("TableRemove() id=0x%08X\n", table->ID); @@ -3925,9 +4028,9 @@ void ImGui::DebugNodeTable(ImGuiTable* table) bool open = TreeNode(table, "Table 0x%08X (%d columns, in '%s')%s", table->ID, table->ColumnsCount, table->OuterWindow->Name, is_active ? "" : " *Inactive*"); if (!is_active) { PopStyleColor(); } if (IsItemHovered()) - GetForegroundDrawList()->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); + GetForegroundDrawList(table->OuterWindow)->AddRect(table->OuterRect.Min, table->OuterRect.Max, IM_COL32(255, 255, 0, 255)); if (IsItemVisible() && table->HoveredColumnBody != -1) - GetForegroundDrawList()->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); + GetForegroundDrawList(table->OuterWindow)->AddRect(GetItemRectMin(), GetItemRectMax(), IM_COL32(255, 255, 0, 255)); if (!open) return; if (table->InstanceCurrent > 0) @@ -3981,7 +4084,7 @@ void ImGui::DebugNodeTable(ImGuiTable* table) if (IsItemHovered()) { ImRect r(column->MinX, table->OuterRect.Min.y, column->MaxX, table->OuterRect.Max.y); - GetForegroundDrawList()->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); + GetForegroundDrawList(table->OuterWindow)->AddRect(r.Min, r.Max, IM_COL32(255, 255, 0, 255)); } } if (ImGuiTableSettings* settings = TableGetBoundSettings(table)) diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle.cpp" index e28b9fc3..c53e2b2d 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle.cpp" @@ -34,6 +34,7 @@ namespace bool ImGui::Toggle(const char* label, bool* v, const ImVec2& size /*= ImVec2()*/) { ::SetToAliasDefaults(::_internalConfig); + ::_internalConfig.Size = size; return ::ToggleInternal(label, v, ::_internalConfig); } diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.cpp" index 416c9300..02682dde 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.cpp" @@ -1,4 +1,4 @@ -#include "imgui_toggle_renderer.h" +#include "imgui_toggle_renderer.h" #include "imgui_toggle_palette.h" #include "imgui_toggle_math.h" @@ -7,517 +7,519 @@ using namespace ImGuiToggleMath; namespace { - // a small helper to quickly check the mixed value flag. - inline bool IsItemMixedValue() - { + // a small helper to quickly check the mixed value flag. + inline bool IsItemMixedValue() + { #if IMGUI_VERSION_NUM >= 19135 - return (GImGui->LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; + return (GImGui->LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; #else - return (GImGui->LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; + return (GImGui->LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; #endif - } + } } // namespace ImGuiToggleRenderer::ImGuiToggleRenderer() { - SetConfig(nullptr, nullptr, ImGuiToggleConfig()); + SetConfig(nullptr, nullptr, ImGuiToggleConfig()); } ImGuiToggleRenderer::ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config) : _style(nullptr), _label(label), _value(value) { - SetConfig(label, value, user_config); + SetConfig(label, value, user_config); } void ImGuiToggleRenderer::SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config) { - // store mandatory settings - _label = label; - _value = value; + // store mandatory settings + _label = label; + _value = value; - // copy our user's config and ensure it's valid. - _config = user_config; - ValidateConfig(); + // copy our user's config and ensure it's valid. + _config = user_config; + ValidateConfig(); } bool ImGuiToggleRenderer::Render() { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - - IM_ASSERT(window); - IM_ASSERT(_label != nullptr); - IM_ASSERT(_value != nullptr); - - if (window->SkipItems) - { - return false; - } - - // update igui context - ImGuiContext& g = *GImGui; - _id = window->GetID(_label); - _drawList = ImGui::GetWindowDrawList(); - _style = &ImGui::GetStyle(); - - // calculate the size of the toggle portion - const float height = _config.Size.y > 0 - ? _config.Size.y - : ImGui::GetFrameHeight(); - const float width = _config.Size.x > 0 - ? _config.Size.x - : height * _config.WidthRatio; - - // get the position of the widget and how large the label should be - ImVec2 widget_position = window->DC.CursorPos; - ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); - - // if the knob is offset horizontally outside of the frame in the on state, we want to bump our label over. - const float label_x_offset = ImMax(0.0f, -_config.On.KnobOffset.x / 2.0f); - - // calculate bounding boxes for the toggle, and the whole widget including the label for interaction - _boundingBox = ImRect(widget_position, widget_position + ImVec2(width, height)); - ImRect total_bounding_box = ImRect(widget_position, - widget_position - + ImVec2( - width + (label_size.x > 0.0f ? _style->ItemInnerSpacing.x + label_size.x : 0.0f) + label_x_offset, - ImMax(height, label_size.y) + _style->FramePadding.y * 2.0f - )); - - // handle the toggle input behavior - bool pressed = ToggleBehavior(total_bounding_box); - _isMixedValue = ::IsItemMixedValue(); - - // draw the toggle itself and the label - DrawToggle(); - DrawLabel(label_x_offset); - - IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); - return pressed; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + + IM_ASSERT(window); + IM_ASSERT(_label != nullptr); + IM_ASSERT(_value != nullptr); + + if (window->SkipItems) + { + return false; + } + + // update igui context + ImGuiContext& g = *GImGui; + _id = window->GetID(_label); + _drawList = ImGui::GetWindowDrawList(); + _style = &ImGui::GetStyle(); + + // calculate the size of the toggle portion + const float height = _config.Size.y > 0 + ? _config.Size.y + : ImGui::GetFrameHeight(); + const float width = _config.Size.x > 0 + ? _config.Size.x + : height * _config.WidthRatio; + + // get the position of the widget and how large the label should be + ImVec2 widget_position = window->DC.CursorPos; + ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); + + // if the knob is offset horizontally outside of the frame in the on state, we want to bump our label over. + const float label_x_offset = ImMax(0.0f, -_config.On.KnobOffset.x / 2.0f); + + // calculate bounding boxes for the toggle, and the whole widget including the label for interaction + _boundingBox = ImRect(widget_position, widget_position + ImVec2(width, height)); + ImRect total_bounding_box = ImRect(widget_position, + widget_position + + ImVec2( + width + (label_size.x > 0.0f ? _style->ItemInnerSpacing.x + label_size.x : 0.0f) + label_x_offset, + ImMax(height, label_size.y) + _style->FramePadding.y * 2.0f + )); + + // handle the toggle input behavior + bool pressed = ToggleBehavior(total_bounding_box); + _isMixedValue = ::IsItemMixedValue(); + + // draw the toggle itself and the label + DrawToggle(); + DrawLabel(label_x_offset); + + IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); + return pressed; } + void ImGuiToggleRenderer::ValidateConfig() { - IM_ASSERT_USER_ERROR(_config.Size.x >= 0, "Size.x specified was negative."); - IM_ASSERT_USER_ERROR(_config.Size.y >= 0, "Size.y specified was negative."); - - // if no flags were specified, use defaults. - if (_config.Flags == ImGuiToggleFlags_None) - { - _config.Flags = ImGuiToggleFlags_Default; - } - - // a zero or negative duration would prevent animation. - _config.AnimationDuration = ImMax(_config.AnimationDuration, AnimationDurationMinimum); - - // keep our size/scale and rounding numbers sane. - _config.FrameRounding = ImClamp(_config.FrameRounding, FrameRoundingMinimum, FrameRoundingMaximum); - _config.KnobRounding = ImClamp(_config.KnobRounding, KnobRoundingMinimum, KnobRoundingMaximum); - _config.WidthRatio = ImClamp(_config.WidthRatio, WidthRatioMinimum, WidthRatioMaximum); - - // Make sure our a11y labels have values. - if (_config.On.Label == nullptr) - { - _config.On.Label = LabelA11yOnDefault; - } - - if (_config.Off.Label == nullptr) - { - _config.Off.Label = LabelA11yOffDefault; - } + IM_ASSERT_USER_ERROR(_config.Size.x >= 0, "Size.x specified was negative."); + IM_ASSERT_USER_ERROR(_config.Size.y >= 0, "Size.y specified was negative."); + + // if no flags were specified, use defaults. + if (_config.Flags == ImGuiToggleFlags_None) + { + _config.Flags = ImGuiToggleFlags_Default; + } + + // a zero or negative duration would prevent animation. + _config.AnimationDuration = ImMax(_config.AnimationDuration, AnimationDurationMinimum); + + // keep our size/scale and rounding numbers sane. + _config.FrameRounding = ImClamp(_config.FrameRounding, FrameRoundingMinimum, FrameRoundingMaximum); + _config.KnobRounding = ImClamp(_config.KnobRounding, KnobRoundingMinimum, KnobRoundingMaximum); + _config.WidthRatio = ImClamp(_config.WidthRatio, WidthRatioMinimum, WidthRatioMaximum); + + // Make sure our a11y labels have values. + if (_config.On.Label == nullptr) + { + _config.On.Label = LabelA11yOnDefault; + } + + if (_config.Off.Label == nullptr) + { + _config.Off.Label = LabelA11yOffDefault; + } } bool ImGuiToggleRenderer::ToggleBehavior(const ImRect& interaction_bounding_box) { - ImGui::ItemSize(interaction_bounding_box, _style->FramePadding.y); - if (!ImGui::ItemAdd(interaction_bounding_box, _id)) - { - ImGuiContext& g = *GImGui; - IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); - return false; - } - - // the meat and potatoes: the actual toggle button - const ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick; - bool hovered, held; - bool pressed = ImGui::ButtonBehavior(interaction_bounding_box, _id, &hovered, &held, button_flags); - if (pressed) - { - *_value = !(*_value); - ImGui::MarkItemEdited(_id); - } - - return pressed; + ImGui::ItemSize(interaction_bounding_box, _style->FramePadding.y); + if (!ImGui::ItemAdd(interaction_bounding_box, _id)) + { + ImGuiContext& g = *GImGui; + IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); + return false; + } + + // the meat and potatoes: the actual toggle button + const ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick; + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(interaction_bounding_box, _id, &hovered, &held, button_flags); + if (pressed) + { + *_value = !(*_value); + ImGui::MarkItemEdited(_id); + } + + return pressed; } void ImGuiToggleRenderer::DrawToggle() { - const float height = GetHeight(); - const float width = GetWidth(); - - ImGuiContext& g = *GImGui; - // update imgui state - _isHovered = g.HoveredId == _id; - _isLastActive = g.LastActiveId == _id; - _lastActiveTimer = g.LastActiveIdTimer; - - // radius is by default half the diameter - const float knob_radius = height * DiameterToRadiusRatio; - - // update the toggle's animation timer, state, and palette. - UpdateAnimationPercent(); - UpdateStateConfig(); - UpdatePalette(); - - // get colors modified by hover. - const ImU32 color_frame = ImGui::GetColorU32(_isHovered ? _palette.FrameHover : _palette.Frame); - const ImU32 color_knob = ImGui::GetColorU32(_isHovered ? _palette.KnobHover : _palette.Knob); - - // draw the background frame - DrawFrame(color_frame); - - // draw accessibility labels, if enabled. - if (HasA11yGlyphs()) - { - DrawA11yFrameOverlays(knob_radius); - } - - // draw the knob - if (HasCircleKnob()) - { - DrawCircleKnob(knob_radius, color_knob); - } - else if (HasRectangleKnob()) - { - DrawRectangleKnob(knob_radius, color_knob); - } - else - { - // user didn't specify a knob mode, they get no knob. - IM_ASSERT_USER_ERROR(false, "No toggle knob type to draw."); - } + const float height = GetHeight(); + const float width = GetWidth(); + + ImGuiContext& g = *GImGui; + // update imgui state + _isHovered = g.HoveredId == _id; + _isLastActive = g.LastActiveId == _id; + _lastActiveTimer = g.LastActiveIdTimer; + + // radius is by default half the diameter + const float knob_radius = height * DiameterToRadiusRatio; + + // update the toggle's animation timer, state, and palette. + UpdateAnimationPercent(); + UpdateStateConfig(); + UpdatePalette(); + + // get colors modified by hover. + const ImU32 color_frame = ImGui::GetColorU32(_isHovered ? _palette.FrameHover : _palette.Frame); + const ImU32 color_knob = ImGui::GetColorU32(_isHovered ? _palette.KnobHover : _palette.Knob); + + // draw the background frame + DrawFrame(color_frame); + + // draw accessibility labels, if enabled. + if (HasA11yGlyphs()) + { + DrawA11yFrameOverlays(knob_radius); + } + + // draw the knob + if (HasCircleKnob()) + { + DrawCircleKnob(knob_radius, color_knob); + } + else if (HasRectangleKnob()) + { + DrawRectangleKnob(knob_radius, color_knob); + } + else + { + // user didn't specify a knob mode, they get no knob. + IM_ASSERT_USER_ERROR(false, "No toggle knob type to draw."); + } } void ImGuiToggleRenderer::DrawFrame(ImU32 color_frame) { - const float height = GetHeight(); - const float frame_rounding = _config.FrameRounding >= 0 - ? height * _config.FrameRounding - : height * 0.5f; - - // draw frame shadow, if enabled - if (HasShadowedFrame()) - { - const ImU32 color_frame_shadow = ImGui::GetColorU32(_palette.FrameShadow); - DrawRectShadow(_boundingBox, color_frame_shadow, frame_rounding, _state.FrameShadowThickness); - } - - // draw frame background - _drawList->AddRectFilled(_boundingBox.Min, _boundingBox.Max, color_frame, frame_rounding); - - // draw frame border, if enabled - if (HasBorderedFrame()) - { - const ImU32 color_frame_border = ImGui::GetColorU32(_palette.FrameBorder); - DrawRectBorder(_boundingBox, color_frame_border, frame_rounding, _state.FrameBorderThickness); - } + const float height = GetHeight(); + const float frame_rounding = _config.FrameRounding >= 0 + ? height * _config.FrameRounding + : height * 0.5f; + + // draw frame shadow, if enabled + if (HasShadowedFrame()) + { + const ImU32 color_frame_shadow = ImGui::GetColorU32(_palette.FrameShadow); + DrawRectShadow(_boundingBox, color_frame_shadow, frame_rounding, _state.FrameShadowThickness); + } + + // draw frame background + _drawList->AddRectFilled(_boundingBox.Min, _boundingBox.Max, color_frame, frame_rounding); + + // draw frame border, if enabled + if (HasBorderedFrame()) + { + const ImU32 color_frame_border = ImGui::GetColorU32(_palette.FrameBorder); + DrawRectBorder(_boundingBox, color_frame_border, frame_rounding, _state.FrameBorderThickness); + } } void ImGuiToggleRenderer::DrawA11yDot(const ImVec2& pos, ImU32 color) { - ImGui::RenderBullet(_drawList, pos, color); + ImGui::RenderBullet(_drawList, pos, color); } void ImGuiToggleRenderer::DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness) { - if (state) - { - // draw the I bar - const float half_thickness = thickness * 0.5f; - const ImVec2 offset(half_thickness, radius); - _drawList->AddRectFilled(pos - offset, pos + offset, color); - } - else - { - // draw the O ring - const float o_adjustment = 1.0f; - const float o_radius = radius - o_adjustment; - const float o_thickness = thickness + o_adjustment; - pos.x += o_adjustment; - _drawList->AddCircle(pos, o_radius, color, 0, o_thickness); - } + if (state) + { + // draw the I bar + const float half_thickness = thickness * 0.5f; + const ImVec2 offset(half_thickness, radius); + _drawList->AddRectFilled(pos - offset, pos + offset, color); + } + else + { + // draw the O ring + const float o_adjustment = 1.0f; + const float o_radius = radius - o_adjustment; + const float o_thickness = thickness + o_adjustment; + pos.x += o_adjustment; + _drawList->AddCircle(pos, o_radius, color, 0, o_thickness); + } } void ImGuiToggleRenderer::DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label) { - // subtract out half the sizes of the text to center them - const ImVec2 text_size = ImGui::CalcTextSize(label); - pos.x -= (text_size.x * 0.5f); - pos.y -= (text_size.y * 0.5f); + // subtract out half the sizes of the text to center them + const ImVec2 text_size = ImGui::CalcTextSize(label); + pos.x -= (text_size.x * 0.5f); + pos.y -= (text_size.y * 0.5f); - // draw the label. - _drawList->AddText(pos, color, label); + // draw the label. + _drawList->AddText(pos, color, label); } void ImGuiToggleRenderer::DrawA11yFrameOverlay(float knob_radius, bool state) { - const float AnimationPercentOff = 0.0f; - const float AnimationPercentOn = 1.0f; - - // notice we swap the animation percents as compared to the labels/glyphs, as we want to draw the - // a11y labels where the knob *isn't* when it's in a given state. - ImVec2 pos = CalculateKnobCenter(knob_radius, state ? AnimationPercentOff : AnimationPercentOn); - - // next, we want to adjust the position to move to a more pleasing spot in the toggle. - // this is just some tinkering that got to a nice looking area based on the sizes, - // but this is subject to change. - const float diameter = ImMax(1.0f, GetHeight() / 3.0f); - const float radius = diameter * 0.5f; - const float thickness = ImCeil(radius * 0.2f); - const ImVec2 adjustment = ImVec2(radius - thickness, 0.0f) - * (state ? -1.0f : 1.0f); // if state is true, we want to subtract rather than add. - - pos += adjustment; - - const ImU32 color = state - ? ImGui::GetColorU32(_colorA11yGlyphOn) - : ImGui::GetColorU32(_colorA11yGlyphOff); - - switch (_config.A11yStyle) - { - case ImGuiToggleA11yStyle_Label: - DrawA11yLabel(pos, color, state ? _config.On.Label : _config.Off.Label); - break; - case ImGuiToggleA11yStyle_Glyph: - DrawA11yGlyph(pos, color, state, radius, thickness); - break; - case ImGuiToggleA11yStyle_Dot: - DrawA11yDot(pos, color); - break; - default: - break; - } + const float AnimationPercentOff = 0.0f; + const float AnimationPercentOn = 1.0f; + + // notice we swap the animation percents as compared to the labels/glyphs, as we want to draw the + // a11y labels where the knob *isn't* when it's in a given state. + ImVec2 pos = CalculateKnobCenter(knob_radius, state ? AnimationPercentOff : AnimationPercentOn); + + // next, we want to adjust the position to move to a more pleasing spot in the toggle. + // this is just some tinkering that got to a nice looking area based on the sizes, + // but this is subject to change. + const float diameter = ImMax(1.0f, GetHeight() / 3.0f); + const float radius = diameter * 0.5f; + const float thickness = ImCeil(radius * 0.2f); + const ImVec2 adjustment = ImVec2(radius - thickness, 0.0f) + * (state ? -1.0f : 1.0f); // if state is true, we want to subtract rather than add. + + pos += adjustment; + + const ImU32 color = state + ? ImGui::GetColorU32(_colorA11yGlyphOn) + : ImGui::GetColorU32(_colorA11yGlyphOff); + + switch (_config.A11yStyle) + { + case ImGuiToggleA11yStyle_Label: + DrawA11yLabel(pos, color, state ? _config.On.Label : _config.Off.Label); + break; + case ImGuiToggleA11yStyle_Glyph: + DrawA11yGlyph(pos, color, state, radius, thickness); + break; + case ImGuiToggleA11yStyle_Dot: + DrawA11yDot(pos, color); + break; + default: + break; + } } void ImGuiToggleRenderer::DrawA11yFrameOverlays(float knob_radius) { - DrawA11yFrameOverlay(knob_radius, true); - DrawA11yFrameOverlay(knob_radius, false); + DrawA11yFrameOverlay(knob_radius, true); + DrawA11yFrameOverlay(knob_radius, false); } void ImGuiToggleRenderer::DrawCircleKnob(float radius, ImU32 color_knob) { - const float inset_size = ImMin(_state.KnobInset.GetAverage(), radius); - IM_ASSERT_USER_ERROR(inset_size <= radius, "Inset size needs to be smaller or equal to the knob's radius for circular knobs."); - - const ImVec2 knob_center = CalculateKnobCenter(radius, _animationPercent, _state.KnobOffset); - const float knob_radius = radius - inset_size; - - // draw knob shadow, if enabled - if (HasShadowedKnob()) - { - const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); - DrawCircleShadow(knob_center, knob_radius, color_knob_shadow, _state.KnobShadowThickness); - } - - // draw circle knob - _drawList->AddCircleFilled(knob_center, knob_radius, color_knob); - - // draw knob border, if enabled - if (HasBorderedKnob()) - { - const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); - DrawCircleBorder(knob_center, knob_radius, color_knob_border, _state.KnobBorderThickness); - } + const float inset_size = ImMin(_state.KnobInset.GetAverage(), radius); + IM_ASSERT_USER_ERROR(inset_size <= radius, "Inset size needs to be smaller or equal to the knob's radius for circular knobs."); + + const ImVec2 knob_center = CalculateKnobCenter(radius, _animationPercent, _state.KnobOffset); + const float knob_radius = radius - inset_size; + + // draw knob shadow, if enabled + if (HasShadowedKnob()) + { + const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); + DrawCircleShadow(knob_center, knob_radius, color_knob_shadow, _state.KnobShadowThickness); + } + + // draw circle knob + _drawList->AddCircleFilled(knob_center, knob_radius, color_knob); + + // draw knob border, if enabled + if (HasBorderedKnob()) + { + const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); + DrawCircleBorder(knob_center, knob_radius, color_knob_border, _state.KnobBorderThickness); + } } void ImGuiToggleRenderer::DrawRectangleKnob(float radius, ImU32 color_knob) { - const ImRect bounds = CalculateKnobBounds(radius, _animationPercent, _state.KnobOffset); - - const float knob_diameter_total = bounds.GetHeight(); - const float knob_rounded_radius = (knob_diameter_total * 0.5f) * _config.KnobRounding; - - // draw knob shadow, if enabled - if (HasShadowedKnob()) - { - const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); - DrawRectShadow(bounds, color_knob_shadow, _config.KnobRounding, _state.KnobShadowThickness); - } - - // draw rectangle/squircle knob - _drawList->AddRectFilled(bounds.Min, bounds.Max, color_knob, knob_rounded_radius); - - // draw knob border, if enabled - if (HasBorderedKnob()) - { - const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); - DrawRectBorder(bounds, color_knob_border, knob_rounded_radius, _state.KnobBorderThickness); - } + const ImRect bounds = CalculateKnobBounds(radius, _animationPercent, _state.KnobOffset); + + const float knob_diameter_total = bounds.GetHeight(); + const float knob_rounded_radius = (knob_diameter_total * 0.5f) * _config.KnobRounding; + + // draw knob shadow, if enabled + if (HasShadowedKnob()) + { + const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); + DrawRectShadow(bounds, color_knob_shadow, _config.KnobRounding, _state.KnobShadowThickness); + } + + // draw rectangle/squircle knob + _drawList->AddRectFilled(bounds.Min, bounds.Max, color_knob, knob_rounded_radius); + + // draw knob border, if enabled + if (HasBorderedKnob()) + { + const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); + DrawRectBorder(bounds, color_knob_border, knob_rounded_radius, _state.KnobBorderThickness); + } } void ImGuiToggleRenderer::DrawLabel(float x_offset) { - const ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); - - const float half_height = GetHeight() * 0.5f; - const float label_x = _boundingBox.Max.x + _style->ItemInnerSpacing.x + x_offset; - const float label_y = _boundingBox.Min.y + half_height - (label_size.y * 0.5f); - const ImVec2 label_pos = ImVec2(label_x, label_y); - - ImGuiContext& g = *GImGui; - if (g.LogEnabled) - { - ImGui::LogRenderedText(&label_pos, _isMixedValue ? "[~]" : *_value ? "[x]" : "[ ]"); - } - - if (label_size.x > 0.0f) - { - ImGui::RenderText(label_pos, _label); - } + const ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); + + const float half_height = GetHeight() * 0.5f; + const float label_x = _boundingBox.Max.x + _style->ItemInnerSpacing.x + x_offset; + const float label_y = _boundingBox.Min.y + half_height - (label_size.y * 0.5f); + const ImVec2 label_pos = ImVec2(label_x, label_y); + + ImGuiContext& g = *GImGui; + if (g.LogEnabled) + { + ImGui::LogRenderedText(&label_pos, _isMixedValue ? "[~]" : *_value ? "[x]" : "[ ]"); + } + + if (label_size.x > 0.0f) + { + ImGui::RenderText(label_pos, _label); + } } void ImGuiToggleRenderer::UpdateAnimationPercent() { - // calculate the lerp percentage for animation, - // but default to 1/0 for if we aren't animating at all, - // or 0.5f if we have a mixed value. Also, trying to keep parity with - // undocumented tristate/mixed/indeterminate checkbox (#2644) - - float t = _isMixedValue - ? 0.5f - : (*_value ? 1.0f : 0.0f); - - if (IsAnimated() && _isLastActive) - { - const float t_anim = ImSaturate(ImInvLerp(0.0f, _config.AnimationDuration, _lastActiveTimer)); - t = *_value ? (t_anim) : (1.0f - t_anim); - } - - _animationPercent = t; + // calculate the lerp percentage for animation, + // but default to 1/0 for if we aren't animating at all, + // or 0.5f if we have a mixed value. Also, trying to keep parity with + // undocumented tristate/mixed/indeterminate checkbox (#2644) + + float t = _isMixedValue + ? 0.5f + : (*_value ? 1.0f : 0.0f); + + if (IsAnimated() && _isLastActive) + { + const float t_anim = ImSaturate(ImInvLerp(0.0f, _config.AnimationDuration, _lastActiveTimer)); + t = *_value ? (t_anim) : (1.0f - t_anim); + } + + _animationPercent = t; } void ImGuiToggleRenderer::UpdateStateConfig() { - if (!IsAnimated()) - { - _state = *_value ? _config.On : _config.Off; - return; - } - - _state.FrameBorderThickness = ImLerp(_config.Off.FrameBorderThickness, _config.On.FrameBorderThickness, _animationPercent); - _state.KnobBorderThickness = ImLerp(_config.Off.KnobBorderThickness, _config.On.KnobBorderThickness, _animationPercent); - _state.KnobInset = ImLerp(_config.Off.KnobInset, _config.On.KnobInset, _animationPercent); - _state.KnobOffset = ImLerp(_config.Off.KnobOffset, _config.On.KnobOffset, _animationPercent); + if (!IsAnimated()) + { + _state = *_value ? _config.On : _config.Off; + return; + } + + _state.FrameBorderThickness = ImLerp(_config.Off.FrameBorderThickness, _config.On.FrameBorderThickness, _animationPercent); + _state.KnobBorderThickness = ImLerp(_config.Off.KnobBorderThickness, _config.On.KnobBorderThickness, _animationPercent); + _state.KnobInset = ImLerp(_config.Off.KnobInset, _config.On.KnobInset, _animationPercent); + _state.KnobOffset = ImLerp(_config.Off.KnobOffset, _config.On.KnobOffset, _animationPercent); } void ImGuiToggleRenderer::UpdatePalette() { - const ImGuiTogglePalette* on_candidate = _config.On.Palette; - const ImGuiTogglePalette* off_candidate = _config.Off.Palette; - - if (!IsAnimated()) - { - ImGui::UnionPalette( - &_palette, - *_value ? on_candidate : off_candidate, - _style->Colors, - *_value); - - // store specific colors that shouldn't blend. - _colorA11yGlyphOff = _palette.A11yGlyph; - _colorA11yGlyphOn = _palette.A11yGlyph; - - return; - } - - ImGuiTogglePalette off_unioned; - ImGuiTogglePalette on_unioned; - ImGui::UnionPalette(&off_unioned, off_candidate, _style->Colors, false); - ImGui::UnionPalette(&on_unioned, on_candidate, _style->Colors, true); - - // otherwise, lets lerp them! - ImGui::BlendPalettes(&_palette, off_unioned, on_unioned, _animationPercent); - - // store specific colors that shouldn't blend. - _colorA11yGlyphOff = off_unioned.A11yGlyph; - _colorA11yGlyphOn = on_unioned.A11yGlyph; + const ImGuiTogglePalette* on_candidate = _config.On.Palette; + const ImGuiTogglePalette* off_candidate = _config.Off.Palette; + + if (!IsAnimated()) + { + ImGui::UnionPalette( + &_palette, + *_value ? on_candidate : off_candidate, + _style->Colors, + *_value); + + // store specific colors that shouldn't blend. + _colorA11yGlyphOff = _palette.A11yGlyph; + _colorA11yGlyphOn = _palette.A11yGlyph; + + return; + } + + ImGuiTogglePalette off_unioned; + ImGuiTogglePalette on_unioned; + ImGui::UnionPalette(&off_unioned, off_candidate, _style->Colors, false); + ImGui::UnionPalette(&on_unioned, on_candidate, _style->Colors, true); + + // otherwise, lets lerp them! + ImGui::BlendPalettes(&_palette, off_unioned, on_unioned, _animationPercent); + + // store specific colors that shouldn't blend. + _colorA11yGlyphOff = off_unioned.A11yGlyph; + _colorA11yGlyphOn = on_unioned.A11yGlyph; } ImVec2 ImGuiToggleRenderer::CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const { - const ImVec2 pos = GetPosition(); - const float double_radius = radius * 2.0f; - const float animation_percent_inverse = 1.0f - animation_percent; - - const float knob_x = (pos.x + radius) - + animation_percent * (GetWidth() - double_radius - offset.x) - + (animation_percent_inverse * offset.x); - const float knob_y = pos.y + radius + offset.y; - return ImVec2(knob_x, knob_y); + const ImVec2 pos = GetPosition(); + const float double_radius = radius * 2.0f; + const float animation_percent_inverse = 1.0f - animation_percent; + + const float knob_x = (pos.x + radius) + + animation_percent * (GetWidth() - double_radius - offset.x) + + (animation_percent_inverse * offset.x); + const float knob_y = pos.y + radius + offset.y; + return ImVec2(knob_x, knob_y); } ImRect ImGuiToggleRenderer::CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const { - const ImVec2 position = GetPosition(); - const float double_radius = radius * 2.0f; - const float animation_percent_inverse = 1.0f - animation_percent; - - const float knob_left = (animation_percent * (GetWidth() - double_radius - offset.x)) - + (animation_percent_inverse * offset.x) - + _state.KnobInset.Left; - const float knob_top = _state.KnobInset.Top + _state.KnobOffset.y; - const float knob_bottom = GetHeight() - _state.KnobInset.Bottom + _state.KnobOffset.y; - const float knob_right = (knob_left - _state.KnobInset.Left) + double_radius - _state.KnobInset.Right; - - // if our offsets in the x or y are close to 0, - // we will just skip drawing the whole thing. - if (ImApproximately(knob_left, knob_right) || - ImApproximately(knob_top, knob_bottom)) - { - return ImRect(); - } - - const ImVec2 knob_min = position + ImVec2(knob_left, knob_top); - const ImVec2 knob_max = position + ImVec2(knob_right, knob_bottom); - - return ImRect(knob_min, knob_max); + const ImVec2 position = GetPosition(); + const float double_radius = radius * 2.0f; + const float animation_percent_inverse = 1.0f - animation_percent; + + const float knob_left = (animation_percent * (GetWidth() - double_radius - offset.x)) + + (animation_percent_inverse * offset.x) + + _state.KnobInset.Left; + const float knob_top = _state.KnobInset.Top + _state.KnobOffset.y; + const float knob_bottom = GetHeight() - _state.KnobInset.Bottom + _state.KnobOffset.y; + const float knob_right = (knob_left - _state.KnobInset.Left) + double_radius - _state.KnobInset.Right; + + // if our offsets in the x or y are close to 0, + // we will just skip drawing the whole thing. + if (ImApproximately(knob_left, knob_right) || + ImApproximately(knob_top, knob_bottom)) + { + return ImRect(); + } + + const ImVec2 knob_min = position + ImVec2(knob_left, knob_top); + const ImVec2 knob_max = position + ImVec2(knob_right, knob_bottom); + + return ImRect(knob_min, knob_max); } void ImGuiToggleRenderer::DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness) { - // the border should only grow "inside" the bounding box, - // so we need to shrink the bounds used to prevent it from puffing out. - const float half_thickness = thickness * 0.5f; - bounds.Expand(-half_thickness); + // the border should only grow "inside" the bounding box, + // so we need to shrink the bounds used to prevent it from puffing out. + const float half_thickness = thickness * 0.5f; + bounds.Expand(-half_thickness); - _drawList->AddRect(bounds.Min, bounds.Max, color_border, rounding, ImDrawFlags_None, thickness); + _drawList->AddRect(bounds.Min, bounds.Max, color_border, rounding, ImDrawFlags_None, thickness); } void ImGuiToggleRenderer::DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness) { - // the border should only grow "inside" the bounding box, - // so we need to shrink the radius used to prevent it from puffing out. - const float half_thickness = thickness * 0.5f; - radius -= half_thickness; + // the border should only grow "inside" the bounding box, + // so we need to shrink the radius used to prevent it from puffing out. + const float half_thickness = thickness * 0.5f; + radius -= half_thickness; - _drawList->AddCircle(center, radius, color_border, 0, thickness); + _drawList->AddCircle(center, radius, color_border, 0, thickness); } + void ImGuiToggleRenderer::DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness) { - // the shadow should only grow "outside" the bounding box, - // so we need to expand the bounds used to puff it out. - const float half_thickness = thickness * 0.5f; - bounds.Expand(half_thickness); + // the shadow should only grow "outside" the bounding box, + // so we need to expand the bounds used to puff it out. + const float half_thickness = thickness * 0.5f; + bounds.Expand(half_thickness); - _drawList->AddRect(bounds.Min, bounds.Max, color_shadow, rounding, ImDrawFlags_None, thickness); + _drawList->AddRect(bounds.Min, bounds.Max, color_shadow, rounding, ImDrawFlags_None, thickness); } void ImGuiToggleRenderer::DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_border, float thickness) { - // the shadow should only grow "outside" the bounding box, - // so we need to expand the radius used to puff it out. - const float half_thickness = thickness * 0.5f; - radius += half_thickness; + // the shadow should only grow "outside" the bounding box, + // so we need to expand the radius used to puff it out. + const float half_thickness = thickness * 0.5f; + radius += half_thickness; - _drawList->AddCircle(center, radius, color_border, 0, thickness); -} \ No newline at end of file + _drawList->AddCircle(center, radius, color_border, 0, thickness); +} diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.h" index 07a8c61d..952feafb 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_toggle_renderer.h" @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS @@ -9,88 +9,89 @@ #include "imgui_toggle.h" #include "imgui_toggle_palette.h" + class ImGuiToggleRenderer { public: - ImGuiToggleRenderer(); - ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config); - void SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config); - bool Render(); + ImGuiToggleRenderer(); + ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config); + void SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config); + bool Render(); private: - // toggle state & context - ImGuiToggleConfig _config; - ImGuiToggleStateConfig _state; - ImGuiTogglePalette _palette; - - bool _isMixedValue; - bool _isHovered; - bool _isLastActive; - float _lastActiveTimer; - float _animationPercent; - - // imgui specific context - const ImGuiStyle* _style; - ImDrawList* _drawList; - ImGuiID _id; - - // raw ui value & label - const char* _label; - bool* _value; - - // calculated values - ImRect _boundingBox; - ImVec4 _colorA11yGlyphOff; - ImVec4 _colorA11yGlyphOn; - - // inline accessors - inline float GetWidth() const { return _boundingBox.GetWidth(); } - inline float GetHeight() const { return _boundingBox.GetHeight(); } - inline ImVec2 GetPosition() const { return _boundingBox.Min; } - inline ImVec2 GetToggleSize() const { return _boundingBox.GetSize(); } - inline bool IsAnimated() const { return (_config.Flags & ImGuiToggleFlags_Animated) != 0 && _config.AnimationDuration > 0; } - inline bool HasBorderedFrame() const { return (_config.Flags & ImGuiToggleFlags_BorderedFrame) != 0 && _state.FrameBorderThickness > 0; } - inline bool HasShadowedFrame() const { return (_config.Flags & ImGuiToggleFlags_ShadowedFrame) != 0 && _state.FrameShadowThickness > 0; } - inline bool HasBorderedKnob() const { return (_config.Flags & ImGuiToggleFlags_BorderedKnob) != 0 && _state.KnobBorderThickness > 0; } - inline bool HasShadowedKnob() const { return (_config.Flags & ImGuiToggleFlags_ShadowedKnob) != 0 && _state.KnobShadowThickness > 0; } - inline bool HasA11yGlyphs() const { return (_config.Flags & ImGuiToggleFlags_A11y) != 0; } - inline bool HasCircleKnob() const { return _config.KnobRounding >= 1.0f; } - inline bool HasRectangleKnob() const { return _config.KnobRounding < 1.0f; } - - // behavior - void ValidateConfig(); - bool ToggleBehavior(const ImRect& interaction_bounding_box); - - // drawing - general - void DrawToggle(); - - // drawing - frame - void DrawFrame(ImU32 color_frame); - - // drawing a11y - void DrawA11yDot(const ImVec2& pos, ImU32 color); - void DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness); - void DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label); - void DrawA11yFrameOverlay(float knob_radius, bool state); - void DrawA11yFrameOverlays(float knob_radius); - - // drawing - knob - void DrawCircleKnob(float radius, ImU32 color_knob); - void DrawRectangleKnob(float radius, ImU32 color_knob); - - // drawing - label - void DrawLabel(float x_offset); - - // state updating - void UpdateAnimationPercent(); - void UpdateStateConfig(); - void UpdatePalette(); - - // helpers - ImVec2 CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; - ImRect CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; - void DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness); - void DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness); - void DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness); - void DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_shadow, float thickness); + // toggle state & context + ImGuiToggleConfig _config; + ImGuiToggleStateConfig _state; + ImGuiTogglePalette _palette; + + bool _isMixedValue; + bool _isHovered; + bool _isLastActive; + float _lastActiveTimer; + float _animationPercent; + + // imgui specific context + const ImGuiStyle* _style; + ImDrawList* _drawList; + ImGuiID _id; + + // raw ui value & label + const char* _label; + bool* _value; + + // calculated values + ImRect _boundingBox; + ImVec4 _colorA11yGlyphOff; + ImVec4 _colorA11yGlyphOn; + + // inline accessors + inline float GetWidth() const { return _boundingBox.GetWidth(); } + inline float GetHeight() const { return _boundingBox.GetHeight(); } + inline ImVec2 GetPosition() const { return _boundingBox.Min; } + inline ImVec2 GetToggleSize() const { return _boundingBox.GetSize(); } + inline bool IsAnimated() const { return (_config.Flags & ImGuiToggleFlags_Animated) != 0 && _config.AnimationDuration > 0; } + inline bool HasBorderedFrame() const { return (_config.Flags & ImGuiToggleFlags_BorderedFrame) != 0 && _state.FrameBorderThickness > 0; } + inline bool HasShadowedFrame() const { return (_config.Flags & ImGuiToggleFlags_ShadowedFrame) != 0 && _state.FrameShadowThickness > 0; } + inline bool HasBorderedKnob() const { return (_config.Flags & ImGuiToggleFlags_BorderedKnob) != 0 && _state.KnobBorderThickness > 0; } + inline bool HasShadowedKnob() const { return (_config.Flags & ImGuiToggleFlags_ShadowedKnob) != 0 && _state.KnobShadowThickness > 0; } + inline bool HasA11yGlyphs() const { return (_config.Flags & ImGuiToggleFlags_A11y) != 0; } + inline bool HasCircleKnob() const { return _config.KnobRounding >= 1.0f; } + inline bool HasRectangleKnob() const { return _config.KnobRounding < 1.0f; } + + // behavior + void ValidateConfig(); + bool ToggleBehavior(const ImRect& interaction_bounding_box); + + // drawing - general + void DrawToggle(); + + // drawing - frame + void DrawFrame(ImU32 color_frame); + + // drawing a11y + void DrawA11yDot(const ImVec2& pos, ImU32 color); + void DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness); + void DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label); + void DrawA11yFrameOverlay(float knob_radius, bool state); + void DrawA11yFrameOverlays(float knob_radius); + + // drawing - knob + void DrawCircleKnob(float radius, ImU32 color_knob); + void DrawRectangleKnob(float radius, ImU32 color_knob); + + // drawing - label + void DrawLabel(float x_offset); + + // state updating + void UpdateAnimationPercent(); + void UpdateStateConfig(); + void UpdatePalette(); + + // helpers + ImVec2 CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; + ImRect CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; + void DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness); + void DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness); + void DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness); + void DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_shadow, float thickness); }; diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_widgets.cpp" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_widgets.cpp" index 322c0846..e487b3f9 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_widgets.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imgui_widgets.cpp" @@ -1,4 +1,4 @@ -// dear imgui, v1.91.3 +// dear imgui, v1.92.5 // (widgets code) /* @@ -70,6 +70,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx' #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse. #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok. +#pragma clang diagnostic ignored "-Wformat" // warning: format specifies type 'int' but the argument has type 'unsigned int' #pragma clang diagnostic ignored "-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code. #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wunused-macros" // warning: macro is not used // we define snprintf/vsnprintf on Windows so they are available, but not always used. @@ -79,11 +80,18 @@ Index of this file: #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access +#pragma clang diagnostic ignored "-Wnontrivial-memaccess" // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type +#pragma clang diagnostic ignored "-Wswitch-default" // warning: 'switch' missing 'default' label #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind +#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe +#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*' #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked -#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead #pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function +#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1 +#pragma GCC diagnostic ignored "-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead +#pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers #endif //------------------------------------------------------------------------- @@ -127,8 +135,7 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); // For InputTextEx() static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0); //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. @@ -163,7 +170,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) // Calculate length const char* text_begin = text; if (text_end == NULL) - text_end = text + strlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); const float wrap_pos_x = window->DC.TextWrapPos; @@ -203,7 +210,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) int lines_skipped = 0; while (line < text_end && lines_skipped < lines_skippable) { - const char* line_end = (const char*)memchr(line, '\n', text_end - line); + const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) @@ -224,7 +231,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) if (IsClippedEx(line_rect, 0)) break; - const char* line_end = (const char*)memchr(line, '\n', text_end - line); + const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x); @@ -239,7 +246,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) int lines_skipped = 0; while (line < text_end) { - const char* line_end = (const char*)memchr(line, '\n', text_end - line); + const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line); if (!line_end) line_end = text_end; if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0) @@ -331,6 +338,46 @@ void ImGui::TextWrappedV(const char* fmt, va_list args) PopTextWrapPos(); } +void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + TextAlignedV(align_x, size_x, fmt, args); + va_end(args); +} + +// align_x: 0.0f = left, 0.5f = center, 1.0f = right. +// size_x : 0.0f = shortcut for GetContentRegionAvail().x +// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024) +void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args) +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return; + + const char* text, *text_end; + ImFormatStringToTempBufferV(&text, &text_end, fmt, args); + const ImVec2 text_size = CalcTextSize(text, text_end); + size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x; + + ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y); + ImVec2 size(ImMin(size_x, text_size.x), text_size.y); + window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x); + window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x); + if (align_x > 0.0f && text_size.x < size_x) + pos.x += ImTrunc((size_x - text_size.x) * align_x); + RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size); + + const ImVec2 backup_max_pos = window->DC.CursorMaxPos; + ItemSize(size); + ItemAdd(ImRect(pos, pos + size), 0); + window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up. + + if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip)) + SetTooltip("%.*s", (int)(text_end - text), text); +} + void ImGui::LabelText(const char* label, const char* fmt, ...) { va_list args; @@ -471,7 +518,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // - PressedOnDragDropHold can generally be associated with any flag. // - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported. //------------------------------------------------------------------------------------------------------------------------------------------------ -// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set: +// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set: // Repeat+ Repeat+ Repeat+ Repeat+ // PressedOnClickRelease PressedOnClick PressedOnRelease PressedOnDoubleClick //------------------------------------------------------------------------------------------------------------------------------------------------- @@ -486,7 +533,7 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. // - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. -// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() +// One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior() // with same ID and different MouseButton (see #8030). You can fix it by: // (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. // or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() @@ -495,24 +542,24 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); + // Default behavior inherited from item flags + // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); + if (flags & ImGuiButtonFlags_AllowOverlap) + item_flags |= ImGuiItemFlags_AllowOverlap; + if (item_flags & ImGuiItemFlags_NoFocus) + flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus; + // Default only reacts to left mouse button if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) flags |= ImGuiButtonFlags_MouseButtonLeft; // Default behavior requires click + release inside bounding box if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0) - flags |= ImGuiButtonFlags_PressedOnDefault_; - - // Default behavior inherited from item flags - // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that. - ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); - if (flags & ImGuiButtonFlags_AllowOverlap) - item_flags |= ImGuiItemFlags_AllowOverlap; - if (flags & ImGuiButtonFlags_Repeat) - item_flags |= ImGuiItemFlags_ButtonRepeat; + flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_; ImGuiWindow* backup_hovered_window = g.HoveredWindow; - const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window; + const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindow == window->RootWindow; if (flatten_hovered_children) g.HoveredWindow = window; @@ -525,9 +572,11 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool bool pressed = false; bool hovered = ItemHoverable(bb, id, item_flags); - // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button - if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) - if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + // Special mode for Drag and Drop used by openables (tree nodes, tabs etc.) + // where holding the button pressed for a long time while drag a payload item triggers the button. + if (g.DragDropActive) + { + if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { hovered = true; SetHoveredID(id); @@ -538,6 +587,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool FocusWindow(window); } } + if (g.DragDropAcceptIdPrev == id && (g.DragDropAcceptFlagsPrev & ImGuiDragDropFlags_AcceptDrawAsHovered)) + hovered = true; + } if (flatten_hovered_children) g.HoveredWindow = backup_hovered_window; @@ -561,7 +613,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } // Process initial action - if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt)) + const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt); + if (mods_ok) { if (mouse_button_clicked != -1 && g.ActiveId != id) { @@ -570,13 +623,13 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere)) { SetActiveID(id, window); - g.ActiveIdMouseButton = mouse_button_clicked; + g.ActiveIdMouseButton = (ImS8)mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) { SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -588,13 +641,13 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ClearActiveID(); else SetActiveID(id, window); // Hold on ID - g.ActiveIdMouseButton = mouse_button_clicked; + g.ActiveIdMouseButton = (ImS8)mouse_button_clicked; if (!(flags & ImGuiButtonFlags_NoNavFocus)) { SetFocusID(id, window); FocusWindow(window); } - else + else if (!(flags & ImGuiButtonFlags_NoFocus)) { FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child } @@ -608,7 +661,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool if (!has_repeated_at_least_once) pressed = true; if (!(flags & ImGuiButtonFlags_NoNavFocus)) - SetFocusID(id, window); + SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why. ClearActiveID(); } } @@ -620,38 +673,41 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool pressed = true; } - if (pressed) - g.NavDisableHighlight = true; + if (pressed && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } - // Gamepad/Keyboard handling + // Keyboard/Gamepad navigation handling // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. - if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) - if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) - hovered = true; - if (g.NavActivateDownId == id) + if ((item_flags & ImGuiItemFlags_Disabled) == 0) { - bool nav_activated_by_code = (g.NavActivateId == id); - bool nav_activated_by_inputs = (g.NavActivatePressedId == id); - if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) - { - // Avoid pressing multiple keys from triggering excessive amount of repeat events - const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space); - const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter); - const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate); - const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration); - nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; - } - if (nav_activated_by_code || nav_activated_by_inputs) + if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav) + if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) + hovered = true; + if (g.NavActivateDownId == id) { - // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. - pressed = true; - SetActiveID(id, window); - g.ActiveIdSource = g.NavInputSource; - if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) - SetFocusID(id, window); - if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) - g.ActiveIdFromShortcut = true; + bool nav_activated_by_code = (g.NavActivateId == id); + bool nav_activated_by_inputs = (g.NavActivatePressedId == id); + if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat)) + { + // Avoid pressing multiple keys from triggering excessive amount of repeat events + const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space); + const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter); + const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate); + const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration); + nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0; + } + if (nav_activated_by_code || nav_activated_by_inputs) + { + // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button. + pressed = true; + SetActiveID(id, window); + g.ActiveIdSource = g.NavInputSource; + if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) + SetFocusID(id, window); + if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) + g.ActiveIdFromShortcut = true; + } } } @@ -689,8 +745,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } ClearActiveID(); } - if (!(flags & ImGuiButtonFlags_NoNavFocus)) - g.NavDisableHighlight = true; + if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { @@ -705,7 +761,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool } // Activation highlight (this may be a remote activation) - if (g.NavHighlightActivatedId == id) + if (g.NavHighlightActivatedId == id && (item_flags & ImGuiItemFlags_Disabled) == 0) hovered = true; if (out_hovered) *out_hovered = hovered; @@ -740,7 +796,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); if (g.LogEnabled) @@ -787,11 +843,12 @@ bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiBut ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size); ItemSize(size); - if (!ItemAdd(bb, id)) + if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav)) return false; bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags); + RenderNavCursor(bb, id); IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags); return pressed; @@ -817,7 +874,7 @@ bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiBu // Render const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); const ImU32 text_col = GetColorU32(ImGuiCol_Text); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding); RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir); @@ -858,12 +915,13 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered); if (hovered) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact); - ImU32 cross_col = GetColorU32(ImGuiCol_Text); - ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); - float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; - window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); - window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); + const ImU32 cross_col = GetColorU32(ImGuiCol_Text); + const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; + const float cross_thickness = 1.0f; // FIXME-DPI + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); + window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness); return pressed; } @@ -885,7 +943,7 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) ImU32 text_col = GetColorU32(ImGuiCol_Text); if (hovered || held) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact); + RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); // Switch to moving the window after mouse is moved beyond the initial drag threshold @@ -903,15 +961,17 @@ ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis) // Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set. ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) { + ImGuiContext& g = *GImGui; const ImRect outer_rect = window->Rect(); const ImRect inner_rect = window->InnerRect; - const float border_size = window->WindowBorderSize; const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) - IM_ASSERT(scrollbar_size > 0.0f); + IM_ASSERT(scrollbar_size >= 0.0f); + const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f); + const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f; if (axis == ImGuiAxis_X) - return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); + return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); else - return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); + return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size); } void ImGui::Scrollbar(ImGuiAxis axis) @@ -949,7 +1009,7 @@ void ImGui::Scrollbar(ImGuiAxis axis) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. // Still, the code should probably be made simpler.. -bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags) +bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -963,8 +1023,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) float alpha = 1.0f; - if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f) - alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); + if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width) + alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f)); if (alpha <= 0.0f) return false; @@ -972,7 +1032,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 const bool allow_interaction = (alpha >= 1.0f); ImRect bb = bb_frame; - bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f))); + float padding = IM_TRUNC(ImMin(style.ScrollbarPadding, ImMin(bb_frame_width, bb_frame_height) * 0.5f)); + bb.Expand(-padding); // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight(); @@ -981,7 +1042,8 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // But we maintain a minimum size in pixel to allow for the user to still aim inside. IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1); - const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v); + const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize); + const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v); const float grab_h_norm = grab_h_pixels / scrollbar_size_v; // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). @@ -1040,7 +1102,7 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // Render const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg); const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha); - window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags); + window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags); ImRect grab_rect; if (axis == ImGuiAxis_X) grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y); @@ -1051,30 +1113,48 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 return held; } -// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. -void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { + ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return; - const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f; - const ImVec2 padding(border_size, border_size); + const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f); ItemSize(bb); if (!ItemAdd(bb, 0)) return; // Render - if (border_size > 0.0f) - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size); - window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + if (g.Style.ImageBorderSize > 0.0f) + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); } -// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) -// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. -bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) +{ + ImageWithBg(tex_ref, image_size, uv0, uv1); +} + +// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238) +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) +{ + ImGuiContext& g = *GImGui; + PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f + PushStyleColor(ImGuiCol_Border, border_col); + ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); + PopStyleColor(); + PopStyleVar(); +} +#endif + +bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); @@ -1092,29 +1172,30 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& imag // Render const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); if (bg_col.w > 0.0f) window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); - window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); + window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); return pressed; } -// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. -bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) +// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. +// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? +bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (window->SkipItems) return false; - return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); + return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col); } #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // Legacy API obsoleted in 1.89. Two differences with new ImageButton() -// - old ImageButton() used ImTextureId as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) +// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID) // - new ImageButton() requires an explicit 'const char* str_id' // - old ImageButton() had frame_padding' override argument. // - new ImageButton() always use style.FramePadding. @@ -1122,7 +1203,7 @@ bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const I bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { // Default to using texture ID as ID. User can still push string/integer prefixes. - PushID((void*)(intptr_t)user_texture_id); + PushID((ImTextureID)(intptr_t)user_texture_id); if (frame_padding >= 0) PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding)); bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col); @@ -1150,7 +1231,7 @@ bool ImGui::Checkbox(const char* label, bool* v) const ImRect total_bb(pos, pos + ImVec2(square_sz + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), label_size.y + style.FramePadding.y * 2.0f)); ItemSize(total_bb, style.FramePadding.y); const bool is_visible = ItemAdd(total_bb, id); - const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; if (!is_visible) if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support { @@ -1180,10 +1261,10 @@ bool ImGui::Checkbox(const char* label, bool* v) } const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz)); - const bool mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; + const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; if (is_visible) { - RenderNavHighlight(total_bb, id); + RenderNavCursor(total_bb, id); RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding); ImU32 check_col = GetColorU32(ImGuiCol_CheckMark); if (mixed_value) @@ -1285,7 +1366,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (pressed) MarkItemEdited(id); - RenderNavHighlight(total_bb, id); + RenderNavCursor(total_bb, id); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); if (active) @@ -1416,7 +1497,7 @@ bool ImGui::TextLink(const char* label) const ImGuiID id = window->GetID(label); const char* label_end = FindRenderedTextEnd(label); - ImVec2 pos = window->DC.CursorPos; + ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); ImVec2 size = CalcTextSize(label, label_end, true); ImRect bb(pos, pos + size); ItemSize(size, 0.0f); @@ -1425,7 +1506,7 @@ bool ImGui::TextLink(const char* label) bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_None); + RenderNavCursor(bb, id); if (hovered) SetMouseCursor(ImGuiMouseCursor_Hand); @@ -1447,8 +1528,8 @@ bool ImGui::TextLink(const char* label) ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); } - float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); - window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. + float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f); + window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); RenderText(bb.Min, label, label_end); @@ -1458,14 +1539,14 @@ bool ImGui::TextLink(const char* label) return pressed; } -void ImGui::TextLinkOpenURL(const char* label, const char* url) +bool ImGui::TextLinkOpenURL(const char* label, const char* url) { ImGuiContext& g = *GImGui; if (url == NULL) url = label; - if (TextLink(label)) - if (g.PlatformIO.Platform_OpenInShellFn != NULL) - g.PlatformIO.Platform_OpenInShellFn(&g, url); + bool pressed = TextLink(label); + if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL) + g.PlatformIO.Platform_OpenInShellFn(&g, url); SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label if (BeginPopupContextItem()) { @@ -1473,6 +1554,7 @@ void ImGui::TextLinkOpenURL(const char* label, const char* url) SetClipboardText(url); EndPopup(); } + return pressed; } //------------------------------------------------------------------------- @@ -1659,7 +1741,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); - RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); + RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); } else { @@ -1755,27 +1837,31 @@ static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs) // Shrink excess width from a set of item, by removing width from the larger items first. // Set items Width to -1.0f to disable shrinking this item. -void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess) +void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min) { if (count == 1) { if (items[0].Width >= 0.0f) - items[0].Width = ImMax(items[0].Width - width_excess, 1.0f); + items[0].Width = ImMax(items[0].Width - width_excess, width_min); return; } - ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); + ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last. int count_same_width = 1; - while (width_excess > 0.0f && count_same_width < count) + while (width_excess > 0.001f && count_same_width < count) { while (count_same_width < count && items[0].Width <= items[count_same_width].Width) count_same_width++; float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f); + max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item); if (max_width_to_remove_per_item <= 0.0f) break; - float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); + float base_width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item); for (int item_n = 0; item_n < count_same_width; item_n++) - items[item_n].Width -= width_to_remove_per_item; - width_excess -= width_to_remove_per_item * count_same_width; + { + float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min); + items[item_n].Width -= width_to_remove_for_this_item; + width_excess -= width_to_remove_for_this_item; + } } // Round width and redistribute remainder @@ -1821,7 +1907,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF ImGuiContext& g = *GImGui; ImGuiWindow* window = GetCurrentWindow(); - ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags; + ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags; g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values if (window->SkipItems) return false; @@ -1856,7 +1942,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF // Render shape const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); if (!(flags & ImGuiComboFlags_NoArrowButton)) @@ -1890,7 +1976,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF if (!popup_open) return false; - g.NextWindowData.Flags = backup_next_window_data_flags; + g.NextWindowData.HasFlags = backup_next_window_data_flags; return BeginComboPopup(popup_id, bb, flags); } @@ -1905,7 +1991,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags // Set popup size float w = bb.GetWidth(); - if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint) + if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint) { g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w); } @@ -1919,9 +2005,9 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4; else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20; ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX); - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size constraint_min.x = w; - if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) + if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f) constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items); SetNextWindowSizeConstraints(constraint_min, constraint_max); } @@ -1952,7 +2038,8 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags if (!ret) { EndPopup(); - IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above + if (!g.IO.ConfigDebugBeginReturnValueOnce && !g.IO.ConfigDebugBeginReturnValueLoop) // Begin may only return false with those debug tools activated. + IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above return false; } g.BeginComboDepth++; @@ -2036,7 +2123,7 @@ static const char* Items_SingleStringGetter(void* data, int idx) { if (idx == items_count) break; - p += strlen(p) + 1; + p += ImStrlen(p) + 1; items_count++; } return *p ? p : NULL; @@ -2053,7 +2140,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo preview_value = getter(user_data, *current_item); // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. - if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)) + if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)) SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items))); if (!BeginCombo(label, preview_value, ImGuiComboFlags_None)) @@ -2104,7 +2191,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open while (*p) { - p += strlen(p) + 1; + p += ImStrlen(p) + 1; items_count++; } bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items); @@ -2166,6 +2253,7 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] = { sizeof(float), "float", "%.3f","%f" }, // ImGuiDataType_Float (float are promoted to double in va_arg) { sizeof(double), "double","%f", "%lf" }, // ImGuiDataType_Double { sizeof(bool), "bool", "%d", "%d" }, // ImGuiDataType_Bool + { 0, "char*","%s", "%s" }, // ImGuiDataType_String }; IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT); @@ -2434,9 +2522,9 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { adjust_delta = g.IO.MouseDelta[axis]; - if (g.IO.KeyAlt) + if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) adjust_delta *= 1.0f / 100.0f; - if (g.IO.KeyShift) + if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks)) adjust_delta *= 10.0f; } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) @@ -2444,7 +2532,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow); const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast); - const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; + const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f; adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -2562,7 +2650,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v } if (g.ActiveId != id) return false; - if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) return false; switch (data_type) @@ -2609,11 +2697,11 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { - // Tabbing or CTRL-clicking on Drag turns it into an InputText + // Tabbing or Ctrl+Click on Drag turns it into an InputText const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id)); const bool make_active = (clicked || double_clicked || g.NavActivateId == id); @@ -2632,6 +2720,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, temp_input_is_active = true; } + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -2643,7 +2735,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, if (temp_input_is_active) { - // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + // Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) bool clamp_enabled = false; if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL)) { @@ -2658,7 +2750,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding); // Drag behavior @@ -3049,14 +3141,14 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0; if (decimal_precision > 0) { - input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds + input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds if (tweak_slow) input_delta /= 10.0f; } else { if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow) - input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Gamepad/keyboard tweak speeds in integer steps + input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps else input_delta /= 100.0f; } @@ -3104,7 +3196,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ } if (set_new_value) - if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) + if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly)) set_new_value = false; if (set_new_value) @@ -3209,11 +3301,11 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id); if (!temp_input_is_active) { - // Tabbing or CTRL-clicking on Slider turns it into an input box + // Tabbing or Ctrl+Click on Slider turns it into an input box const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); const bool make_active = (clicked || g.NavActivateId == id); if (make_active && clicked) @@ -3222,6 +3314,10 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) temp_input_is_active = true; + // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert) + if (make_active) + memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); + if (make_active && !temp_input_is_active) { SetActiveID(id, window); @@ -3233,14 +3329,14 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat if (temp_input_is_active) { - // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) + // Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp) const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL); } // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // Slider behavior @@ -3329,7 +3425,8 @@ bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, fl format = "%.0f deg"; float v_deg = (*v_rad) * 360.0f / (2 * IM_PI); bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags); - *v_rad = v_deg * (2 * IM_PI) / 360.0f; + if (value_changed) + *v_rad = v_deg * (2 * IM_PI) / 360.0f; return value_changed; } @@ -3375,7 +3472,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags); const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id); if (clicked || g.NavActivateId == id) { @@ -3389,7 +3486,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Draw frame const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // Slider behavior @@ -3569,7 +3666,7 @@ int ImParseFormatPrecision(const char* fmt, int default_precision) return (precision == INT_MAX) ? default_precision : precision; } -// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets) +// Create text input in place of another active widget (e.g. used when doing a Ctrl+Click on drag/slider widgets) // FIXME: Facilitate using this in variety of other situations. // FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but // the expected relationship between TempInputXXX functions and LastItemData is a little fishy. @@ -3583,7 +3680,7 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* ClearActiveID(); g.CurrentWindow->DC.CursorPos = bb.Min; - g.LastItemData.InFlags |= ImGuiItemFlags_AllowDuplicateId; + g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem); if (init) { @@ -3595,12 +3692,13 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* } // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set! -// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. +// This is intended: this way we allow Ctrl+Click manual input to set a value out of bounds, for maximum flexibility. // However this may not be ideal for all uses, as some user code may break on out of bound values. bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max) { // FIXME: May need to clarify display behavior if format doesn't contain %. // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405 + ImGuiContext& g = *GImGui; const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type); char fmt_buf[32]; char data_buf[32]; @@ -3610,8 +3708,8 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; - + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData. bool value_changed = false; if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) { @@ -3630,6 +3728,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG } // Only mark as edited if new value is different + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; if (value_changed) MarkItemEdited(id); @@ -3640,7 +3739,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data) { ImGuiContext& g = *GImGui; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasRefVal; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal; memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size); } @@ -3654,11 +3753,12 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; + IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()! if (format == NULL) format = DataTypeGetInfo(data_type)->PrintFmt; - void* p_data_default = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; + void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue; char buf[64]; if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0) @@ -3666,8 +3766,10 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data else DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); - flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. - flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; + // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited. + // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; + flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (p_step == NULL) @@ -3689,21 +3791,22 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data // Step buttons const ImVec2 backup_frame_padding = style.FramePadding; style.FramePadding.x = style.FramePadding.y; - ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) BeginDisabled(); + PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("-", ImVec2(button_size, button_size), button_flags)) + if (ButtonEx("-", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } SameLine(0, style.ItemInnerSpacing.x); - if (ButtonEx("+", ImVec2(button_size, button_size), button_flags)) + if (ButtonEx("+", ImVec2(button_size, button_size))) { DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step); value_changed = true; } + PopItemFlag(); if (flags & ImGuiInputTextFlags_ReadOnly) EndDisabled(); @@ -3718,6 +3821,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data PopID(); EndGroup(); } + + g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; if (value_changed) MarkItemEdited(g.LastItemData.ID); @@ -3813,9 +3918,6 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f // - InputText() // - InputTextWithHint() // - InputTextMultiline() -// - InputTextGetCharInfo() [Internal] -// - InputTextReindexLines() [Internal] -// - InputTextReindexLinesRange() [Internal] // - InputTextEx() [Internal] // - DebugNodeInputTextState() [Internal] //------------------------------------------------------------------------- @@ -3825,6 +3927,7 @@ namespace ImStb #include "imstb_textedit.h" } +// If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp! bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) { IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() @@ -3842,75 +3945,12 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, si return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } -// This is only used in the path where the multiline widget is inactivate. -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) -{ - int line_count = 0; - const char* s = text_begin; - while (true) - { - const char* s_eol = strchr(s, '\n'); - line_count++; - if (s_eol == NULL) - { - s = s + strlen(s); - break; - } - s = s_eol + 1; - } - *out_text_end = s; - return line_count; -} - -// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA() -static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) +static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags) { ImGuiContext& g = *ctx; - ImFont* font = g.Font; - const float line_height = g.FontSize; - const float scale = line_height / font->FontSize; - - ImVec2 text_size = ImVec2(0, 0); - float line_width = 0.0f; - - const char* s = text_begin; - while (s < text_end) - { - unsigned int c = (unsigned int)*s; - if (c < 0x80) - s += 1; - else - s += ImTextCharFromUtf8(&c, s, text_end); - - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - if (stop_on_new_line) - break; - continue; - } - if (c == '\r') - continue; - - const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; - line_width += char_width; - } - - if (text_size.x < line_width) - text_size.x = line_width; - - if (out_offset) - *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n - - if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n - text_size.y += line_height; - - if (remaining) - *remaining = s; - - return text_size; + ImGuiInputTextState* obj = &g.InputTextState; + IM_ASSERT(text_end_display >= text_begin && text_end_display <= text_end); + return ImFontCalcTextSizeEx(g.Font, g.FontSize, FLT_MAX, obj->WrapWidth, text_begin, text_end_display, text_end, out_remaining, out_offset, flags); } // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) @@ -3920,15 +3960,15 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c // - ...but we don't use that feature. namespace ImStb { -static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenA; } -static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenA); return obj->TextA[idx]; } -static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextA.Size); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } +static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } +static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx >= 0 && idx <= obj->TextLen); return obj->TextSrc[idx]; } +static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; } static char STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) { - const char* text = obj->TextA.Data; + const char* text = obj->TextSrc; const char* text_remaining = NULL; - const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->CurLenA, &text_remaining, NULL, true); + const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, text + obj->TextLen, &text_remaining, NULL, ImDrawTextFlags_StopOnNewLine | ImDrawTextFlags_WrapKeepBlanks); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -3942,18 +3982,18 @@ static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* ob static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { - if (idx >= obj->CurLenA) - return obj->CurLenA + 1; + if (idx >= obj->TextLen) + return obj->TextLen + 1; unsigned int c; - return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextA.Size); + return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen); } static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx) { if (idx <= 0) return -1; - const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx); - return (int)(p - obj->TextA.Data); + const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx); + return (int)(p - obj->TextSrc); } static bool ImCharIsSeparatorW(unsigned int c) @@ -3972,14 +4012,14 @@ static bool ImCharIsSeparatorW(unsigned int c) static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx) { - // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. + // When ImGuiInputTextFlags_Password is set, we don't want actions such as Ctrl+Arrow to leak the fact that underlying data are blanks or separators. if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - const char* curr_p = obj->TextA.Data + idx; - const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); - unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextA.Size); - unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextA.Size); + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen); bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); @@ -3992,10 +4032,10 @@ static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) return 0; - const char* curr_p = obj->TextA.Data + idx; - const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p); - unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextA.Size); - unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextA.Size); + const char* curr_p = obj->TextSrc + idx; + const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p); + unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen); + unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen); bool prev_white = ImCharIsBlankW(prev_c); bool prev_separ = ImCharIsSeparatorW(prev_c); @@ -4012,7 +4052,7 @@ static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) } static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { - int len = obj->CurLenA; + int len = obj->TextLen; idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); while (idx < len && !is_word_boundary_from_left(obj, idx)) idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); @@ -4021,7 +4061,7 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); - int len = obj->CurLenA; + int len = obj->TextLen; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx); return idx > len ? len : idx; @@ -4030,35 +4070,105 @@ static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL +// Reimplementation of stb_textedit_move_line_start()/stb_textedit_move_line_end() which supports word-wrapping. +static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor) +{ + if (state->single_line) + return 0; + + if (obj->WrapWidth > 0.0f) + { + ImGuiContext& g = *obj->Ctx; + const char* p_cursor = obj->TextSrc + cursor; + const char* p_bol = ImStrbol(p_cursor, obj->TextSrc); + const char* p = p_bol; + const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough + while (p >= p_bol) + { + const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks); + if (p == p_cursor) // If we are already on a visible beginning-of-line, return real beginning-of-line (would be same as regular handler below) + return (int)(p_bol - obj->TextSrc); + if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left) + return (int)(p_bol - obj->TextSrc); + if (p_eol >= p_cursor) + return (int)(p - obj->TextSrc); + p = (*p_eol == '\n') ? p_eol + 1 : p_eol; + } + } + + // Regular handler, same as stb_textedit_move_line_start() + while (cursor > 0) + { + int prev_cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, cursor); + if (STB_TEXTEDIT_GETCHAR(obj, prev_cursor) == STB_TEXTEDIT_NEWLINE) + break; + cursor = prev_cursor; + } + return cursor; +} + +static int STB_TEXTEDIT_MOVELINEEND_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor) +{ + int n = STB_TEXTEDIT_STRINGLEN(obj); + if (state->single_line) + return n; + + if (obj->WrapWidth > 0.0f) + { + ImGuiContext& g = *obj->Ctx; + const char* p_cursor = obj->TextSrc + cursor; + const char* p = ImStrbol(p_cursor, obj->TextSrc); + const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough + while (p < text_end) + { + const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks); + cursor = (int)(p_eol - obj->TextSrc); + if (p_eol == p_cursor && obj->LastMoveDirectionLR != ImGuiDir_Left) // If we are already on a visible end-of-line, switch to regular handle + break; + if (p_eol > p_cursor) + return cursor; + p = (*p_eol == '\n') ? p_eol + 1 : p_eol; + } + } + // Regular handler, same as stb_textedit_move_line_end() + while (cursor < n && STB_TEXTEDIT_GETCHAR(obj, cursor) != STB_TEXTEDIT_NEWLINE) + cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, cursor); + return cursor; +} + +#define STB_TEXTEDIT_MOVELINESTART STB_TEXTEDIT_MOVELINESTART_IMPL +#define STB_TEXTEDIT_MOVELINEEND STB_TEXTEDIT_MOVELINEEND_IMPL + static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n) { + // Offset remaining text (+ copy zero terminator) + IM_ASSERT(obj->TextSrc == obj->TextA.Data); char* dst = obj->TextA.Data + pos; - + char* src = obj->TextA.Data + pos + n; + memmove(dst, src, obj->TextLen - n - pos + 1); obj->Edited = true; - obj->CurLenA -= n; - - // Offset remaining text (FIXME-OPT: Use memmove) - const char* src = obj->TextA.Data + pos + n; - while (char c = *src++) - *dst++ = c; - *dst = '\0'; + obj->TextLen -= n; } -static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) +static int STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len) { const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; - const int text_len = obj->CurLenA; + const int text_len = obj->TextLen; IM_ASSERT(pos <= text_len); - if (!is_resizable && (new_text_len + obj->CurLenA + 1 > obj->BufCapacityA)) - return false; + // We support partial insertion (with a mod in stb_textedit.h) + const int avail = obj->BufCapacity - 1 - obj->TextLen; + if (!is_resizable && new_text_len > avail) + new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion. + if (new_text_len == 0) + return 0; // Grow internal buffer if needed - if (new_text_len + text_len + 1 > obj->TextA.Size) + IM_ASSERT(obj->TextSrc == obj->TextA.Data); + if (text_len + new_text_len + 1 > obj->TextA.Size && is_resizable) { - if (!is_resizable) - return false; obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1); + obj->TextSrc = obj->TextA.Data; } char* text = obj->TextA.Data; @@ -4067,10 +4177,10 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch memcpy(text + pos, new_text, (size_t)new_text_len); obj->Edited = true; - obj->CurLenA += new_text_len; - obj->TextA[obj->CurLenA] = '\0'; + obj->TextLen += new_text_len; + obj->TextA[obj->TextLen] = '\0'; - return true; + return new_text_len; } // We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) @@ -4100,12 +4210,13 @@ static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ch // the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) { - stb_text_makeundo_replace(str, state, 0, str->CurLenA, text_len); - ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenA); + stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len); + ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen); state->cursor = state->select_start = state->select_end = 0; if (text_len <= 0) return; - if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) + int text_len_inserted = ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len); + if (text_len_inserted > 0) { state->cursor = state->select_start = state->select_end = text_len; state->has_preferred_x = 0; @@ -4121,6 +4232,7 @@ ImGuiInputTextState::ImGuiInputTextState() { memset(this, 0, sizeof(*this)); Stb = IM_NEW(ImStbTexteditState); + memset(Stb, 0, sizeof(*Stb)); } ImGuiInputTextState::~ImGuiInputTextState() @@ -4133,6 +4245,11 @@ void ImGuiInputTextState::OnKeyPressed(int key) stb_textedit_key(this, Stb, key); CursorFollow = true; CursorAnimReset(); + const int key_u = (key & ~STB_TEXTEDIT_K_SHIFT); + if (key_u == STB_TEXTEDIT_K_LEFT || key_u == STB_TEXTEDIT_K_LINESTART || key_u == STB_TEXTEDIT_K_TEXTSTART || key_u == STB_TEXTEDIT_K_BACKSPACE || key_u == STB_TEXTEDIT_K_WORDLEFT) + LastMoveDirectionLR = ImGuiDir_Left; + else if (key_u == STB_TEXTEDIT_K_RIGHT || key_u == STB_TEXTEDIT_K_LINEEND || key_u == STB_TEXTEDIT_K_TEXTEND || key_u == STB_TEXTEDIT_K_DELETE || key_u == STB_TEXTEDIT_K_WORDRIGHT) + LastMoveDirectionLR = ImGuiDir_Right; } void ImGuiInputTextState::OnCharPressed(unsigned int c) @@ -4141,40 +4258,40 @@ void ImGuiInputTextState::OnCharPressed(unsigned int c) // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great. char utf8[5]; ImTextCharToUtf8(utf8, c); - stb_textedit_text(this, Stb, utf8, (int)strlen(utf8)); + stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8)); CursorFollow = true; CursorAnimReset(); } // Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header. void ImGuiInputTextState::CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking -void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, CurLenA); Stb->select_start = ImMin(Stb->select_start, CurLenA); Stb->select_end = ImMin(Stb->select_end, CurLenA); } +void ImGuiInputTextState::CursorClamp() { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); } bool ImGuiInputTextState::HasSelection() const { return Stb->select_start != Stb->select_end; } void ImGuiInputTextState::ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; } int ImGuiInputTextState::GetCursorPos() const { return Stb->cursor; } int ImGuiInputTextState::GetSelectionStart() const { return Stb->select_start; } int ImGuiInputTextState::GetSelectionEnd() const { return Stb->select_end; } -void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = CurLenA; Stb->has_preferred_x = 0; } -void ImGuiInputTextState::ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } -void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } -void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } +float ImGuiInputTextState::GetPreferredOffsetX() const { return Stb->has_preferred_x ? Stb->preferred_x : -1; } +void ImGuiInputTextState::SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; } +void ImGuiInputTextState::ReloadUserBufAndSelectAll() { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } +void ImGuiInputTextState::ReloadUserBufAndKeepSelection() { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } +void ImGuiInputTextState::ReloadUserBufAndMoveToEnd() { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } ImGuiInputTextCallbackData::ImGuiInputTextCallbackData() { memset(this, 0, sizeof(*this)); } -// Public API to manipulate UTF-8 text -// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) +// Public API to manipulate UTF-8 text from within a callback. // FIXME: The existence of this rarely exercised code path is a bit of a nuisance. +// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar +// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both. void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count) { IM_ASSERT(pos + bytes_count <= BufTextLen); char* dst = Buf + pos; const char* src = Buf + pos + bytes_count; - while (char c = *src++) - *dst++ = c; - *dst = '\0'; + memmove(dst, src, BufTextLen - bytes_count - pos + 1); if (CursorPos >= pos + bytes_count) CursorPos -= bytes_count; @@ -4191,22 +4308,29 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (new_text == new_text_end) return; + ImGuiContext& g = *Ctx; + ImGuiInputTextState* obj = &g.InputTextState; + IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID); const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; - const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text); - if (new_text_len + BufTextLen >= BufSize) - { - if (!is_resizable) - return; + const bool is_readonly = (Flags & ImGuiInputTextFlags_ReadOnly) != 0; + int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text); + + // We support partial insertion (with a mod in stb_textedit.h) + const int avail = BufSize - 1 - BufTextLen; + if (!is_resizable && new_text_len > avail) + new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion. + if (new_text_len == 0) + return; - // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!) - ImGuiContext& g = *Ctx; - ImGuiInputTextState* edit_state = &g.InputTextState; - IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TextA.Data); + // Grow internal buffer if needed + if (new_text_len + BufTextLen + 1 > obj->TextA.Size && is_resizable && !is_readonly) + { + IM_ASSERT(Buf == obj->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TextA.reserve(new_buf_size + 1); - Buf = edit_state->TextA.Data; - BufSize = edit_state->BufCapacityA = new_buf_size; + obj->TextA.resize(new_buf_size + 1); + obj->TextSrc = obj->TextA.Data; + Buf = obj->TextA.Data; + BufSize = obj->BufCapacity = new_buf_size; } if (BufTextLen != pos) @@ -4214,11 +4338,40 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char)); Buf[BufTextLen + new_text_len] = '\0'; + BufDirty = true; + BufTextLen += new_text_len; if (CursorPos >= pos) CursorPos += new_text_len; + CursorPos = ImClamp(CursorPos, 0, BufTextLen); SelectionStart = SelectionEnd = CursorPos; - BufDirty = true; - BufTextLen += new_text_len; +} + +void ImGui::PushPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); + ImFontGlyph* glyph = g.FontBaked->FindGlyph('*'); + g.InputTextPasswordFontBackupFlags = g.Font->Flags; + backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex; + backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX; + backup->IndexLookup.swap(g.FontBaked->IndexLookup); + backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX); + g.Font->Flags |= ImFontFlags_NoLoadGlyphs; + g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph); + g.FontBaked->FallbackAdvanceX = glyph->AdvanceX; +} + +void ImGui::PopPasswordFont() +{ + ImGuiContext& g = *GImGui; + ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked; + g.Font->Flags = g.InputTextPasswordFontBackupFlags; + g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex; + g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX; + g.FontBaked->IndexLookup.swap(backup->IndexLookup); + g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX); + IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0); } // Return false to discard a character. @@ -4231,7 +4384,13 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im if (c < 0x20) { bool pass = false; - pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code) + pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code) + if (c == '\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \n with a space + { + c = *p_char = ' '; + pass = true; + } + pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0; if (!pass) return false; @@ -4268,7 +4427,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im if (c == '.' || c == ',') c = c_decimal_point; - // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) + // Full-width -> half-width conversion for numeric fields: https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font. if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal)) @@ -4322,26 +4481,23 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im return true; } -// Find the shortest single replacement we can make to get the new text from the old text. -// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end. +// Find the shortest single replacement we can make to get from old_buf to new_buf +// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately. // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. -static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a) +static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length) { - const char* old_buf = state->CallbackTextBackup.Data; - const int old_length = state->CallbackTextBackup.Size - 1; - - const int shorter_length = ImMin(old_length, new_length_a); + const int shorter_length = ImMin(old_length, new_length); int first_diff; for (first_diff = 0; first_diff < shorter_length; first_diff++) - if (old_buf[first_diff] != new_buf_a[first_diff]) + if (old_buf[first_diff] != new_buf[first_diff]) break; - if (first_diff == old_length && first_diff == new_length_a) + if (first_diff == old_length && first_diff == new_length) return; int old_last_diff = old_length - 1; - int new_last_diff = new_length_a - 1; + int new_last_diff = new_length - 1; for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) - if (old_buf[old_last_diff] != new_buf_a[new_last_diff]) + if (old_buf[old_last_diff] != new_buf[new_last_diff]) break; const int insert_len = new_last_diff - first_diff + 1; @@ -4370,19 +4526,109 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) else { IM_ASSERT(state->TextA.Data != 0); - g.InputTextDeactivatedState.TextA.resize(state->CurLenA + 1); - memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->CurLenA + 1); + IM_ASSERT(state->TextA[state->TextLen] == 0); + g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1); + memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1); } } +static int* ImLowerBound(int* in_begin, int* in_end, int v) +{ + int* in_p = in_begin; + for (size_t count = (size_t)(in_end - in_p); count > 0; ) + { + size_t count2 = count >> 1; + int* mid = in_p + count2; + if (*mid < v) + { + in_p = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return in_p; +} + +// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool? +// 'max_output_buffer_size' happens to be a meaningful optimization to avoid writing the full line_index when not necessarily needed (e.g. very large buffer, scrolled up, inactive) +static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, float wrap_width, int max_output_buffer_size, const char** out_buf_end) +{ + ImGuiContext& g = *GImGui; + int size = 0; + const char* s; + if (flags & ImGuiInputTextFlags_WordWrap) + { + for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s) + { + if (size++ <= max_output_buffer_size) + line_index->Offsets.push_back((int)(s - buf)); + s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); + } + } + else if (buf_end != NULL) + { + for (s = buf; s < buf_end; s = s ? s + 1 : buf_end) + { + if (size++ <= max_output_buffer_size) + line_index->Offsets.push_back((int)(s - buf)); + s = (const char*)ImMemchr(s, '\n', buf_end - s); + } + } + else + { + const char* s_eol; + for (s = buf; ; s = s_eol + 1) + { + if (size++ <= max_output_buffer_size) + line_index->Offsets.push_back((int)(s - buf)); + if ((s_eol = strchr(s, '\n')) != NULL) + continue; + s += strlen(s); + break; + } + } + if (out_buf_end != NULL) + *out_buf_end = buf_end = s; + if (size == 0) + { + line_index->Offsets.push_back(0); + size++; + } + if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size) + { + line_index->Offsets.push_back((int)(buf_end - buf)); + size++; + } + return size; +} + +static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n) +{ + const char* cursor_ptr = buf + cursor_n; + int* it_begin = line_index->Offsets.begin(); + int* it_end = line_index->Offsets.end(); + const int* it = ImLowerBound(it_begin, it_end, cursor_n); + if (it > it_begin) + if (it == it_end || *it != cursor_n || (state != NULL && state->WrapWidth > 0.0f && state->LastMoveDirectionLR == ImGuiDir_Right && cursor_ptr[-1] != '\n' && cursor_ptr[-1] != 0)) + it--; + + const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it); + const char* line_start = line_index->get_line_begin(buf, line_no); + ImVec2 offset; + offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x; + offset.y = (line_no + 1) * g.FontSize; + return offset; +} + // Edit a string of text // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match // Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. // - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. -// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h -// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are -// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) +// - If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp! bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) { ImGuiWindow* window = GetCurrentWindow(); @@ -4392,6 +4638,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(buf != NULL && buf_size >= 0); IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline))); // Multiline does not not work with left-trimming + IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0); // WordWrap does not work with Password mode. + IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0); // WordWrap does not work in single-line mode. ImGuiContext& g = *GImGui; ImGuiIO& io = g.IO; @@ -4425,8 +4674,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ item_data_backup = g.LastItemData; window->DC.CursorPos = backup_pos; - // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping. - if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput)) + // Prevent NavActivation from explicit Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping. + if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && !(g.NavActivateFlags & ImGuiActivateFlags_FromFocusApi) && (flags & ImGuiInputTextFlags_AllowTabInput)) g.NavActivateId = 0; // Prevent NavActivate reactivating in BeginChild() when we are already active. @@ -4462,14 +4711,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable)) return false; } - const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.InFlags); + + // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417) + bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover); if (hovered) SetMouseCursor(ImGuiMouseCursor_TextInput); + if (hovered && g.NavHighlightItemUnderNav) + hovered = false; // We are only allowed to access the state if we are already the active widget. ImGuiInputTextState* state = GetInputTextState(id); - if (g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) + if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) flags |= ImGuiInputTextFlags_ReadOnly; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; @@ -4478,6 +4731,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_resizable) IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! + // Word-wrapping: enforcing a fixed width not altered by vertical scrollbar makes things easier, notably to track cursor reliably and avoid one-frame glitches. + // Instead of using ImGuiWindowFlags_AlwaysVerticalScrollbar we account for that space if the scrollbar is not visible. + const bool is_wordwrap = (flags & ImGuiInputTextFlags_WordWrap) != 0; + float wrap_width = 0.0f; + if (is_wordwrap) + wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize)); + const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard))); const bool user_clicked = hovered && io.MouseClicked[0]; @@ -4488,60 +4748,64 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; - const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf); + const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf); const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); const bool init_state = (init_make_active || user_scroll_active); - if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf) + if (init_reload_from_user_buf) + { + int new_len = (int)ImStrlen(buf); + IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?"); + state->WantReloadUserBuf = false; + InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len); + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + state->TextLen = new_len; + memcpy(state->TextA.Data, buf, state->TextLen + 1); + state->Stb->select_start = state->ReloadSelectionStart; + state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below + } + else if ((init_state && g.ActiveId != id) || init_changed_specs) { // Access state even if we don't own it yet. state = &g.InputTextState; state->CursorAnimReset(); - state->ReloadUserBuf = false; // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) InputTextDeactivateHook(state->ID); + // Take a copy of the initial buffer value. // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) - const int buf_len = (int)strlen(buf); - if (!init_reload_from_user_buf) - { - // Take a copy of the initial buffer value. - state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->InitialTextA.Data, buf, buf_len + 1); - } + const int buf_len = (int)ImStrlen(buf); + IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?"); + state->TextToRevertTo.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextToRevertTo.Data, buf, buf_len + 1); // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? - bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf); - if (recycle_state && (state->CurLenA != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0))) + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; // Start edition state->ID = id; - state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. - state->CurLenA = (int)strlen(buf); - memcpy(state->TextA.Data, buf, state->CurLenA + 1); - - if (recycle_state) + state->TextLen = buf_len; + if (!is_readonly) { - // Recycle existing cursor/selection/undo stack but clamp position - // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. - state->CursorClamp(); + state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->TextA.Data, buf, state->TextLen + 1); } - else - { - state->Scroll = ImVec2(0.0f, 0.0f); + + // Find initial scroll position for right alignment + state->Scroll = ImVec2(0.0f, 0.0f); + if (flags & ImGuiInputTextFlags_ElideLeft) + state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f); + + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + if (!recycle_state) stb_textedit_initialize_state(state->Stb, !is_multiline); - } - if (init_reload_from_user_buf) - { - state->Stb->select_start = state->ReloadSelectionStart; - state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; - state->CursorClamp(); - } - else if (!is_multiline) + if (!is_multiline) { if (flags & ImGuiInputTextFlags_AutoSelectAll) select_all = true; @@ -4566,15 +4830,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { // Declare some inputs, the other are registered and polled via Shortcut() routing system. + // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combination into individual shortcuts. + const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End }; + for (ImGuiKey key : always_owned_keys) + SetKeyOwner(key, id); if (user_clicked) SetKeyOwner(ImGuiKey_MouseLeft, id); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) + { g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); - SetKeyOwner(ImGuiKey_Enter, id); - SetKeyOwner(ImGuiKey_KeypadEnter, id); - SetKeyOwner(ImGuiKey_Home, id); - SetKeyOwner(ImGuiKey_End, id); + SetKeyOwner(ImGuiKey_UpArrow, id); + SetKeyOwner(ImGuiKey_DownArrow, id); + } if (is_multiline) { SetKeyOwner(ImGuiKey_PageUp, id); @@ -4587,7 +4855,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Expose scroll in a manner that is agnostic to us using a child window if (is_multiline && state != NULL) state->Scroll.y = draw_window->Scroll.y; + + // Read-only mode always ever read from source buffer. Refresh TextLen when active. + if (is_readonly && state != NULL) + state->TextLen = (int)ImStrlen(buf); + if (state != NULL) + state->CursorClamp(); + //if (is_readonly && state != NULL) + // state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation. } + if (state != NULL) + state->TextSrc = is_readonly ? buf : state->TextA.Data; // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) if (g.ActiveId == id && state == NULL) @@ -4605,22 +4883,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Select the buffer to render. const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; - const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); // Password pushes a temporary font with only a fallback glyph if (is_password && !is_displaying_hint) + PushPasswordFont(); + + // Word-wrapping: attempt to keep cursor in view while resizing frame/parent + // FIXME-WORDWRAP: It would be better to preserve same relative offset. + if (is_wordwrap && state != NULL && state->ID == id && state->WrapWidth != wrap_width) { - const ImFontGlyph* glyph = g.Font->FindGlyph('*'); - ImFont* password_font = &g.InputTextPasswordFont; - password_font->FontSize = g.Font->FontSize; - password_font->Scale = g.Font->Scale; - password_font->Ascent = g.Font->Ascent; - password_font->Descent = g.Font->Descent; - password_font->ContainerAtlas = g.Font->ContainerAtlas; - password_font->FallbackGlyph = glyph; - password_font->FallbackAdvanceX = glyph->AdvanceX; - IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); - PushFont(password_font); + state->CursorCenterY = true; + state->WrapWidth = wrap_width; + render_cursor = true; } // Process mouse inputs and character inputs @@ -4628,8 +4903,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { IM_ASSERT(state != NULL); state->Edited = false; - state->BufCapacityA = buf_size; + state->BufCapacity = buf_size; state->Flags = flags; + state->WrapWidth = wrap_width; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. // Down the line we should have a cleaner library-wide concept of Selected vs Active. @@ -4651,7 +4927,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if ((multiclick_count % 2) == 0) { // Double-click: Select word - // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: + // We always use the "Mac" word advance for double-click select vs Ctrl+Right which use the platform dependent variant: // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n'; if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol) @@ -4667,9 +4943,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Triple-click: Select line const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n'; + state->WrapWidth = 0.0f; // Temporarily disable wrapping so we use real line start. state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); + state->WrapWidth = wrap_width; if (!is_eol && is_multiline) { ImSwap(state->Stb->select_start, state->Stb->select_end); @@ -4718,7 +4996,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } // Process regular text input (before we check for Return because using some IME will effectively send a Return?) - // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. + // We ignore Ctrl inputs, but need to allow Alt+Ctrl as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl); if (io.InputQueueCharacters.Size > 0) { @@ -4751,19 +5029,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End - // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use Ctrl+A and Ctrl+B: former would be handled by InputText) // Otherwise we could simply assume that we own the keys as we are active. const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat; const bool is_cut = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); const bool is_copy = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, 0, id)) && !is_password && (!is_multiline || state->HasSelection()); const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly; const bool is_undo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; - const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable; + const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable; const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id); // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true); + const bool is_enter = Shortcut(ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiKey_KeypadEnter, f_repeat, id); + const bool is_ctrl_enter = Shortcut(ImGuiMod_Ctrl | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_KeypadEnter, f_repeat, id); const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false)); const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id)); @@ -4798,11 +5077,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } - else if (is_enter_pressed || is_gamepad_validate) + else if (is_enter || is_ctrl_enter || is_gamepad_validate) { // Determine if we turn Enter into a \n character bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; - if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl)) + if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line != is_ctrl_enter)) { validated = true; if (io.ConfigInputTextEnterKeepActive && !is_multiline) @@ -4812,7 +5091,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else if (!is_readonly) { - unsigned int c = '\n'; // Insert new line + // Insert new line + unsigned int c = '\n'; if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnCharPressed(c); } @@ -4821,7 +5101,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { if (flags & ImGuiInputTextFlags_EscapeClearsAll) { - if (buf[0] != 0) + if (state->TextA.Data[0] != 0) { revert_edit = true; } @@ -4852,13 +5132,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Cut, Copy if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) { + // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy. const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0; - const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->CurLenA; - - char backup = state->TextA.Data[ie]; - state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings - SetClipboardText(state->TextA.Data + ib); - state->TextA.Data[ie] = backup; + const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen; + g.TempBuffer.reserve(ie - ib + 1); + memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib); + g.TempBuffer.Data[ie - ib] = 0; + SetClipboardText(g.TempBuffer.Data); } if (is_cut) { @@ -4873,26 +5153,29 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (const char* clipboard = GetClipboardText()) { // Filter pasted buffer - const int clipboard_len = (int)strlen(clipboard); - char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1); - int clipboard_filtered_len = 0; + const int clipboard_len = (int)ImStrlen(clipboard); + const char* clipboard_end = clipboard + clipboard_len; + ImVector clipboard_filtered; + clipboard_filtered.reserve(clipboard_len + 1); for (const char* s = clipboard; *s != 0; ) { unsigned int c; - int len = ImTextCharFromUtf8(&c, s, NULL); - s += len; + int in_len = ImTextCharFromUtf8(&c, s, clipboard_end); + s += in_len; if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) continue; - memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len); - clipboard_filtered_len += len; + char c_utf8[5]; + ImTextCharToUtf8(c_utf8, c); + int out_len = (int)ImStrlen(c_utf8); + clipboard_filtered.resize(clipboard_filtered.Size + out_len); + memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len); } - clipboard_filtered[clipboard_filtered_len] = 0; - if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len); + clipboard_filtered.push_back(0); + stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1); state->CursorFollow = true; } - MemFree(clipboard_filtered); } } @@ -4911,38 +5194,36 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (flags & ImGuiInputTextFlags_EscapeClearsAll) { // Clear input - IM_ASSERT(buf[0] != 0); + IM_ASSERT(state->TextA.Data[0] != 0); apply_new_text = ""; apply_new_text_length = 0; value_changed = true; - IMSTB_TEXTEDIT_CHARTYPE empty_string; + char empty_string = 0; stb_textedit_replace(state, state->Stb, &empty_string, 0); } - else if (strcmp(buf, state->InitialTextA.Data) != 0) + else if (strcmp(state->TextA.Data, state->TextToRevertTo.Data) != 0) { - apply_new_text = state->InitialTextA.Data; - apply_new_text_length = state->InitialTextA.Size - 1; + apply_new_text = state->TextToRevertTo.Data; + apply_new_text_length = state->TextToRevertTo.Size - 1; // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. - // Push records into the undo stack so we can CTRL+Z the revert operation itself + // Push records into the undo stack so we can Ctrl+Z the revert operation itself value_changed = true; - stb_textedit_replace(state, state->Stb, state->InitialTextA.Data, state->InitialTextA.Size - 1); + stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1); } } - // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer - // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. - // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. - // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage + // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId, + // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks. + // If we do that, need to ensure that as special case, 'validated == true' also writes back. + // This also allows the user to use InputText() without maintaining any user-side storage. // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). - const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); + const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0); if (apply_edit_back_to_user_buffer) { - // Apply new value immediately - copy modified buffer back + // Apply current edited text immediately. // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer - // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. - // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. // User callback if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) @@ -4985,19 +5266,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ callback_data.UserData = callback_user_data; // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925 - state->CallbackTextBackup.resize(state->CurLenA + 1); - memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->CurLenA + 1); - char* callback_buf = is_readonly ? buf : state->TextA.Data; + IM_ASSERT(callback_buf == state->TextSrc); + state->CallbackTextBackup.resize(state->TextLen + 1); + memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1); + callback_data.EventKey = event_key; callback_data.Buf = callback_buf; - callback_data.BufTextLen = state->CurLenA; - callback_data.BufSize = state->BufCapacityA; + callback_data.BufTextLen = state->TextLen; + callback_data.BufSize = state->BufCapacity; callback_data.BufDirty = false; - - const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor; - const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start; - const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end; + callback_data.CursorPos = state->Stb->cursor; + callback_data.SelectionStart = state->Stb->select_start; + callback_data.SelectionEnd = state->Stb->select_end; // Call user code callback(&callback_data); @@ -5005,29 +5286,29 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Read back what user may have modified callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == state->BufCapacityA); + IM_ASSERT(callback_data.BufSize == state->BufCapacity); IM_ASSERT(callback_data.Flags == flags); - const bool buf_dirty = callback_data.BufDirty; - if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; } - if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; } - if (buf_dirty) + if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor) + state->CursorFollow = true; + state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen); + state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen); + state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen); + if (callback_data.BufDirty) { // Callback may update buffer and thus set buf_dirty even in read-only mode. - IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! - InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? - state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() - state->TextA.Size = state->CurLenA + 1; + IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen); + state->TextLen = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() state->CursorAnimReset(); } } } // Will copy result string if modified - if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) + if (!is_readonly && strcmp(state->TextSrc, buf) != 0) { - apply_new_text = state->TextA.Data; - apply_new_text_length = state->CurLenA; + apply_new_text = state->TextSrc; + apply_new_text_length = state->TextLen; value_changed = true; } } @@ -5079,19 +5360,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Otherwise request text input ahead for next frame. if (g.ActiveId == id && clear_active_id) ClearActiveID(); - else if (g.ActiveId == id) - g.WantTextInputNextFrame = 1; // Render frame if (!is_multiline) { - RenderNavHighlight(frame_bb, id); + RenderNavCursor(frame_bb, id); RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); } - const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.0f, 0.0f); + ImRect clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size + if (is_multiline) + clip_rect.ClipWith(draw_window->ClipRect); // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. @@ -5099,20 +5380,64 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const int buf_display_max_length = 2 * 1024 * 1024; const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595 const char* buf_display_end = NULL; // We have specialized paths below for setting the length + + // Display hint when contents is empty + // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) + const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); + if (new_is_displaying_hint != is_displaying_hint) + { + if (is_password && !is_displaying_hint) + PopPasswordFont(); + is_displaying_hint = new_is_displaying_hint; + if (is_password && !is_displaying_hint) + PushPasswordFont(); + } if (is_displaying_hint) { buf_display = hint; - buf_display_end = hint + strlen(hint); + buf_display_end = hint + ImStrlen(hint); + } + else + { + if (render_cursor || render_selection || g.ActiveId == id) + buf_display_end = buf_display + state->TextLen; //-V595 + else if (is_multiline && !is_wordwrap) + buf_display_end = NULL; // Inactive multi-line: end of buffer will be output by InputTextLineIndexBuild() special strchr() path. + else + buf_display_end = buf_display + ImStrlen(buf_display); } + // Calculate visibility + int line_visible_n0 = 0, line_visible_n1 = 1; + if (is_multiline) + CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1); + + // Build line index for easy data access (makes code below simpler and faster) + ImGuiTextIndex* line_index = &g.InputTextLineIndex; + line_index->Offsets.resize(0); + int line_count = 1; + if (is_multiline) + { + // If scrolling is expected to change build full index. + // FIXME-OPT: Could append to index when new value of line_visible_n1 becomes bigger, see second call to CalcClipRectVisibleItemsY() below. + bool will_scroll_y = state && ((state->CursorFollow && render_cursor) || (state->CursorCenterY && (render_cursor || render_selection))); + line_count = InputTextLineIndexBuild(flags, line_index, buf_display, buf_display_end, wrap_width, will_scroll_y ? INT_MAX : line_visible_n1 + 1, buf_display_end ? NULL : &buf_display_end); + } + line_index->EndOffset = (int)(buf_display_end - buf_display); + line_visible_n1 = ImMin(line_visible_n1, line_count); + + // Store text height (we don't need width) + text_size = ImVec2(inner_size.x, line_count * g.FontSize); + //GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255)); + + // Calculate blinking cursor position + const ImVec2 cursor_offset = render_cursor && state ? InputTextLineIndexGetPosOffset(g, state, line_index, buf_display, buf_display_end, state->Stb->cursor) : ImVec2(0.0f, 0.0f); + ImVec2 draw_scroll; + // Render text. We currently only render selection when the widget is active or while scrolling. - // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. + const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); if (render_cursor || render_selection) { - IM_ASSERT(state != NULL); - if (!is_displaying_hint) - buf_display_end = buf_display + state->CurLenA; - // Render text (with cursor and selection) // This is going to be messy. We need to: // - Display the text (this alone can be more easily clipped) @@ -5120,48 +5445,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const char* text_begin = state->TextA.Data; - const char* text_end = text_begin + state->CurLenA; - ImVec2 cursor_offset, select_start_offset; - - { - // Find lines numbers straddling cursor and selection min position - int cursor_line_no = render_cursor ? -1 : -1000; - int selmin_line_no = render_selection ? -1 : -1000; - const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL; - const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL; - - // Count lines and find line number for cursor and selection ends - int line_count = 1; - if (is_multiline) - { - for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++) - { - if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; } - if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; } - line_count++; - } - } - if (cursor_line_no == -1) - cursor_line_no = line_count; - if (selmin_line_no == -1) - selmin_line_no = line_count; - - // Calculate 2d position by finding the beginning of the line and measuring distance - cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x; - cursor_offset.y = cursor_line_no * g.FontSize; - if (selmin_line_no >= 0) - { - select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x; - select_start_offset.y = selmin_line_no * g.FontSize; - } - - // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) - if (is_multiline) - text_size = ImVec2(inner_size.x, line_count * g.FontSize); - } + IM_ASSERT(state != NULL); + state->LineCount = line_count; // Scroll + float new_scroll_y = scroll_y; if (render_cursor && state->CursorFollow) { // Horizontal scroll in chunks of quarter width @@ -5176,7 +5464,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { - state->Scroll.y = 0.0f; + state->Scroll.x = 0.0f; } // Vertical scroll @@ -5184,98 +5472,110 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Test if cursor is vertically visible if (cursor_offset.y - g.FontSize < scroll_y) - scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + new_scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y) - scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; - const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); - scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y); - draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag - draw_window->Scroll.y = scroll_y; + new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; } - state->CursorFollow = false; } - - // Draw selection - const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); - if (render_selection) + if (state->CursorCenterY) { - const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); - const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); - - ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. - float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. - float bg_offy_dn = is_multiline ? 0.0f : 2.0f; - ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; - for (const char* p = text_selected_begin; p < text_selected_end; ) - { - if (rect_pos.y > clip_rect.w + g.FontSize) - break; - if (rect_pos.y < clip_rect.y) - { - p = (const char*)memchr((void*)p, '\n', text_selected_end - p); - p = p ? p + 1 : text_selected_end; - } - else - { - ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines - ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); - rect.ClipWith(clip_rect); - if (rect.Overlaps(clip_rect)) - draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); - rect_pos.x = draw_pos.x - draw_scroll.x; - } - rect_pos.y += g.FontSize; - } + if (is_multiline) + new_scroll_y = cursor_offset.y - g.FontSize - (inner_size.y * 0.5f - style.FramePadding.y); + state->CursorCenterY = false; + render_cursor = false; } - - // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. - // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. - if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + if (new_scroll_y != scroll_y) { - ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f); + scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y); + draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag + draw_window->Scroll.y = scroll_y; + CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1); + line_visible_n1 = ImMin(line_visible_n1, line_count); } - // Draw blinking cursor - if (render_cursor) + // Draw selection + draw_scroll.x = state->Scroll.x; + if (render_selection) { - state->CursorAnim += io.DeltaTime; - bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; - ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); - if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + const ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. + const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + const float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines - // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) - if (!is_readonly) + const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end); + const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end); + for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++) { - g.PlatformImeData.WantVisible = true; - g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); - g.PlatformImeData.InputLineHeight = g.FontSize; + const char* p = line_index->get_line_begin(buf_display, line_n); + const char* p_eol = line_index->get_line_end(buf_display, line_n); + const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n'); + if (p_eol_is_wrap) + p_eol++; + const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p; + const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol; + + float rect_width = 0.0f; + if (line_selected_begin < line_selected_end) + rect_width += CalcTextSize(line_selected_begin, line_selected_end).x; + if (text_selected_begin <= p_eol && text_selected_end > p_eol && !p_eol_is_wrap) + rect_width += bg_eol_width; // So we can see selected empty lines + if (rect_width == 0.0f) + continue; + + ImRect rect; + rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x; + rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize; + rect.Max.x = rect.Min.x + rect_width; + rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize; + rect.Min.y -= bg_offy_up; + rect.ClipWith(clip_rect); + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); } } } - else + + // Find render position for right alignment (single-line only) + if (g.ActiveId != id && flags & ImGuiInputTextFlags_ElideLeft) + draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive? + + // Render text + if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1)) + g.Font->RenderText(draw_window->DrawList, g.FontSize, + draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize), + text_col, clip_rect.AsVec4(), + line_index->get_line_begin(buf_display, line_visible_n0), + line_index->get_line_end(buf_display, line_visible_n1 - 1), + wrap_width, ImDrawTextFlags_WrapKeepBlanks | ImDrawTextFlags_CpuFineClip); + + // Render blinking cursor + if (render_cursor) { - // Render text only (no selection, no cursor) - if (is_multiline) - text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width - else if (!is_displaying_hint && g.ActiveId == id) - buf_display_end = buf_display + state->CurLenA; - else if (!is_displaying_hint) - buf_display_end = buf_display + strlen(buf_display); + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031) - if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + // This is required for some backends (SDL3) to start emitting character/text inputs. + // As per #6341, make sure we don't set that on the deactivating frame. + if (!is_readonly && g.ActiveId == id) { - ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler) + ime_data->WantVisible = true; + ime_data->WantTextInput = true; + ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + ime_data->InputLineHeight = g.FontSize; + ime_data->ViewportId = window->Viewport->ID; } } if (is_password && !is_displaying_hint) - PopFont(); + PopPasswordFont(); if (is_multiline) { @@ -5291,10 +5591,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y)) { g.LastItemData.ID = id; - g.LastItemData.InFlags = item_data_backup.InFlags; + g.LastItemData.ItemFlags = item_data_backup.ItemFlags; g.LastItemData.StatusFlags = item_data_backup.StatusFlags; } } + if (state) + state->TextSrc = NULL; // Log as text if (g.LogEnabled && (!is_password || is_displaying_hint)) @@ -5306,7 +5608,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); - if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) + if (value_changed) MarkItemEdited(id); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable); @@ -5324,7 +5626,11 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) ImStb::StbUndoState* undo_state = &stb_state->undostate; Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); DebugLocateItemOnHover(state->ID); - Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("TextLen: %d, Cursor: %d%s, Selection: %d..%d", state->TextLen, stb_state->cursor, + (state->Flags & ImGuiInputTextFlags_WordWrap) ? (state->LastMoveDirectionLR == ImGuiDir_Left ? " (L)" : " (R)") : "", + stb_state->select_start, stb_state->select_end); + Text("BufCapacity: %d, LineCount: %d", state->BufCapacity, state->LineCount); + Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state @@ -5401,7 +5707,7 @@ static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V) // Edit colors components (each component in 0.0f..1.0f range). // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. -// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item. +// With typical options: Left-click on color square to open color picker. Right-click to open option menu. Ctrl+Click over input fields to edit them and TAB to go to next item. bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags) { ImGuiWindow* window = GetCurrentWindow(); @@ -5620,7 +5926,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag // Drag and Drop Target // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. - if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) + if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget()) { bool accepted_drag_drop = false; if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) @@ -5848,7 +6154,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoLabel)) Text("Current"); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip; ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)); if (ref_col != NULL) { @@ -5888,7 +6194,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl if ((flags & ImGuiColorEditFlags_NoInputs) == 0) { PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x); - ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf; + ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview; ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker; if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0) if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB)) @@ -6064,8 +6370,8 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held); - if (flags & ImGuiColorEditFlags_NoAlpha) - flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf); + if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque)) + flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf); ImVec4 col_rgb = col; if (flags & ImGuiColorEditFlags_InputHSV) @@ -6084,25 +6390,28 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f); - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight); + else + window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); } else { // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha - ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha; - if (col_source.w < 1.0f) + ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb; + if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0) RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding); else window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding); } - RenderNavHighlight(bb, id); + RenderNavCursor(bb, id); if ((flags & ImGuiColorEditFlags_NoBorder) == 0) { if (g.Style.FrameBorderSize > 0.0f) RenderFrameBorder(bb.Min, bb.Max, rounding); else - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border + window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border // FIXME-DPI } // Drag and Drop Source @@ -6121,7 +6430,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl // Tooltip if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip)) - ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)); + ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_)); return pressed; } @@ -6162,7 +6471,8 @@ void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2); ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]); int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]); - ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz); + ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_; + ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz); SameLine(); if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_)) { @@ -6187,8 +6497,9 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_); if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; - g.LockMarkEdited++; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); ImGuiColorEditFlags opts = g.ColorEditOptions; if (allow_opt_inputs) { @@ -6230,8 +6541,8 @@ void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags) } g.ColorEditOptions = opts; + PopItemFlag(); EndPopup(); - g.LockMarkEdited--; } void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags) @@ -6240,8 +6551,9 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar); if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context")) return; + ImGuiContext& g = *GImGui; - g.LockMarkEdited++; + PushItemFlag(ImGuiItemFlags_NoMarkEdited, true); if (allow_opt_picker) { ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function @@ -6270,8 +6582,8 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl if (allow_opt_picker) Separator(); CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar); } + PopItemFlag(); EndPopup(); - g.LockMarkEdited--; } //------------------------------------------------------------------------- @@ -6281,6 +6593,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl // - TreeNodeV() // - TreeNodeEx() // - TreeNodeExV() +// - TreeNodeStoreStackData() [Internal] // - TreeNodeBehavior() [Internal] // - TreePush() // - TreePop() @@ -6402,7 +6715,7 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) ImGuiStorage* storage = window->DC.StateStorage; bool is_open; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasOpen) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen) { if (g.NextItemData.OpenCond & ImGuiCond_Always) { @@ -6439,18 +6752,26 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags) // Store ImGuiTreeNodeStackData for just submitted node. // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. -static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) +static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); - ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); + ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; tree_node_data->ID = g.LastItemData.ID; tree_node_data->TreeFlags = flags; - tree_node_data->InFlags = g.LastItemData.InFlags; + tree_node_data->ItemFlags = g.LastItemData.ItemFlags; tree_node_data->NavRect = g.LastItemData.NavRect; + + // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees. + const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0; + tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX; + tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1; + tree_node_data->DrawLinesToNodesY2 = -FLT_MAX; window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); + if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes) + window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth); } // When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. @@ -6476,10 +6797,11 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // We vertically grow up to current line height up the typical widget height. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); + const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; frame_bb.Min.y = window->DC.CursorPos.y; - frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; + frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; frame_bb.Max.y = window->DC.CursorPos.y + frame_height; if (display_frame) { @@ -6493,15 +6815,15 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing ImRect interact_bb = frame_bb; - if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) + if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); // Compute open and multi-select states before ItemAdd() as it clear NextItem data. - ImGuiID storage_id = (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; + ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id; bool is_open = TreeNodeUpdateNextOpen(storage_id, flags); bool is_visible; - if (span_all_columns) + if (span_all_columns || span_all_columns_label) { // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. const float backup_clip_rect_min_x = window->ClipRect.Min.x; @@ -6519,14 +6841,18 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; g.LastItemData.DisplayRect = frame_bb; - // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: + // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsToParent enabled: // Store data for the current depth to allow returning to this node from any child item. // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). - // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. + // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsToParent by default or move it to ImGuiStyle. bool store_tree_node_stack_data = false; + if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0) + flags |= g.Style.TreeLinesFlags; + const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f); if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) { - if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) + store_tree_node_stack_data = draw_tree_lines; + if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive) if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) store_tree_node_stack_data = true; } @@ -6534,15 +6860,22 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; if (!is_visible) { - if (store_tree_node_stack_data && is_open) - TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1)))) + { + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway. + if (frame_bb.Min.y >= window->ClipRect.Max.y) + window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done + } + if (is_open && store_tree_node_stack_data) + TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); return is_open; } - if (span_all_columns) + if (span_all_columns || span_all_columns_label) { TablePushBackgroundChannel(); g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect; @@ -6550,7 +6883,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l } ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None; - if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) + if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) button_flags |= ImGuiButtonFlags_AllowOverlap; if (!is_leaf) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; @@ -6562,7 +6895,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x; const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2); - const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow; @@ -6581,6 +6914,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; else button_flags |= ImGuiButtonFlags_PressedOnClickRelease; + if (flags & ImGuiTreeNodeFlags_NoNavFocus) + button_flags |= ImGuiButtonFlags_NoNavFocus; bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; const bool was_selected = selected; @@ -6596,7 +6931,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l else { if (window != g.HoveredWindow || !is_mouse_x_over_arrow) - button_flags |= ImGuiButtonFlags_NoKeyModifiers; + button_flags |= ImGuiButtonFlags_NoKeyModsAllowed; } bool hovered, held; @@ -6609,7 +6944,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select)) toggled = true; // Single click if (flags & ImGuiTreeNodeFlags_OpenOnArrow) - toggled |= is_mouse_x_over_arrow && !g.NavDisableMouseHover; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job + toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2) toggled = true; // Double click } @@ -6658,15 +6993,17 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // Render { const ImU32 text_col = GetColorU32(ImGuiCol_Text); - ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact; + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact; if (is_multi_select) - nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle if (display_frame) { // Framed type const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); - RenderNavHighlight(frame_bb, id, nav_highlight_flags); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6686,7 +7023,9 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); } - RenderNavHighlight(frame_bb, id, nav_highlight_flags); + RenderNavCursor(frame_bb, id, nav_render_cursor_flags); + if (span_all_columns && !span_all_columns_label) + TablePopBackgroundChannel(); if (flags & ImGuiTreeNodeFlags_Bullet) RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); else if (!is_leaf) @@ -6695,18 +7034,21 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l LogSetNextTextDecoration(">", NULL); } - if (span_all_columns) - TablePopBackgroundChannel(); + if (draw_tree_lines) + TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f)); // Label if (display_frame) RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size); else RenderText(text_pos, label, label_end, false); + + if (span_all_columns_label) + TablePopBackgroundChannel(); } - if (store_tree_node_stack_data && is_open) - TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() + if (is_open && store_tree_node_stack_data) + TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID() if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice @@ -6714,6 +7056,64 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l return is_open; } +// Draw horizontal line from our parent node +// This is only called for visible child nodes so we are not too fussy anymore about performances +void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0) + return; + + ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + float x1 = ImTrunc(parent_data->DrawLinesX1); + float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); + float y = ImTrunc(target_pos.y); + float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; + parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); + if (x1 >= x2) + return; + if (rounding > 0.0f) + { + x1 += 0.5f + rounding; + window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); + if (x1 < x2) + window->DrawList->PathLineTo(ImVec2(x2, y)); + window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize); + } + else + { + window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + } +} + +// Draw vertical line of the hierarchy +void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y); + float y2 = data->DrawLinesToNodesY2; + if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull) + { + float y2_full = window->DC.CursorPos.y; + if (g.CurrentTable) + y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full); + y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f); + if (y2 + (g.Style.ItemSpacing.y + g.Style.TreeLinesRounding) < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y + y2 = y2_full; + } + y2 = ImMin(y2, window->ClipRect.Max.y); + if (y2 <= y1) + return; + float x = ImTrunc(data->DrawLinesX1); + if (data->DrawLinesTableColumn != -1) + TablePushColumnChannel(data->DrawLinesTableColumn); + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + if (data->DrawLinesTableColumn != -1) + TablePopColumnChannel(); +} + void ImGui::TreePush(const char* str_id) { ImGuiWindow* window = GetCurrentWindow(); @@ -6748,18 +7148,23 @@ void ImGui::TreePop() window->DC.TreeDepth--; ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); - if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request + if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) { - ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); + const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; IM_ASSERT(data->ID == window->IDStack.back()); - if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) - { - // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) + + // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsToParent is enabled) + if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); - } + + // Draw hierarchy lines + if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y) + TreeNodeDrawLineToTreePop(data); + g.TreeNodeStack.pop_back(); window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; + window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask; } IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. @@ -6779,7 +7184,7 @@ void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond) ImGuiContext& g = *GImGui; if (g.CurrentWindow->SkipItems) return; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen; g.NextItemData.OpenVal = is_open; g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always); } @@ -6790,7 +7195,7 @@ void ImGui::SetNextItemStorageID(ImGuiID storage_id) ImGuiContext& g = *GImGui; if (g.CurrentWindow->SkipItems) return; - g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasStorageID; + g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID; g.NextItemData.StorageId = storage_id; } @@ -6877,13 +7282,9 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth)) size.x = ImMax(label_size.x, max_x - min_x); - // Text stays at the submission position, but bounding box may be extended on both sides - const ImVec2 text_min = pos; - const ImVec2 text_max(min_x + size.x, pos.y + size.y); - // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos. - ImRect bb(min_x, pos.y, text_max.x, text_max.y); + ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y); if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) { const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; @@ -6916,7 +7317,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl is_visible = ItemAdd(bb, id, NULL, extra_item_flags); } - const bool is_multi_select = (g.LastItemData.InFlags & ImGuiItemFlags_IsMultiSelect) != 0; + const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0; if (!is_visible) if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd) return false; @@ -6944,7 +7345,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } - if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.InFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } + if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; } // Multi-selection support (header) const bool was_selected = selected; @@ -6956,6 +7357,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + bool auto_selected = false; // Multi-selection support (footer) if (is_multi_select) @@ -6972,17 +7374,18 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // - (2) usage will fail with clipped items // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId) - if (g.NavJustMovedToId == id) - selected = pressed = true; + if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0) + selected = pressed = auto_selected = true; } - // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) { - if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) { SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect) - g.NavDisableHighlight = true; + if (g.IO.ConfigNavCursorVisibleAuto) + g.NavCursorVisible = false; } } if (pressed) @@ -6997,20 +7400,16 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight); if (highlighted || selected) { - // FIXME-MULTISELECT: Styling: Color for 'selected' elements? ImGuiCol_HeaderSelected - ImU32 col; - if (selected && !highlighted) - col = GetColorU32(ImLerp(GetStyleColorVec4(ImGuiCol_Header), GetStyleColorVec4(ImGuiCol_HeaderHovered), 0.5f)); - else - col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106) + ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header); RenderFrame(bb.Min, bb.Max, col, false, 0.0f); } if (g.NavId == id) { - ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding; + ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding; if (is_multi_select) - nav_highlight_flags |= ImGuiNavHighlightFlags_AlwaysDraw; // Always show the nav rectangle - RenderNavHighlight(bb, id, nav_highlight_flags); + nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle + RenderNavCursor(bb, id, nav_render_cursor_flags); } } @@ -7022,11 +7421,12 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl PopColumnsBackground(); } + // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) - RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); // Automatically close popups - if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.InFlags & ImGuiItemFlags_AutoClosePopups)) + if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); if (disabled_item && !disabled_global) @@ -7086,7 +7486,7 @@ ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags f // Append to buffer const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1; - int buffer_len = (int)strlen(data->SearchBuffer); + int buffer_len = (int)ImStrlen(data->SearchBuffer); bool select_request = false; for (ImWchar w : g.IO.InputQueueCharacters) { @@ -7181,7 +7581,7 @@ int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, else idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data); if (idx != -1) - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); return idx; } @@ -7380,7 +7780,7 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag ImRect box_select_r = bs->BoxSelectRectCurr; box_select_r.ClipWith(scope_rect); window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling - window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavHighlight)); // FIXME-MULTISELECT: Styling + window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling // Scroll const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; @@ -7464,6 +7864,12 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel if (flags & ImGuiMultiSelectFlags_BoxSelect2d) flags &= ~ImGuiMultiSelectFlags_BoxSelect1d; + // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) + // They should perhaps be stacked properly? + if (ImGuiTable* table = g.CurrentTable) + if (table->CurrentColumn != -1) + TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); ms->Clear(); @@ -7541,7 +7947,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel } } - // Shortcut: Select all (CTRL+A) + // Shortcut: Select all (Ctrl+A) if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll)) if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A)) request_select_all = true; @@ -7578,7 +7984,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() if (ms->IsFocused) { // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. - if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) + if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure) { IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; @@ -7686,7 +8092,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags if (ms->LoopRequestSetAll != -1) selected = (ms->LoopRequestSetAll == 1); - // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) + // When using Shift+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection) // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function) if (ms->IsKeyboardSetRange) { @@ -7717,7 +8123,7 @@ void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags // Alter button behavior flags // To handle drag and drop of multiple items we need to avoid clearing selection on click. - // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. + // Enabling this test makes actions using Ctrl+Shift delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items. if (p_button_flags != NULL) { ImGuiButtonFlags button_flags = *p_button_flags; @@ -7821,8 +8227,8 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } // Right-click handling. - // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816 - if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0) + // FIXME-MULTISELECT: Maybe should be moved to Selectable()? Also see #5816, #8200, #9015 + if (hovered && IsMouseClicked(1) && (flags & (ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoSelectOnRightClick)) == 0) { if (g.ActiveId != 0 && g.ActiveId != id) ClearActiveID(); @@ -7916,7 +8322,7 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data); } - // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect) + // Update/store the selection state of the Source item (used by Ctrl+Shift, when Source is unselected we perform a range unselect) if (storage->RangeSrcItem == item_data) storage->RangeSelected = selected ? 1 : 0; @@ -8098,7 +8504,7 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass. // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector to reduce bandwidth, but this is a reasonable trade off to reuse code. // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling - // left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.) + // left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each. // FIXME-OPT: For each block of consecutive SetRange request: // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage. // - rewrite sorted storage a single time. @@ -8130,7 +8536,7 @@ void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) else { // Append insertion + single sort likely be faster. - // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1 + // Use req.RangeDirection to set order field so that Shift+Clicking from 1 to 5 is different than Shift+Clicking from 5 to 1 const int size_before_amends = _Storage.Data.Size; int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0); for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection) @@ -8177,15 +8583,10 @@ void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io) //------------------------------------------------------------------------- // This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label. -// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as: -// if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle)) -// { .... } -// ImGui::EndChild(); -// ImGui::SameLine(); -// ImGui::AlignTextToFramePadding(); -// ImGui::Text("Label"); +// This handle some subtleties with capturing info from the label. +// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle. // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" -// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). +// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.5f * item_height). bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) { ImGuiContext& g = *GImGui; @@ -8564,12 +8965,13 @@ bool ImGui::BeginMenuBar() IM_ASSERT(!window->DC.MenuBarAppending); BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore - PushID("##menubar"); + PushID("##MenuBar"); // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect. // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy. + const float border_top = ImMax(window->WindowBorderSize * 0.5f - window->TitleBarHeight, 0.0f); ImRect bar_rect = window->MenuBarRect(); - ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize), IM_ROUND(bar_rect.Min.y + window->WindowBorderSize), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), IM_ROUND(bar_rect.Max.y)); + ImRect clip_rect(IM_ROUND(bar_rect.Min.x + window->WindowBorderSize * 0.5f), IM_ROUND(bar_rect.Min.y + border_top), IM_ROUND(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize * 0.5f))), IM_ROUND(bar_rect.Max.y)); clip_rect.ClipWith(window->OuterRectClipped); PushClipRect(clip_rect.Min, clip_rect.Max, false); @@ -8590,6 +8992,10 @@ void ImGui::EndMenuBar() return; ImGuiContext& g = *GImGui; + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" + IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); + IM_ASSERT(window->DC.MenuBarAppending); + // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings. if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) { @@ -8605,17 +9011,20 @@ void ImGui::EndMenuBar() IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary) FocusWindow(window); SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]); - g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection. - g.NavDisableMouseHover = g.NavMousePosDirty = true; + // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto? + if (g.NavCursorVisible) + { + g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again + g.NavCursorHideFrames = 2; + } + g.NavHighlightItemUnderNav = g.NavMousePosDirty = true; NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat } } - IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" - IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar); - IM_ASSERT(window->DC.MenuBarAppending); PopClipRect(); PopID(); + IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'" window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos. // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here. @@ -8682,22 +9091,33 @@ bool ImGui::BeginMainMenuBar() float height = GetFrameHeight(); bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); - - if (is_open) - BeginMenuBar(); - else + if (!is_open) + { End(); + return false; + } + + // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356) + g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings; + BeginMenuBar(); return is_open; } void ImGui::EndMainMenuBar() { + ImGuiContext& g = *GImGui; + if (!g.CurrentWindow->DC.MenuBarAppending) + { + IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar + return; + } + EndMenuBar(); + g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356) // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window // FIXME: With this strategy we won't be able to restore a NULL focus. - ImGuiContext& g = *GImGui; - if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest) + if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0) FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild); End(); @@ -8750,7 +9170,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (g.MenusIdSubmittedThisFrame.contains(id)) { if (menu_is_open) - menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) else g.NextWindowData.ClearFlags(); // we behave like Begin() and need to consume those values return menu_is_open; @@ -8781,7 +9201,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups; if (window->DC.LayoutType == ImGuiLayoutType_Horizontal) { - // Menu inside an horizontal menu bar + // Menu inside a horizontal menu bar // Selectable extend their highlight by half ItemSpacing in each direction. // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight); @@ -8790,6 +9210,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float w = label_size.x; ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y)); + LogSetNextTextDecoration("[", "]"); RenderText(text_pos, label); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). @@ -8798,7 +9219,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) { // Menu inside a regular/vertical menu // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. - // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.) popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y); float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); @@ -8806,6 +9227,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w); ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); + LogSetNextTextDecoration("", ">"); RenderText(text_pos, label); if (icon_w > 0.0f) RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); @@ -8814,7 +9236,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (!enabled) EndDisabled(); - const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover; + const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav; if (menuset_is_open) PopItemFlag(); @@ -8849,7 +9271,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not) // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon. // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.) - if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover && g.ActiveId == 0) + if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0) want_close = true; // Open @@ -8864,7 +9286,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) { want_open = want_open_nav_init = true; NavMoveRequestCancel(); - NavRestoreHighlightAfterMove(); + SetNavCursorVisibleAfterMove(); } } else @@ -8910,7 +9332,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImGuiLastItemData last_item_in_parent = g.LastItemData; SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos. PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding - menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display) + menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open may be 'false' when the popup is completely clipped (e.g. zero size display) PopStyleVar(); if (menu_is_open) { @@ -9004,7 +9426,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut { // Menu item inside a vertical menu // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f. - // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system. + // Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.) float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f; float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f; float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); @@ -9019,6 +9441,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut if (shortcut_w > 0.0f) { PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); + LogSetNextTextDecoration("(", ")"); RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false); PopStyleColor(); } @@ -9081,6 +9504,7 @@ struct ImGuiTabBarSection { int TabCount; // Number of tabs in this section. float Width; // Sum of width of tabs in this section (after shrinking down) + float WidthAfterShrinkMinWidth; float Spacing; // Horizontal spacing at the end of the section. ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); } @@ -9141,6 +9565,19 @@ static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) return ImGuiPtrOrIndex(tab_bar); } +ImGuiTabBar* ImGui::TabBarFindByID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + return g.TabBars.GetByKey(id); +} + +// Remove TabBar data (currently only used by TestEngine) +void ImGui::TabBarRemove(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + g.TabBars.Remove(tab_bar->ID, tab_bar); +} + bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; @@ -9152,8 +9589,8 @@ bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); tab_bar->ID = id; - tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); - tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMinX = tab_bar_bb.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f); + tab_bar->SeparatorMaxX = tab_bar_bb.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f); //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false)) flags |= ImGuiTabBarFlags_IsFocused; return BeginTabBarEx(tab_bar, tab_bar_bb, flags); @@ -9273,6 +9710,10 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) ImGuiContext& g = *GImGui; tab_bar->WantLayout = false; + // Track selected tab when resizing our parent down + const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth()); + tab_bar->BarRectPrevWidth = tab_bar->BarRect.GetWidth(); + // Garbage collect by compacting list // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section) int tab_dst_n = 0; @@ -9349,6 +9790,9 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount }; g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size); + // Minimum shrink width + const float shrink_min_width = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed) ? g.Style.TabMinWidthShrink : 1.0f; + // Compute ideal tabs widths + store them into shrink buffer ImGuiTabItem* most_recently_selected_tab = NULL; int curr_section_n = -1; @@ -9371,10 +9815,13 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) const char* tab_name = TabBarGetTabName(tab_bar, tab); const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument); tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x; + if ((tab->Flags & ImGuiTabItemFlags_Button) == 0) + tab->ContentWidth = ImMax(tab->ContentWidth, g.Style.TabMinWidthBase); int section_n = TabItemGetSectionIdx(tab); ImGuiTabBarSection* section = §ions[section_n]; section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); + section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f); curr_section_n = section_n; // Store data so we can build an array sorted by width if we need to shrink tabs down @@ -9386,19 +9833,28 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) } // Compute total ideal width (used for e.g. auto-resizing a window) + float width_all_tabs_after_min_width_shrink = 0.0f; tab_bar->WidthAllTabsIdeal = 0.0f; for (int section_n = 0; section_n < 3; section_n++) + { tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing; + width_all_tabs_after_min_width_shrink += sections[section_n].WidthAfterShrinkMinWidth + sections[section_n].Spacing; + } // Horizontal scrolling buttons - // (note that TabBarScrollButtons() will alter BarRect.Max.x) - if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll)) + // Important: note that TabBarScrollButtons() will alter BarRect.Max.x. + const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); + const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink; + tab_bar->ScrollButtonEnabled = ((width_all_tabs_to_use_for_scroll > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll); + if (tab_bar->ScrollButtonEnabled) if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar)) { scroll_to_tab_id = scroll_and_select_tab->ID; if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0) tab_bar->SelectedTabId = scroll_to_tab_id; } + if (scroll_to_tab_id == 0 && scroll_to_selected_tab) + scroll_to_tab_id = tab_bar->SelectedTabId; // Shrink widths if full tabs don't fit in their allocated space float section_0_w = sections[0].Width + sections[0].Spacing; @@ -9412,11 +9868,12 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore - if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible)) + const bool can_shrink = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed); + if (width_excess >= 1.0f && (can_shrink || !central_section_is_visible)) { int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount); int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0); - ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess); + ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess, shrink_min_width); // Apply shrunk values into tabs and sections for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++) @@ -9459,7 +9916,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) tab_bar->TabsNames.Buf.resize(0); // If we have lost the selected tab, select the next most recently active one - if (found_selected_tab_id == false) + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + if (found_selected_tab_id == false && !tab_bar_appearing) tab_bar->SelectedTabId = 0; if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; @@ -9471,7 +9929,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) // Apply request requests if (scroll_to_tab_id != 0) TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections); - else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow)) + else if (tab_bar->ScrollButtonEnabled && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow)) { const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH; const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX; @@ -9745,17 +10203,19 @@ static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) PushStyleColor(ImGuiCol_Text, arrow_col); PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + PushItemFlag(ImGuiItemFlags_ButtonRepeat | ImGuiItemFlags_NoNav, true); const float backup_repeat_delay = g.IO.KeyRepeatDelay; const float backup_repeat_rate = g.IO.KeyRepeatRate; g.IO.KeyRepeatDelay = 0.250f; g.IO.KeyRepeatRate = 0.200f; float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width); window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y); - if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) select_dir = -1; window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y); - if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick)) select_dir = +1; + PopItemFlag(); PopStyleColor(2); g.IO.KeyRepeatRate = backup_repeat_rate; g.IO.KeyRepeatDelay = backup_repeat_delay; @@ -9853,7 +10313,7 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!"); return false; } - IM_ASSERT(!(flags & ImGuiTabItemFlags_Button)); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! + IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead! bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) @@ -9899,6 +10359,23 @@ bool ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags) return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL); } +void ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + ImGuiTabBar* tab_bar = g.CurrentTabBar; + if (tab_bar == NULL) + { + IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } + SetNextItemWidth(width); + TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL); +} + bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) { // Layout whole tab bar if not already done @@ -9949,7 +10426,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // Calculate tab contents size ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument)); tab->RequestedWidth = -1.0f; - if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth) + if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth) size.x = tab->RequestedWidth = g.NextItemData.Width; if (tab_is_new) tab->Width = ImMax(1.0f, size.x); @@ -9972,7 +10449,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, else { tab->NameOffset = (ImS32)tab_bar->TabsNames.size(); - tab_bar->TabsNames.append(label, label + strlen(label) + 1); + tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1); } // Update selected tab @@ -9986,7 +10463,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } // Lock visibility - // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!) + // (Note: tab_contents_visible != tab_selected... because Ctrl+Tab operations may preview some tabs without selecting them!) bool tab_contents_visible = (tab_bar->VisibleTabId == id); if (tab_contents_visible) tab_bar->VisibleTabWasSubmitted = true; @@ -10044,8 +10521,11 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap); if (g.DragDropActive) button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + bool hovered, held, pressed; + if (flags & ImGuiTabItemFlags_Invisible) + hovered = held = pressed = false; + else + pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); if (pressed && !is_tab_button) TabBarQueueFocus(tab_bar, tab); @@ -10077,36 +10557,59 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, #endif // Render tab shape - ImDrawList* display_draw_list = window->DrawList; - const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); - TabItemBackground(display_draw_list, bb, flags, tab_col); - if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) - { - float x_offset = IM_TRUNC(0.4f * style.TabRounding); - if (x_offset < 2.0f * g.CurrentDpiScale) - x_offset = 0.0f; - float y_offset = 1.0f * g.CurrentDpiScale; - display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize); - } - RenderNavHighlight(bb, id); - - // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. - const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); - if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) - TabBarQueueFocus(tab_bar, tab); + const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible); + if (is_visible) + { + ImDrawList* display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) + { + // Might be moved to TabItemBackground() ? + ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); + if (style.TabRounding > 0.0f) + { + float rounding = style.TabRounding; + display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9); + display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11); + display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize); + } + else + { + display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); + } + } + RenderNavCursor(bb, id); - if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) - flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. + const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button) + TabBarQueueFocus(tab_bar, tab); - // Render tab label, process close button - const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; - bool just_closed; - bool text_clipped; - TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); - if (just_closed && p_open != NULL) - { - *p_open = false; - TabBarCloseTab(tab_bar, tab); + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Render tab label, process close button + const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, id) : 0; + bool just_closed; + bool text_clipped; + TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped); + if (just_closed && p_open != NULL) + { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Tooltip + // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) + // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) + // FIXME: This is a mess. + // FIXME: We may want disabled tab to still display the tooltip? + if (text_clipped && g.HoveredId == id && !held) + if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) + SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); } // Restore main window position so user can draw there @@ -10114,15 +10617,6 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, PopClipRect(); window->DC.CursorPos = backup_main_cursor_pos; - // Tooltip - // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok) - // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores) - // FIXME: This is a mess. - // FIXME: We may want disabled tab to still display the tooltip? - if (text_clipped && g.HoveredId == id && !held) - if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip)) - SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); - IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected if (is_tab_button) return pressed; @@ -10212,13 +10706,12 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, #endif // Render text label (with clipping + alpha gradient) + unsaved marker - ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); - ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; + ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); // Return clipped state ignoring the close button if (out_text_clipped) { - *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; + *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_ellipsis_clip_bb.Max.x; //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); } @@ -10232,13 +10725,24 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false bool close_button_pressed = false; bool close_button_visible = false; + bool is_hovered = g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id; // Any interaction account for this too. + if (close_button_id != 0) - if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton)) - if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id) - close_button_visible = true; - bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x); + { + if (is_contents_visible) + close_button_visible = (g.Style.TabCloseButtonMinWidthSelected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthSelected)); + else + close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected)); + } - if (close_button_visible) + // When tabs/document is unsaved, the unsaved marker takes priority over the close button. + const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered); + if (unsaved_marker_visible) + { + ImVec2 bullet_pos = button_pos + ImVec2(button_sz, button_sz) * 0.5f; + RenderBullet(draw_list, bullet_pos, GetColorU32(ImGuiCol_UnsavedMarker)); + } + else if (close_button_visible) { ImGuiLastItemData last_item_backup = g.LastItemData; if (CloseButton(close_button_id, button_pos)) @@ -10246,26 +10750,29 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, g.LastItemData = last_item_backup; // Close with middle mouse button - if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) + if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) close_button_pressed = true; } - else if (unsaved_marker_visible) - { - const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz)); - RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text)); - } // This is all rather complicated // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position) // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. - float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; + float ellipsis_max_x = text_ellipsis_clip_bb.Max.x; if (close_button_visible || unsaved_marker_visible) { - text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f); - text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; - ellipsis_max_x = text_pixel_clip_bb.Max.x; + const bool visible_without_hover = unsaved_marker_visible || (is_contents_visible ? g.Style.TabCloseButtonMinWidthSelected : g.Style.TabCloseButtonMinWidthUnselected) < 0.0f; + if (visible_without_hover) + { + text_ellipsis_clip_bb.Max.x -= button_sz * 0.90f; + ellipsis_max_x -= button_sz * 0.90f; + } + else + { + text_ellipsis_clip_bb.Max.x -= button_sz * 1.00f; + } } - RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); + LogSetNextTextDecoration("/", "\\"); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size); #if 0 if (!is_contents_visible) diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_textedit.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_textedit.h" index b7a761c8..583508f0 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_textedit.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_textedit.h" @@ -5,7 +5,8 @@ // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) // - Added name to struct or it may be forward declared in our code. // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) -// Grep for [DEAR IMGUI] to find the changes. +// - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. +// Grep for [DEAR IMGUI] to find some changes. // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* // stb_textedit.h - v1.14 - public domain - Sean Barrett @@ -39,6 +40,7 @@ // // VERSION HISTORY // +// !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. // 1.14 (2021-07-11) page up/down, various fixes // 1.13 (2019-02-07) fix bug in undo size management // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash @@ -141,12 +143,14 @@ // with previous char) // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character // (return type is int, -1 means not valid to insert) +// (not supported if you want to use UTF-8, see below) // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize // as manually wordwrapping for end-of-line positioning // // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i -// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) +// returns number of characters actually inserted. [DEAR IMGUI] // // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key // @@ -178,6 +182,13 @@ // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // +// To support UTF-8: +// +// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character +// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character +// Do NOT define STB_TEXTEDIT_KEYTOTEXT. +// Instead, call stb_textedit_text() directly for text contents. +// // Keyboard input must be encoded as a single integer value; e.g. a character code // and some bitflags that represent shift states. to simplify the interface, SHIFT must // be a bitflag, so we can test the shifted state of cursor movements to allow selection, @@ -250,8 +261,10 @@ // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are // transformed into text and stb_textedit_text() is automatically called. // -// text: [DEAR IMGUI] added 2024-09 -// call this to text inputs sent to the textfield. +// text: (added 2025) +// call this to directly send text input the textfield, which is required +// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT() +// cannot infer text length. // // // When rendering, you can read the cursor position and selection state from @@ -400,6 +413,16 @@ typedef struct #define IMSTB_TEXTEDIT_memmove memmove #endif +// [DEAR IMGUI] +// Functions must be implemented for UTF8 support +// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. +// There is not necessarily a '[DEAR IMGUI]' at the usage sites. +#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX +#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1) +#endif +#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX +#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1) +#endif ///////////////////////////////////////////////////////////////////////////// // @@ -407,7 +430,7 @@ typedef struct // // traverse the layout to locate the nearest character to a display position -static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) +static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line) { StbTexteditRow r; int n = STB_TEXTEDIT_STRINGLEN(str); @@ -417,6 +440,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) r.x0 = r.x1 = 0; r.ymin = r.ymax = 0; r.num_chars = 0; + *out_side_on_line = 0; // search rows to find one that straddles 'y' while (i < n) { @@ -436,7 +460,10 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) // below all text, return 'after' last character if (i >= n) + { + *out_side_on_line = 1; return n; + } // check if it's before the beginning of the line if (x < r.x0) @@ -449,6 +476,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) { float w = STB_TEXTEDIT_GETWIDTH(str, i, k); if (x < prev_x+w) { + *out_side_on_line = (k == 0) ? 0 : 1; if (x < prev_x+w/2) return k+i; else @@ -460,6 +488,7 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y) } // if the last character is a newline, return that. otherwise return 'after' the last character + *out_side_on_line = 1; if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) return i+r.num_chars-1; else @@ -471,6 +500,7 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st { // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // goes off the top or bottom of the text + int side_on_line; if( state->single_line ) { StbTexteditRow r; @@ -478,16 +508,18 @@ static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *st y = r.ymin; } - state->cursor = stb_text_locate_coord(str, x, y); + state->cursor = stb_text_locate_coord(str, x, y, &side_on_line); state->select_start = state->cursor; state->select_end = state->cursor; state->has_preferred_x = 0; + str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); } // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) { int p = 0; + int side_on_line; // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse // goes off the top or bottom of the text @@ -501,8 +533,9 @@ static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *sta if (state->select_start == state->select_end) state->select_start = state->cursor; - p = stb_text_locate_coord(str, x, y); + p = stb_text_locate_coord(str, x, y, &side_on_line); state->cursor = state->select_end = p; + str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); } ///////////////////////////////////////////////////////////////////////////// @@ -552,6 +585,8 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING STB_TEXTEDIT_LAYOUTROW(&r, str, i); if (n < i + r.num_chars) break; + if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling + break; if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line break; // [DEAR IMGUI] prev_start = i; @@ -648,15 +683,33 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt } } -// [DEAR IMGUI] -// Functions must be implemented for UTF8 support -// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. -// There is not necessarily a '[DEAR IMGUI]' at the usage sites. -#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX -#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1) +// [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping. +#ifndef STB_TEXTEDIT_MOVELINESTART +static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) +{ + if (state->single_line) + return 0; + while (cursor > 0) { + int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor); + if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) + break; + cursor = prev; + } + return cursor; +} +#define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start #endif -#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX -#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1) +#ifndef STB_TEXTEDIT_MOVELINEEND +static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) +{ + int n = STB_TEXTEDIT_STRINGLEN(str); + if (state->single_line) + return n; + while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE) + cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor); + return cursor; +} +#define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end #endif #ifdef STB_TEXTEDIT_IS_SPACE @@ -668,9 +721,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) #ifndef STB_TEXTEDIT_MOVEWORDLEFT static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) { - --c; // always move at least one character - while( c >= 0 && !is_word_boundary( str, c ) ) - --c; + c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character + while (c >= 0 && !is_word_boundary(str, c)) + c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c); if( c < 0 ) c = 0; @@ -684,9 +737,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) { const int len = STB_TEXTEDIT_STRINGLEN(str); - ++c; // always move at least one character + c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character while( c < len && !is_word_boundary( str, c ) ) - ++c; + c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); if( c > len ) c = len; @@ -725,7 +778,8 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS stb_textedit_clamp(str, state); stb_textedit_delete_selection(str,state); // try to insert the characters - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { + len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len); + if (len) { stb_text_makeundo_insert(state, state->cursor, len); state->cursor += len; state->has_preferred_x = 0; @@ -739,6 +793,7 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS #define STB_TEXTEDIT_KEYTYPE int #endif +// API key: process text input // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) { @@ -749,14 +804,15 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { stb_text_makeundo_replace(str, state, state->cursor, 1, 1); STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { + text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); + if (text_len) { state->cursor += text_len; state->has_preferred_x = 0; } - } - else { + } else { stb_textedit_delete_selection(str, state); // implicitly clamps - if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { + text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); + if (text_len) { stb_text_makeundo_insert(state, state->cursor, text_len); state->cursor += text_len; state->has_preferred_x = 0; @@ -771,6 +827,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat switch (key) { default: { #ifdef STB_TEXTEDIT_KEYTOTEXT + // This is not suitable for UTF-8 support. int c = STB_TEXTEDIT_KEYTOTEXT(key); if (c > 0) { IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; @@ -911,15 +968,16 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat // [DEAR IMGUI] // going down while being on the last line shouldn't bring us to that line end - if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) - break; + //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) + // break; // now find character position down a row state->cursor = start; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; - for (i=0; i < row.num_chars; ++i) { + for (i=0; i < row.num_chars; ) { float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); + int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) break; @@ -927,10 +985,13 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat x += dx; if (x > goal_x) break; - state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); + i += next - state->cursor; + state->cursor = next; } stb_textedit_clamp(str, state); + if (state->cursor == find.first_char + find.length) + str->LastMoveDirectionLR = ImGuiDir_Left; state->has_preferred_x = 1; state->preferred_x = goal_x; @@ -980,8 +1041,9 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat state->cursor = find.prev_first; STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); x = row.x0; - for (i=0; i < row.num_chars; ++i) { + for (i=0; i < row.num_chars; ) { float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); + int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) break; @@ -989,10 +1051,15 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat x += dx; if (x > goal_x) break; - state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); + i += next - state->cursor; + state->cursor = next; } stb_textedit_clamp(str, state); + if (state->cursor == find.first_char) + str->LastMoveDirectionLR = ImGuiDir_Right; + else if (state->cursor == find.prev_first) + str->LastMoveDirectionLR = ImGuiDir_Left; state->has_preferred_x = 1; state->preferred_x = goal_x; @@ -1002,10 +1069,15 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat // go to previous line // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; - while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) - --prev_scan; + while (prev_scan > 0) + { + int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan); + if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) + break; + prev_scan = prev; + } find.first_char = find.prev_first; - find.prev_first = prev_scan; + find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan); } break; } @@ -1079,10 +1151,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat case STB_TEXTEDIT_K_LINESTART: stb_textedit_clamp(str, state); stb_textedit_move_to_first(state); - if (state->single_line) - state->cursor = 0; - else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; + state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); state->has_preferred_x = 0; break; @@ -1090,13 +1159,9 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat case STB_TEXTEDIT_K_LINEEND2: #endif case STB_TEXTEDIT_K_LINEEND: { - int n = STB_TEXTEDIT_STRINGLEN(str); stb_textedit_clamp(str, state); - stb_textedit_move_to_first(state); - if (state->single_line) - state->cursor = n; - else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; + stb_textedit_move_to_last(str, state); + state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); state->has_preferred_x = 0; break; } @@ -1107,10 +1172,7 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: stb_textedit_clamp(str, state); stb_textedit_prep_selection_at_cursor(state); - if (state->single_line) - state->cursor = 0; - else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) - --state->cursor; + state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); state->select_end = state->cursor; state->has_preferred_x = 0; break; @@ -1119,13 +1181,9 @@ static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *stat case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: #endif case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { - int n = STB_TEXTEDIT_STRINGLEN(str); stb_textedit_clamp(str, state); stb_textedit_prep_selection_at_cursor(state); - if (state->single_line) - state->cursor = n; - else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) - ++state->cursor; + state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); state->select_end = state->cursor; state->has_preferred_x = 0; break; @@ -1300,7 +1358,7 @@ static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) // check type of recorded action: if (u.insert_length) { // easy case: was a deletion, so we need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); + u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); s->undo_char_point -= u.insert_length; } @@ -1351,7 +1409,7 @@ static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) if (r.insert_length) { // easy case: need to insert n characters - STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); + r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); s->redo_char_point += r.insert_length; } diff --git "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_truetype.h" "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_truetype.h" index 976f09cb..cf33289f 100644 --- "a/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_truetype.h" +++ "b/\346\231\272\347\273\230\346\225\231/additional/imgui/imstb_truetype.h" @@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex q2[0] = (float)x2; q2[1] = (float)y2; if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; + x0 = (int)verts[i-1].x; //-V1048 + y0 = (int)verts[i-1].y; //-V1048 x1 = (int)verts[i ].x; y1 = (int)verts[i ].y; if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { diff --git "a/\346\231\272\347\273\230\346\225\231/exe/DesktopDrawpadBlocker.exe" "b/\346\231\272\347\273\230\346\225\231/exe/DesktopDrawpadBlocker.exe" index 0537bb981cc92634777b7aa264f66de5b4df493f..72e0c4f5e3c7172f525054f890a70ac9fdeab01a 100644 GIT binary patch delta 235582 zcmdpfe_WKs7WcEu0*kKhqKl%UpstF7is5g>Kv}R7T|~jkUzOTMR$gP(TjHY27Te>N zHI`OZm{#7hd#SXr)KG~fBgL|!vZ5Q^J6))#$grq=zh|CjVNv_>zJI>>@tNm2bLO0x zGiPSb%$%7mJ~XI!&!DZ-w36oj=`)Al4fhdq<>eAyeSaZAZaz-H9Zrh%1}xAMkB!IZVizn6JF~8lhTxTtt5p>N_-%c z8p7FCn$bP?4i1;ffGHJ+OQqWT2lqB?4VU6Gk$!)ev>M-o(hh`4(LgW(kVcq+KX90g z6#ZF?Mn7@q5%43vlowq9MG501Y1x=H%dacEPLeh(K|UyN%MezvG1~q;V=f0;Qn(}m zPqMH)ZEVD9d?C|P2HU8O9fzz5l9>pD{?bssF>6+=y6px@354{EcI$Fa52A3j{N>`~wc^iSSDYp} z?ZtQ|~Hmo7zu%xgjf&#PMwgnj~yA_DvUWfciCxN&xfLIV= z-$3!D6n~21O%%T+5O0s8&!WI*tv%_a)hY2?Iss{fu(;#BSWG^7}F#_FQNbf_G_l@-4M|m%$_ip0-AfJbnt0?6I1Z~X^kr)e&(j>8egvQM> zov}(bNKV7Ahla}8?XD`lT%G*(5+1t%-(N01rlZU(kel=5?~M7C-b|g%7e=X&NRq93 zSEbIzhu_K~!_0a?jNfuA8y(iy+3L)c+;1AKMuVl8=Hxy3Uub(?cJ8n%NCjsiR^*&(&=3X1DTp{C>Z^FfL1i{@8fQnh+s6 z#K-?pe)Db-+C3p8!J488#|>!c_%Q;2;0n)5OCbhg|VK5UWyEel}105|R*VViXt%E(MA7q#BG}t_j zv;5Jyk|fX6d#q>WnFf!wQEeiwqB^P2SfI5hAy?k)O1>M4=y#lz!@b#ibL3Ngo4(rG zuJKy;x{3}Nf#7#k7_+Q?<~gvQBVTzkyWA@2f-AJ`od{P{FK)%KxC zJxH&zz`Rqh%z1pG%B))G74Tin7PZUqSle@Ax*=^l-4;$!oa>}R*j)t-%}R?K!AhK{n7XG-!(V`P{wlg1dymuaSe z54sjwRlS6|551IclqzDAOkcED>ZW?htn0>Az3&KBTmHcFK^#bW*q!fjnLaqd?9~6v8Wvu<`dEqg?5HnTezga;60p1@Mu_*fLYpHO~ZM zW0Y9461z137AhoX8Wix~17L%Kjrt*4*DGN8B-IZ7_;bG?v*SLBW#5>_Ywc&4CGtA$ zs$zC` zZX;!MG<&V}HZ`{@+mHMjR@846 z;0m9$vJ?Dd09@{~R&;{p0C=y@TCRX&NT7uZ9QpQH_Y#~RYSTy++U>hmXnM1>5xS9f zp%mYq?_!;hl0&ZEbiK{^vQ-boMVVT9j|_=N7-JqBa!&m|fpC?1s`lJ+w%0se`|@(e z%|oY=^xc&kyZP4|_>Vh6q^53v?@sTZScL@qYt8&qfJvFcNV8RgAv%xGoW-)DE;Enb z*3pttJ8?>D_D1FQH_VgJ#n!1EhRyG2mQUvMwcq>wY}x2x#&`@wE3$|(hN)@Y*_b~4 zqXI)bn#20-fqt1~_ZjDW{Q3~-+)-U&_>tA9-6qqyquL{-c&jMG!Hh;GT6!+dp@Q#Ik$)MkRI`3sx-pgV6)!Kh?MI zjMYK8xkN{yvde?jgl?Zd14RH!wMHiA@my;N`_vfC(xPK~)O=6fb(?2FJAe9nwkmp% z_OiKbOY~spw<|+YCc8B8R*n1-Fmi3YeNKp!fIi4Bop>Z=z|Z-4{C|L`Nb_v`keURs zxqe4m)6`sB4v-ey7%Is>T&t$b%kfxSzAp41^oB^4-dWPDzbpuW#7^z$!}lFk6x|};KJ;!t^MlSY)N7cy~X)jr*xocmP_@7 z9tZc|uJ_+=_TO&e?>1wkzullp%{#~J3ri!pHBG|ACBHJc5*;!>sKg6|GK|8Z&ZC9n2;hr|Fri3RZ9(FPu%|ZI|Kh z%f)0znV@m(jhOM%dUnpZkoz>XdNHErIN{lv(vWdx%fms zC-DOG3NEKPT(0qCSIEA#S~=Xm&Lp3<<#@9X@hirPJi$bj(RZ~|3fFpbII#5o)h2mC zMGnx;@F8Q_vjZ&!C;3ze1HzI|LQ*^xqAm!rSD;PFC;3=>!LBuWt%v-MT3D-xKp<)9@Gbv+^4ozWek?*EL2R{%#3vAytD6(3*}=uHXfl6^3FkB z&L@HSG8Y;VRA**LT)GmS&>S;wy8>b7?B_j9tvZ{|qC8$TUE~I)5zT&r^6<^QA`NcH-le08BMF@rU7i( zkbY)wCrc>kdbJ}T(l@*cmCRj}*mFbrTYg4|x|K{M_j)ug8{dv8uG*y{6B^D0u9OSBgA(_|zc>Ws*lkue>l)6vi9a(|E{e0<6@IOot$q_A!L=f;en?EYLj`>IS zi%+Yg0o~EdeXcM(kDyi(<8p{oK1_9n-l{;#=ZvL|8h`>U8Z}tEp^iC6jp}CFgrPJG zotcfjG-{;g4Eto%@PYTGLlSPvsEzu$26Qj7w+f%tp~XvCT*AmJS7AL0&HB@cq6G!9 zwUZU&b=Lv80Lu6`s0!K~OaiZ$U^YJYGSLyEuPydji}%0CSi%_X-$t_I3D?Z~F_8v0 zeMx0uwmlvhvzzhoW>@e<4`@iGCfU;nny5n0-KUX?*Xu(avG#=o&rso?0xs8ZUny+U z7se$;^g)#%v!xT*|Hcg%wseCAG)o0@=(r7^vdFKTrt_7AQZ8%C*{I86`fNlud12Jf za<=HQ;eEweocNU(i<>&d&hkf(2}=gAr^d6lFB>%T+i@Mb$A5(q1@$q{mvl6u&bEY3 zprKQn6xICC=-7-Jn(>9nHl24ynI~iFPQG#?vKB@;&X`=bQ;D6W^QLo8#+03^2Iu?2 zMK(urkxepD`m$wX`eprPg{{xl1d~Cmwmr&Vq z%~vZgR9ROdOCQxAI=geM(An3=j_SS>*%CUNHjV8c6|Z?;D6JD?dXUoAjk-u_Z&j4` zBq{}3_WMeEomjmHR@%$a>tJy1sC#r4a-zDob44#JH>v0p!-zl zf9_G0cCh^@!7EhwNx-4BuejLr!m3EX)9?G;g-7BQ3|#5dw&XP5rTknFVw~}TtEk3k*KHQcxS#vj&haC)FZN)*@xum&VUYE@ zsW9k4_w)SWNbg;RPtk)$Oc-gq8YIzCEExSe%19#v^`IQ4wdg_jd%Xm+@#=V08O3ny z6HoM`nr+V8YP>i#(nG7NcLF-$-n~*V2qNn#daG3XRBy6gtm|%^A)sL008z@S>$xA zeHq0qQn4Q)Y^R+-%N2Bwg07{!$57r^Q{*ZY|7OD9K#)QO{n@)oI>wS2P;Gk&MHH*3 zX@t6!Af*cGSO8EsozWdd$U7)%8zEaD<=%w7hd?_OY%Ks@ZyM?`2f5#KJ1d`LHf@|u zRc!Ej*8|RNXV{rZ!<+{{fd(m%$KREdtr@;;0;=95^eVYhX)G%};uE_uw0VP-Vz(Vs zU?GMk;oB#^sM-14q<%E9;d4L*=3r0RJ0Bo2ugA&{Ql8S~W1G&?zSSBt`?S*JY>f~) z>K`d|)V_}jvX{z+qW$JV(Ox4+xl*)L)G{<`p}mrThg7r=2<;R>suZ+p0K}j@)_#V- zbt>XN3GoYp)GLUOxtyYk@4^86vz!D+6f5Wv0sc=BaHop4o6sr+|Gf&{1AwSvdz&z; zRLnaFlM|#)LA(h7@SjKgml6LTDY98bzlzW!O8{w8(C;ZxYi%F_&3I7xErd43NuW49 zK(99-Dt{}1r>U5~Yy#$W1ev8E{tp1)zk3JxPb2=fP~>tIy_V2D1X-=1cMtG?kbqlM zw5JH|1;KxZg0~G2mH%PF+^=G8Aj}U5QmG)`3;_7gC;q9#|4WKItD~nU-y-x;1To`*e0B@)pFqGw6>X1`@a7O` zvVz9|P|JTcfo&@0orJiCAd3{l0sufam-x>h{zVj7sG`p%^sNLbR?uZsF7+kF=pF5k zQN%Vvbt12W3Ada;WeTnd0Fj4$`(Xm_Crlu=Z3JQ+K`Ir*{{nzK)WKMLawhWdWClH~ z^vI+~7CmOs;|hAvUiy<);bH%Th@8R0NpyD+-5&_lpwMkVC7`lywEs%LRy+vp4npgF zHy{!o;CmAQBA?Uk0|{)#gAj8FF_|E73St@nz#mEZoK2a?rpFvSoKMcB{G<}{cr|%% zO1_XFX-e{WN_MWduOQ$e6|K1lXvGAvD`+1BAhI*X?j>-Eig=t5_Y!2Qg7_i;$j+k} zshv;SC@VSim`6D|KzRGql=o1|_X$#=q5EC9G5hoxBdm$@N<`VxY6q}^tcPIROf=pBJuMF_Nj(`hQv_=eHb|*oW zD`@Wmpz`++xL8GejS!z9$QA|hStlUi&m7=CpZLE(v3phgEref1ko^k&Tc{e8f35vA z0c%vW6@=DEkTVL}d;kRhZ2NZv<|^VeLezNxX;lytRQ_h-pGTFDPmhK4xSAg1_VMHr zdMw4m9!Y8Sc#t%|#rR`SAcz?clwypMyT$ei1WZ)XY6xvEK_)9`uK*x&H{O1A62WaM z<`aZ@J3$sHnD+tzy6Xy&yG4|V#VM?B+O&vxWwvh_%oe2$i2vnAnmQE+tYl31zXC*A zLs2IDWK?7J^h8=rpT0w3riKGFNxmTc3nawq8GY%nv()CswB4omiE6M_>-a-bhGv7TCP*czj^G zL|bw+W$2Tx+LHw59Gh0K_CkfPj4O2~JZ8do?ZDHR)q~t;yt;I(BD)R1wRycF$%<2C zdy3alO6qX0mk{I!WALoD*6TK9*GtHrYwqDVK#V@?Suh7lf#$O|&=(fcDd7KIOxqH5 z_B*Ne3PpWZAV=N!N%YTRg~_p!YN?Jtwt}6{xXgJ1(-p|{)q4(SxT3cr>n1co-1wqB+3tl5_@guF~8>G)p^Zn)v_FSK7A` zbSoZE!Vduu1u0gTpmaLEXSqfS)DBh4e6eN?GhYaHxKC6|r0}_NrH)62!grC$Zp?D_ zAXEMl^|Jfzj(V|Ii{!n3uU`H_wG3ONRLf2($c~QU_2Cu+m2WSB%aro{f-X>$?@5%2 zD(b`yqN09MCJ2wlh>9vBD;@O|`bYIt3PmU<()&6%_T#sLzWsHA?pGKt4lvZI3=?nA zNM&SC2h(>dTGv$(h{2mgsp-7O+79~8 z-)W0f1ve3?WgQIS`Mt!zP0&>egQ5Tfi^^a&F(7lbgTWurzaK=L_R|;sAY}NVW;#`B)UKqmv*sxbI< zO`sKkuxJHK!GNs5zfyomCy~s43 z@1Q@G&msD=2|AwWJJAdR3>raLv;ty4*4kfTa5a&#budWf-~0^>?jYzQVo<|B0YGU5 zD*b&+K%cBJ(0Bf6Pv<658#)-I@q3BEZi1F742l8_EGmQ9#DL7LKQDl@&i*`++S9>c zCLco#DhXPyFc=tMPzA!G6<|0nBa7+J8PwU|CQ4Nu^fUR%)u8`hg4W6C{~)5nfGDj% zWv~Sd{)~1tTx{&^M22?|zk;s`km{$BN(ZTX++^&G*IZ1}d2!Dl0k2@YgomP%vw2*A zqz|M}d@(dPVd%3P2+P|DgMvR72Ez}2hdR%Jc6*Gd;mV81-#1vHS0p2(^%*{k4^t&b zj$X=s09M!ybygd#nmbJHbB+OO>aX5Jh7oBJE9j2CL7OZAjAwu$_EN;cHMUer72`Y( zj^?a%!frwz^xvr*MK{sSahr8>Ag1VvPh-t3)x3;f()8wk9%Fyr$<#! zQP|_$eGHSKJN1KlZS2fG+!-kU>`~ig>|@)2WlK{@N7k(;0odU8GLJt{0|ssGcN{mt zsW;{Py58_BmaEaojMT)cb>UKzA>pWel5F>f21BV3qcp#K5HNuT{9gEMPyvlZ;nOny zjUFBH=RX|Agy{rNJSu`=2*gIOaG9_uKF}@s{MQrgyPTogS^Zg`dDFE!PO!Xr$y3Hx zBWsJ><)YT6*U-MxW*DX1JpR}?;l)K?a@)Lo{(Qd>Ddk;f_y%1{_3fH0(0YR%n>W-M z4F`A7|4?p`du%~J?^)d9S=h4s%s&SHfT5$As@I(A_yt|CVqxrP{dp zC1{>2bt{sDS8TvD- zka-oQc9a~Mw>TFgOpzMg}J>~c47hYgm~^W%SFv0 ze$Q1VxI@Dick`$aDdb$9>}dBpTD*oyIeaxBvK1+!liysr(qqV!k1x5F4w>*i=m3;m zbU2xIlf$qK5@F*nEMZ^gMmzQL0usk}@)DD0aa+nq#=Eo7y7T3Y=9Hg}B~$TkbbXE- z$|r;{65dKUo^sTYs)5sbE{{g*<AZyozHQr6Ggz~4CP~p>x zX#baRETtw$g}Ys#!jtp)R~Y*hesBUsecL=s&3rldqI*iNbXA!JYip~kEqv{0ufa;X zCx?@#UTQz#F4wD@&^}LWQ2v5cZ{jsjy<;zRs2-$8hcR+l`=b8NaCxC=ZK#aJqjsec zu5+N)*7f%+HTklHHk0qT&lnlLR4sX){DbRcxKjSL;YWh|pBCec7H=k6XPejBX3OWC z%r%2(ES_+vZab*jJnPJS=`hN%S@t=X1jurO$f4?}g8*41S5l2HUFiYSMQ&DlG%_8$ zQk#(hold@7q({3!^`?Aayn75ZJ{N5%7p8KA)i{b#D#ofUaFCobp8nG#O+> zY)1z~u|tK%x%|d;8N~5&^guH-)cbTJ^f_kmh2!Sm`;Lo|f6mG0C&L4s5!p8#Ry%u>wJKaDy|ACceAX7|uS5&zx-cxBFJ5AqpU)qGk^)Qg4q=`m zu0*SDZREHHx3+QZ@+qFv7iq0@ZZ55&d=TXh-j^a@vHnu9Z# zB<5G27n2|M^|R4z*-c^23*7`SIK%LroLT1GSVm&bNGcO@4wnOR-szoF&R3ub07=CV zE`<0}eF(<|d_|~V80-r{?2Tgv`JtFY*q_|qd(W!?pI_i5>E6#7x5oy&BfYB~5#_`hD zJak!}qBdp7Gv0ld=%m8ejqr{ifNlgGj57G)Jo8JvL?ZwtXs6!w< znvU~(t)3;Oe8`1=Xb6eNEGY&h#AN^t;yBSFK4%}vBbH; zG;)|DJh{@}bIIc4slo!acxMg8#3Lbx7k|wTuNe?~!`EWu7AX9a9bJmwBQ1oT4)4vj zIYtcheyiTk#h$HHGfAknXiY_nJxf zxX`_!m_tEzXd#5ZLM_qSEOf6`(LJZ4d+$MrvbB+FE_nJ$x_2_7lfd;I1cdH2c0uOL zw|5bKRjO(sp>xZDU`<$$F}S|?47uHeqL2;UAUR)N#qawVz zN$9dX>63{#%sUgt)^vksvYZ}6k3^>@>D+OxV=9)4&K-}Ib0*6tj2WjAPP>XQJIpiz zCo>My@{@9UlE+loyGvRhvVb|jTN=;R)6@>nfIbFazI6C-V~Rq3v;pTtc4}}y^}#OE z*^3iw==vus)}vxxBz4+!u4b$|)D9)ncCZSv)k5NoU+v*OUjz(l#oh?HqiD4D{XIB z4zK$!7}vmH#wa@Y#r zc$XFla=7Ea<2z}agJ_Epb0NqFP56u|QJk5ac*ij9-=1WP@0dKV;Xj=eVmm3ojI;47 z#8Lk6!7qQHOAc?Ff*dAX(f-vlEq*_RuYVgb2-pXAOicHr+dS#>OVby6(ihQ10<&qd z5_G))suF#NcTOCC;|Da=Il3iZ9m0!J!}MfF?i|=*`QgMRE^oLUMjN-c6sKWfIZArR zDkZ<;SjzY*Sb_ts?Pbu{5YpFB`QW|KY-vhKk*ekqcC<#d52|C{nv?Br76Y=!37OVB)f8Z<8l$!3-0 zTR3LdK{8P#IgTjC1}N&J!W8OCDt{_yakUxN^_LwS8yy^^zOHYO^%2uS!QF&ynpZb{ zrSVp4Jsx$&Yu}NJ8t^vP^n>y0s3h!_{9rTmuaGq58zMe<&;Og`qdX^a_lu5UDs zuhCZ}SEh)1Uy7rTWaMx80&1+B3f3!TVnCI0<+JD@P}{WcxIn&H=UHXZ(Q(RnwT;uv zA|I>&q*&F~&rnbU>DD=1#8iR35FAYrxia4SDeBJSs86meK0+5WLF@+sDXyTb;scW> z$vbT=NNA4!H&nj+48YZQi%4jdGtTwf07pc%ps8t^g3YT?*KZm}Qg!G-aM3i4fK}=k z;(w(P$*|XjEMRqA*#VC~+CUCj8&aTa>J><Hw$!*omEZcuJ>J;?6>~ zBg3+_KGRjX%ww%jQEGV02xuXc>mKW8xcFuXpZ0k`sgAQzj!X?P1U$aCFRwx~FFObf$&;es1A4t0a=b?rkmNj6|H7Iz)l=qeAs;|caj@q}AH zq$JidXt=T^GgX|u)5+EyK5H4+k}hGVW@rGs)o0z&2{r`4IIT&Glq&X*Nz{RXNU_ga z+6n$Ni4|{(9#o~odPUZi%~QhRVK@~Ubg{-n7*;1dD5n0R zRELyOt$7@!vak`G&Dw__W2u`*YVUoF**EXj>K|i0T#4F`cCr*#lD7VF@%8NE>`~Wf zTzK%ND?TpsVU1*bB^!&YW_KlKd}x`oPOOxVQlIfQPyU!imh=Vc$dakrGdtLdlF@z3 zKN1xNutgp+iZJ?kFiT!@*i(ic^EN-Xnf8g4atw^(X0 zbE2N<2qwakC&Ln#=fgt?c~cRPQe)6luUxs(7+XR>$#uxmh$P|O5=$K=HD;Aq&fvuc zwf{h_!F6RNwLnWbxn`EU#nOP7kaO7Za1Os9p=ZB{q~t^gdrKa}>ZT{<>V%`n6@h50 z_A_Lm$t#x#Tx?ZcL`7lILFJv59`mfG$b=ef?}JP|$nZx&!|04x`BWN5W?0g2v`V1~ zJ1xUv^C=%xKD>(Z;dLbhMHy*|FQ8ph$YnlZ*!SPPjxh`GP?HSSaMx%jdVEcmTxca=Z zYNTjfT89E@>|5dN0k@VW+_?|aulxH85X+@ z(U^@(e5owNA(GGBg!c@~vef0sSz*dS3iuf0!)OAOR`w8}OawU3r>N#16uW0!jdIZJV>7EUbBd_cL^?;-*qjc%^+HdpF; ze1#t&9&01z2^7jF(U5n7YLh|a$+Ja#$?wDCIi;@fN@Hv>BGFk+$G*YeL|;YLZ$950 z_$cfv&$gK1#DFa9r0J(ucV$Y8<7AW3z73VHl(Yxx-@b=F^{Zg;gzE6r@SolSw49*L zHxrcOso|dkDo<#{*joTt9AWxGEeMc}KGmV|1n|FKpxK>>H~;%~P6TRifon_Bsw^jx z)ZksiA0^_^Uc}8_M#LpDpnRb^VXf&wNUs142!=!Le4!@1)qqQN=q#kr@T-Z!Y@$#j zC=?Ua7g~Zql88d70@#8;p|EwCGeDw>;&@*oVJ8y%@Le6cS3orc-Ad5->j>IFP+w>x z0$eU?t`4;Ttl=M>0NQ>+GYB-jfL0OoEJ0TZXc|F%p&1B}*ooA3f%h2jNPuNZ{2mcc zx!y|vA;5ijg8 z6&Iy45_B6u?FvCWi68>}-BlgBoB%i?0<plJk! z!XZ!+mk6|Rgf`hpc&h}SU4RD=co{*tfR+)|7rF<5lDbi#wfcZ&C$w6DRwJOF5_Bs; zXRV-oOL!u&4uO(5g8=*$Lfc7b@Dd@scmdu;;3@*|70_h_g$^Q6QtuIHcMw_wp=}Xp zr2@K~p!)j()vqLyl>~(rAy5+Q0M_tSLW?7`W|7#*Md}d*wh`DaQpXW8>KcKPdbvm~ z5!xa`n=R003F!aeglFGI&>8_PCMfC}fs(jNpdBZ)J%qMTpzS57lRr=3vjmR6QPeeo zQP&8R)E0m>{4PRkCNzUU(+lWof?6H`v{XRT2#UH!pd>C4XjcFY{S3X^#}oD{ymE3L zbfUu{aK(7&7OENVl+YNPhH+grL4CMt5QAyuvef;E-q>PaMI<=ht3w+pG5_Bi$VMqa zYX!7MK+h2L3_)kv30J}snbRRakJJ~vkRAQMhCfeuErgdS@Zt%NZzpIP19XdkE+Z(k z0Rd9SqBj?4s|l^1(3%BWrGi!^(5Ua>0__Syi`xb?`%RRWI6Ohk;zX!)kIMNv{O9)ycpv4454I@xWQYFy7tOD9rLfa?M_6q1Jf*vAhe1WK7f}(;Ezzdmr zrdIJv9ozy8@#vQe_0 z3+Z!~un8ox2~f!h6f$PKDP%GziXWo{t0+MtzM+5d0=$pF#RT4qZwx>LMRg-k=oI2j zp|gjg_??uXloD*gH`GBuR}ge3LE~Xnpc^14x&Z`aj$kmsn?lAyW`~oHrxbfAg+Zjy z3u|N$L2C$FDxhfug_IE}*_n?wg-!`Y@vq@VYpH4#t+&WL4Bcn z5GZ7v`|+yKSx$x$f0|OXQHt3j#Vi5cN>IZ?fYu0TF+rh#2oyTo@J6k)I<$(S_)1futV0zU&+8>~7d~40uZnE4#msGg?z<0Covs{qVlu zU%bK6TPpmUeNiVBE?=BFRXNP3ro_dKuOKZQD!AeW4S()AF=H(wn)fM1qY`okoa9Ql zgKZJ(1j)7h11FXVXz#>?y*aGW<35>6?#1pB4nDlXb=WPw>`lHnh+KLQIV-VDjiH{z z(Mz0=^ZI=w_&nBNf_0#UwvwRapW9 z3o;-Ckbxzg;#9(x3Y$=)ty)Y=a82zzP%KW}L~x@!l=!Ef!8C)rA0kN@-QSZW?e`1$ z82Go32(iq17L&!HjRK=&H!u`gfWkxgC{CqwmRQ*J;6o8k{!Jtt$DMKyI!9VT$46yn z6a`Qwd4h7R^qA|nNJng*eWy^YsQ-}OkX~gfspJ5@NRH);tYbDlzCXzjvT?+zXH=C~ z4Lrk%JV_eMIJA%>j_E=iBm*>!|SL4_oi)=v-S#isL6X<%Gun9qKDUaBGAQZ+f?76z{P40 zZV(HE%H6N~dPg`7!p$5$$V<#{Fiv^vc8fP%_4__`X9joKIb!NPM;maRcomDg zVT zfwwfs>7yQ3X$7hUO>S~uoMZoBds^swr~Lkj@A(c(mcARsgLQU(no&LX^$OZ?kCdv79SE_Psqoppj@0N zkn8hux%Ojr_K63y*BpVb;Uvqj8@tG50sKXjZ6E9{Dh`Iy3+%13tF$j377m8U92^XX zr5_e6C|!J!R>M`KR!(marPVi}Q2Tbp3>1Z)(H1ClWUD+Q;7bRQeYtCx7J=c(d$lv_ z+0G}w(a!sTZP|T7mxl6@2cmY)t8BxbLA@`-&B>}yAB~>aZhVzJzb8KAhl9e0Z`L3C z%E5=P!MjGunF%=yZcUMAFk+ z@w9`gmtPN-RuJ~*4ux&rHEjyZHX$Fyp%6L|*?Ix1Ze3WB>q|w4Ltzh7#GkVY@?GrO zNnGT+c*m;XQo(m|cqc3JT}0KxcX4XXE28QfgS;=$vX!BoSrhWbUro1595*(!$7`%pFaKqQ<@x!NB`o6oO+i)zL z$1Up8l58R9VLsdUfw8WWBxQ)b@qDtjs(}6c{Org-IK%c6&e#Szn$#+IXUYRBu>u!- zhV2Ycrii$%XV?gv&afGlVMhz`bcRiP_SKT{99#k#x-+QPG^@SlJ^$Jleh#y^iiU$MwLB?&t{qmwEw`}3 z@)<+^xS@W>BW_{!<#T!^b<)jIsZM?&+c^o{6^4z#@AfEz!10QaucfK35#&gr#r=#F zUZaM+epJ36K^WN6R<@}cD?h=g(X9U@#=_5fg#obxmZ0#msHlMug8VIL%Zlk`?|hauzc@*Anps|&Vi=3A)811GFXqDzvzuNT zH5^8k4>mdml1v?p9NYx|+CPM@sXqolKYufBA3OL`Qq*IQbx0e#p%q9)8ui;tmknR_ zHYG85-8DaBE<#--?J(sDs2=w*0?JEcSl<5O+N)Qwjr*@Ky9Z+Uc9;GtE>mfs_}ydK z$NS^852vxv0~w~=TG2LevmgAgxqAS+`oK`_)i<+^2d@SB7dmH?cXdT#;~~Kang`NVZV?yV%g^ zn%i(9dG<|q?3EGyUVp;4f!!uMHey#AQPSw)qcKA%WMMgeRX8k9MK4 z*9bxc0U;9~`XcN0TH*wf0lp)Va#e83S(LJVgh*K>QW6B1z4);g*vqdavU?AOkLitM zCBey-QL=SPvPzMRAV_BB*S)~5Ka_~8g6}&NlhOw$XiG?nxIUmVEttxBqVmIVVvgv( z_GwsrLz5)vC*^G1;VJCckx`Kqn3-T->z3W35y)N}`}N2$cH;1GZS-#T{o%>lPoHF0 zzCMmEJ`&z-&yyVT>Ia-eYb6v%)6g z?o!id5c0j1!xK(p;0E$tfe1Sa;B%)TTG2Z~B(L9UcM^O0^>N|j6hvk}oY-rIlJ=)E zmFS+I+0%zDnyoCoa#;5#1JJ~u*=DPZSOKR?&!D( zixTnS6YS=r;|Bf@wmHzActWLIi(IKUh*q3urz;0Wlq(o3o?uTQwOjd0c!GU)bcALl z3qLkCVweJZ>v4AZu@TxZ4YMB`6Y&O$0>U>x&Yn0nLi0599UBvN69C4?*)ITGx&4J2 zjd^?8IF0VANso$AlYTgWTX9y2r{NJa4QYWRY|!WD_!I-vrEb@zS>mCKW{FRb86`}~ z$vgHdu{6E7V3t_*f|wK57K@s8of)jS(R4V|$!iyu3~K0I8c zaJRkBN}h9A&dBJ6-?hi**tWfc20hP47Q8mOcrf5L!q9#u)tT2LC$vAE=z}K(uT<3r;?3 z10}ah+XcggHog*8C#)*esw5}Dg+M1yY|{2fK4)J96mNDT&-{j!zA=3ca)gmM!sdVX zf|2=tC|}o%g)13TZxCyWk_T2%d901Z8uww~lbNe0nulMMa_1v#N%gGwj$lL6;}2(Tv8$D$q}ky3$KP)jr9le{Dh&!# zAqvyXUv6g6zIo=b?HA^G!^3Q&FU|boLl;KGKP<9TdFd>bQ3`Rl^|x+Q3|F>6KQM}SDm3gTT3sXiY0Qblta%lb z9|J#uUV^SdTZXZdOoFKj-0LL(H>7n;|3wA4Qul#<_^!Qp;*z=@DXih`s2RBE5`DBt zaMk4^fl#=^7Lah`?r!=)DA(b5d>Or7GlB{WN$V%vNk?C=uHV7hPTX+A2>{ijBd#{B zakCBPULcv95DT=h1L(zG2HE7>aBs*We9^rvQG3wY6*b~&kviNgQilSOF(6DH8*iiA z39{jSZ(z$#4ITcOGI*GT;Pv+iOjwM-Pd-QMn6&J*awY3qJ4iE`O{^WG8P8VK4$+pq z#5}b_uNe=#Yd!dtiyF^5ooBAzlW*`JAfW$18nHZs)?N^46?_)-Ht#&PC2`QR(DrCO4SeFFj37fqCKlsM-`0QvG$PWchs&Z zH@|6@D13LXo5bKcybw=lQ#k_vf%){2C1Py+J`54aZ#QPIOB|65;uxxI50I$!vey7i z%EF%D97Ke$Ti)uIx%U!mU%{z55W_s<&)9x~N(r!aZc;|($M09!PE*;E`*gy0uHT^D zR|Ca$m{i8-b(8RfcGHV}-z72~w3Lo*FGMazCm6_{C#1#z>!G$!W z=8CeTIgq{=#;6PCEcQSYJ6o4HFhfap-Bva8CL~b}iS~9fBzksJFh(dCZ1Jh#5wti8 zLH}?ci#$CpVyE(T@IH3c=^;I-{gFLN4y)GAXYQ8l-105)RDT# z;9VCjhx>v~M2F)JsJ@gB(uAhb~;$9?HR8vOU^C zRty1Zv@l$hXe@6}X~)kloqz_5PKx{oBG&%!;T_+2;3?d-cu07&74s`XK;EI7D#cS>}5`Y1qxBfy$<1A|s}C ztWwD7$||A04F+7y2eG}rzF-mb`FmLQ2mSu97C*ZhiNTAX;6aO@!btps#n1WEgO>@h z_$l4<|GfAq>ihQ>KTklikgK}Xh{eyfp5VpL$R9hE0l(U)+Na7LP}?U3t)EfF$^(T; z1l<8u2$U&84=$w!&3qr?WyFj1v&B=z`dPn#8g#t@>U&(l6TE(=r~v0fKtimaWo~mT zW(Z$^)-0N;(oBOkgD&~&o19x!b?M3bf5^-q&4?Ub*3q(+AKzj(eq_`9$SOXXIiC)C zSHY!mmTXl|7dp)OHv2X-TWktPV>K$x#y5OOLsN#uf>V>xIL>3jkGkScjW=*V0`?qB z&{w&tlH}u9T*FRRR0*5=abM@+PNvGwXelSkTY`Tc&XZk*O&Cn;ajFQv#%49+3=Mv! zuF|uh0roJ>081C(>f3gCmU+|XQW8$xou{Xxr|Xp8yQYVysP?P7fh4bvk=IAN&u!@D z)Vckz(SK@+&X#@Ms~tVypd0F4t!Y}Ge6A_<2<#yu5eE{v-{WV9V~p`fjq^k6Udn_! z&Dc;I!nISq)><1@_`Wwr8Z7)YU36@%WmC=$z5$0ILRwtam!*8a&a95NxY3IS+-hBt z)YPLuO86e8R30zJ2Iwc&7rKQWPNsq{br2*|D<}34(O9xOVlpM%i$F7@*=`A+sVIO}sAgY>v+h?|AgR3eo(8~HbS@(uG zpd~hVwY%RHX#au3_yuTFHmYcb&)s9+dPix-i6~E?8RzG6GbP(%*@DJF&6w_5LNi`o z#40}@00w7~@|V+>OqpT0RLX4|*w%mdiF_)!1DHkArdR%bz4mrJ8}P*%Kz~T6C~dUx8(7 z#hhXL8b?LXHOaXqY*Tv{wBQmh95$1)%(&95j(yWOJrP&&IN;-!h5HoOt>rTOM0DfvZ9X~+Y z9FpCJyI*!Fk!p@dh$Fn#VO<7^Z-QlSR<5=A$A3oQwm>6m`Ol4Ex#(NvYIgdg*#2Vq zhz3mT-ztY?@MF)|sakUfEB)`1$O*eT#tv8WqQHud)04elcEq9cma}oUU5v-N13%vB zDca+4?8GWbBlVfd?iF~T$r@y^t~x({zD z)$95cPm4`*nC_V!t0_LezA&`-{6dFTuE%A77SyiCy0-#52JmRIHuD#qbkJM6M=G`A zN06IwEs+)|GEVkGq!n^n63k6ccDby9dF8e4Q!Q5d;a6e4!jMM8)ydiA9%~s+2uBhk zeqXfnN{s?3kA78j-x^exwG6N78W4V-(TJaCJHrfLb$9l(p-#&D@5uPs()0^h!p_s; zYDG*@@TO6i$t{=UC5Pd6PIGV*q|Ho*D9&fDlYvlNWRVIpQ`)?aL&l7vz4Qa8#w^F7d%~tU3&)mJZcH#9=_&hg?_bUB zael`hT#c1|ra+U=yZ3^-%|={CnyE3)Z6S2WX}PEZCq*G@m(wE2>*84D7X!D?N!RvX zyiXbQ(Q)kOA7e_Ge2(&xD+|M3{k_VZd62bdfq{@b*;pmli4LV{1)(C-xfm+XAQES0 zK~A7?KWOE#=Kn=I*U&7sDhcECkUzjbB(S2vH%&)qa`s-2wF2knRiPxjl@oFtd5{kS&x*qoV;95HjXyqt~D3~SaHb;w@}ExX6~j0@w2Y~Sj%2+ znzp?>Q=`+~xEB4-fGPWLSGu0K;I3yq9D6R9N&SJ9xy}+~Wi|*avkuSTUE29TnUYFz zU9C)l(0bSw#LI{mR%Yr5Secgb>(LP_vyHB1<~zf?nwi9lwrRDXA43OnAliM3kD7cx(d7GeY4UQW(wg?G4Y<6c0e`kMxB+81WIyc2 zOSUTf0-iU!o}ao|B%n?RpRS^2qij8d7F>^}TY)uYTMWhmYR8ArjxS`ZG~z?pt?X#T z`{fq=p0Uc&*|pQ4hFsa%kiCvdSJ4@2jFqA>W}z|8B(55@c~=N<($ZACZiBH-KrOS`xc#+C?V$LZXyKtMiCSPYT3|i3zspCj{=*06_FxsAta<>9Y8|Hi&&@2SHQu0}`o&L5zZS#XtyhjK$7O`Rndqa? z{6ln~TP05{ei<67Mtiq5{t2QXXDsrFg?&E#$OAt{IY%6!H65JKtylL7lt@zmj30ii zCyEOAyl{E~Hhy`xt`q$40Jzd;t?2~6rGPOS_^g!#=DpzuTR}MtcrdV6*L8C$r+a5% zRUy#tkf%X2T}OmFOukdDHEpGPntbZDrp=zB7F-Uq&Q;W6^y+$(iWXy>4U3+Bh<#$WWcCF7ymVdcow(Q#fP}b2$uzwJF)m6E^KMd=X+=&!rGot z4~M&u8oFa9Jcr*0psUIfWKk)L(#PmNqXv}5tLu*+`^H5)7hY;KNd7>eifbU-0#`!e zV$`WYcS2nWJL>B@F)%9^?9#QUbh{DEBU}&3#a%Peu>I&hBAa?Y6#w;Zk!QMPi+_iA z7*w#_#qChJR&l%0ft#r$sP5n!W^gyk3rb4pZX17!QW8#d(8V&q$&Tu>w)XsMtl@M& zTyTnep>TT`T@8h6F@IS%7I*XD9w^)-soVpVpJSt23R47Cy19z(W$uugUfM)<3T~l! zmK1<4&Fp#u)WvsZ0=o&q>Mw-F#^gmLK6(eqsU79U9m#FPj_x~p<1Q)(tR-A97EYH$ zt{o+Z&1B!?56s?Jphg9YK_5KcJ@G156BEoGOHQPN%F4v(CxcAI7&7>OI(L~i_unkx zrwskCPONHu=uczoej3|5ZwQvP3P*@_3RsYh{Ha&j!Jo!!pIXVj{V8V2cr-@7^B}5h zt%+|Fp_y+%2pM%b&mEn|zq2cgtjaZbfd5nfv>kxp!ev-~0alWtY2m?wNCE&YYP!bLN~g ze|NSdqc8}(j`2nvPKt%y9EKA}w0i-rjWVw~lecIQ)>Y`|oXJHMrY%j-k2{lL@=ID~ z4asC@@|p_MIyJcig)bPUri)XhX@v;$Zhs6)eA7JU7j#S=iarW}HQQABGnxJ*2NX)T z=OA2F_-d06%!bWg6!gzrkN$wZP+)S%vwUK|Ha>^rEp&1Qf~G}6Vyla-UpmaL?_`6n zxPoG(NwP=e*Y*KyB!;#6bh}soROKt|3Lu`815rnn$I)wO@$HNZ?4|8|9U~hRQn*@> z<(@vH#jk2m2P6!AB4`@*8&7lyH!S=_=(z@rA6QgGn8hdqw`P5RTe2UDhE_#5RR{VK zmTr+3M%vOY+GbRz*uAjYRP0{vZYOD`wIy9%GB-*dls*xsErJKZN8qDb`%n_hEOY>OAOvA=47^xMf4))-UBce= zm{Q=xk}mfJTnEPzgA)3II@q-ncR$4!chF(xF|tbBazV95+y#9gJ)eKM2TN%)qpAI{ ztuY0|?aMOj9=RGz9j&#Dov|&bV59Xs!ct)61y2Tl3SXgyr6|$8{2hyyjCyWltf%yo z8;`|(W=0lmj2BTGV(;US64(ivANdTPB_%S0_KJ&?umyBKq;$Z&whLK~oqz zDZs*}ix4)3k;s3TrdBoW^L}v9n5Kp*@tq+Gb_^MvZ}Fm-!Gq(oY090JZm@I%N?oh$$BXBLxG5j2HXO(m(_=)XsGoC?cEAoH>mmd5Um}l!2Px6JVWG^$N4}ON348IZV)^mLHCu%`JtvzJD&DH z)VLkKXj^*`ZeLKG4ljDSNq z)S&s=!P{-*^xM|5Fl>?}P;mC8^TmAUIb8^Fw%Eo7_t8z7pP_iTem3;~^jRNFzJhO( zaezmxHh9B5Z&izCbKj+%n!Ox?eUpItPK%H(KIct{C1d=ODH7>IC^)78+ssIn%PGv+ zfhoMUCF?kfcFMGn$Q7pRRglyWLa*G^z*3>x1>0A+H(}bms0q_$K&qIgOyT1_S@@6( zA<)7=AFMpGMCgvtb--vqVNcMfsAy0g5FIgQ^eM_fAQNS}{662|$s+YWC^~P~iX}+P z{P~<#tZ!mUh8Vc8(=%&k36zcq?I>a|#Wbelxde=Z_cWA+G^SGr#`h6hl#qViicOTJ z1@T^9tbb^?Ac&tApi|BNcG(;`32Mm5%Ggg=Buug=U59U>x0$a3nAAI+@AG2aB=>aw zy_XowZ+Wq(j&lN$8*?F!P_hIyFV&lAe1tdaB`r?li@aIe07JHz?bskHbg5dxv1$A@ zZ`KJ@!vSv=EB(}l-=HigkkXddXP2c#tU z_bSdmN>RRP%a{AGUOmE)Cc>O*0)O6C$_|15qGlebO7>4|r)k~O{MyJ z_*U+~YfFUlHS`I`(e#?6@2e)GOcka>l&nm3!=lH%9>u~m@8($QRgoA+FDep8(G6x= zO1_8Mn)=O$;4ES@7sEKB4+Mn0%5jN|BPjJQedSC_BZ$8NA+kYK-Ib2z0lt=ishd=m zUUEXvuu6P6d!kzS>@A|o6$wEEaF||+kc0`jh&%q*s^2LgAzc~vJHP77+8XFFRXO?; z_iDptNC#5*<82tilJQsDupG%NWq(jxHdB(yH}PfuEUfLn)EKNb4}7atI<|m97VzEv ztcUNP4UJ*mp=F) zjJ9w}V7{bKI;Fm>QePg@@!0mPXCF2jgD`j{aXh`oz@GO3?y^O1PdiVVZEW_QmyNcx zO>SwIbe1X4E6^s(v!P) z7}cz$XQQRn!M@JbT7e?hZ%0^E5_rdX$%~=A++;Hq!B1cC!+PGjJ)2}H$0=tZC0ISn zpmain*5-};8)`13aP3;q+^>?B5AwryOP}~2P2hsjXWYT#H9Pl~v z%V~=0r)zjR#2#pBb>gcq~{0SeEi1mW06;Ua8puq=%P zotg3g`m;Em?KFh;))1-+*j)=%@;@8qKmnHyMh*$xkU8^wi97*NcCIc9AMTY~FalYf zhrpH9xaVH#oO`0Q<_DBu7`a|7P0q6DvakcGlugw>dN%?|{!&8_AeWUX?)O3Km3zsU zd!o9=rlN(v%?r8siK5}SJLORgvpwAp^;b2a2$c)vP#xi#j>Lj^H}MS*ik4qj>jqF2 zKzt7Xs7bseXo4vALYG>H^VO(4UZOmSRLvozLUJ#wX_I@&Blkpo0VD+B%^q+nGlT?! zeK)nU4Nlc8AEDC~nhoK8Ew;K&|1m;c$nBlkEUA7B|GhKo+NDQ4ZOA;M%kCv7jFKnB ziGvwegy0;;Ek!gFjS1urgs{Q0H$rDf3Yi9D_HVVV?WT+J=@iALCg!eOqRU>6DQIa{ zH?+JqcW0KoPWJA{wG9M}DaVzP6dJOSTboayn>kL17Dn;YA*_$oDuFvfSl3o1;Bz$m z8Rd=byiX{Lk*Z(kkAKTB7?Oq zTBSm}F04aWGng#bvQU@pKYuOkvN1R*pCHMaQ)$e;Qri+qDau|xwF`@DHF7w884ccZ z8{g7}g#`P+_{tHUKP4(26%ttk$Os|BOJ3^3tGlr7{ubDOYOi(#;8n7C6&%L8vypsM z7>f_x2ERNgliFf@EiheEGFJmCr>^E#ERYG>;=|t#W8-*bH|EXngt2ydfuTbd0-a}f_97hs-Iy{4A!glfVZ8fJK2u=&!TO6E$;VSHu})>~RKjKA1}wVxgiR3tL&8ze?&r)<1`rReHT zu3BoBbje-gb$xoq>tyZzX}5S?1%7{z`}5u7bps;ebr*0wi_X;raD966zk0AoTkegH zCyvRC-j>yqv37gF9&!SzXjn~_h7=(^i-Eyjn)+V{x^`?-X2L0q!>V+81dlfYb55y_r9c>&@Kh&%(l7 zLp#gvrcQ;WMf86YF|@5ew56Oe%D&JMdo+9QYC~K>(&x6s(Yd#JnwJzLowbj~j>9=! z?8(IstWNqI34jV$q2$WkoX>j#*6fiR(kx~8t`U)zZ13Y z$Rp79$$q9^sZh>Y{xiTixgN{C)yMp8zu#2+6L8bQS5j^|yJfYZ-*1g2^URLIm%QbZ z*@oCM^TmF@)dq_)mSy|)`z<%d)9j5xwxrM1x~+cVTqEaVnTRoEGDK#v6>2fZ$cf`I zIQE5D8>D@3wITPGXLje@Ti)62;~>*s$;~<2CtIKQo4FOT0f7!&&xrp`SPI>yE&{(DDoI zs}eDiJEB?lP+thIHy;qfa51Zy22G%I48|HU6ncF=voEXcnf!L+T(qigJIdO~F?X5J zZsIBZSlEy}8jseI-nQJd#$(mIHFQvEmZ8G5mdK%P>S!UT75{!{3Q}1`yni_ z)78GBNH(1!abWr6gexN_|vP8l# z0jQj7?LDx20-AhUmEizfNm&H{@dDG4nujR+Eq7!7i}*n9%531yYaH#j5Ow#GY`<#W zE}|?>F;?j+HsUHwCG=!BZ2_@7wQo`R^`2eS*iKXb+KyRqS%+y$O;3Myhtpo zDhIHr{susRHEd`a=raIBbXT5y1}4j>s7@DWv|dLMb5}0aWhZM??*0H9((i>AX=!iF zk3nfk)`kfyzU=#_O7R<@r#0tPYh!*L&1q`u6h6RWB0iG^JeDwOs@usMWp0>U$Lh;! zq`?ymOG(?Syzn?*FqDOIzkw`3`l>gN8pygmR%G)kfyG*aj}lx5Nio64VfLs<@FE(> zr>T)XgO!Lqe=b#5X^E%`lm;6Koh;Jewv^r4+Tfkt5(1Jj9|{TG`wTzLA#3yKzzzXK zl{n3h3}pUKptrd41P-mB7CZ-=lB`hC%S9t(Mr@!d3A>=8G0@kyDBlkevzgGdfSz#G zhE*L9zXAb6h{|@%aWG*4189rZEwpI0z%hp!qieL*7mEif0` zlk$|uop}em4^E(>sdD<$^i zRYO=;DWvEAn?sm8liv89J7OW09I^1>Ls_KMWghjmLDJI^eCtrwpY7#e4`p50W_}wt zY2|M`WEdMOjo-i*3}fxPEg7J4H^hH3Q8QrRG2kaLcq9~TV!_p=4c|44#aiC)A)2^$ zn7lIFo=-Ul>@qYG##ULo>CHgu5YFGcz{NH5RF=AQ$Eh`>#mvQP(C zI7+z>PrTpF$rGt|`xF9yri*SI#dQ!5ABXF72P3u#Wn&91Z7PN^wd^Tc%2Ghdm8g`v zvgYw-B4yMww14fV+txTgTI)us_Fnr>J_v=hS2EfKrg+NIClKCvH(cvG#KL&$@)b3m`uP1r?%kfC{&qYI85I=`*Fu_!?fFCmP0%1U z@56~iTbLT3SLu_Y#D&uMM>tUh?4+t>^X$ewN1!>+@?Im@@CncNLG!W90t{Oqj!p%Z z6vuub&%X}-q#}sbM*d|yA=rmAO$4!^w8kJ7*Y8l!i^!}tnb|Z7+UX@e?WTMHu|y4e zamp3`qJR&S?YEI?&!=}VdWLKS|Fkzfd7qJNxYR40FB}OO`AQhyJ(6WeuY_@rQEZ5J zCp3s!US(t!pD>EGlPZh(W20DbKo;!KWkDYD^2=*MgeofLua9COMseO{r?04=!iGv; zSXJA_cQ^)#om~WHuFmfaU?GH#{j;-E2*qxhOE2Ar9U7O%ZFp?n^|m%Ovb8Rb8aUC! zUY1H{^Y2aU9qG?Uxh;W3TF!^4pEP}SAN~#PA+xsnG) zAhdCZ(<9G7WdWfnGC>c;ZHh8J&|$&YR3|UdC&*(BV{A#Ku~&_G=rk15cN997dje$6 zpo~L_ZOoXJTEts84()_DWF1+p zZ5zOV1~tFl;XjOFof0krqb_ron7|>zSJNYU<0&Pz2jxMDPQmmA`ZAnTOV*j5v=~gP z#u8pk$W%``?G#PjeCAl@A#Lo$A05lOj8O5exfkyz;LK)t>5D1KO1%r-C0&7&e>#>u z@PG>^#6BtM{xv3Ew;tCUxZcL~F0Q?}KEibr*9lz9$8wM&y`9Q0UAMaoR}rdC<(+Zt z(Wt6>%Qgnn4EhM_4EqtPvsyOEjSn5q21>64^C1&h2maD{_Go0#Mv{4&k?fIfcV(yM zN>PG&Xc8OS+odL7z~)iyK57}~xt1|XE#s?6Y=nQN_z0XJNDdVW1Z8DcUZ2GLSPXA5 z0b_1edn!{wYt)0r+9fP>mM`{ z<2`mSX%CzBSBLniLx`bpRfh;m?{^y89`%`hpTR;tYfCFuw-&IgRAC~a0WL3!(O1`W zaEB1%XigIRaG1y_KN}*C2zRT%2xBwtLeR{-c3@y_)hv$Yyf?00&2bBdn)=h-&|>@&jFK-{StY_Xir(4bDo?rd5fA%TR(B(WI6RX(&6ufg#nu1lNL_;%#g@^; z8b_zsGFYh8%a?zg!A42H_;9x=EN;S9ACcXj49AHWkN}(XLj_MKMqvE{OCq>BW*Jg& zj5fVgmZ;$JnOQSydbh$v-}q12I2*%lS(d-WJ^}VFdudN33W9;=J0Fs+^wjb;nO0XY z0<)GGY9v59?Y^IC8r~2G_dcVWeUTw^5Evsf0)Ma+dtd=6EX*yG;0Y+NrBeCiVNCue zyo9ieDLperz>zercsgOPCIPlmd5u7)5wg(&r~oiOLmn0QK2u?q5X^jnnXbWnp&WI> zyraTQB$zycv1DpMUn(1&K=~?AZvt9RKm{7iSIV5=^lM^R?2?31exem1r7(DhTM10J52?mDWz6KgWyOZzM!t5KN_p=o{tNEUoru zRhVZ9=2wEbpuv2rlsjSGRbkQz<{H7&y)mBJq`-7hdD97GQ-S&s&>C=2f$2MCV=K&P z@O`QiDOf6K(~xoq+K1JqYxb33qTD_F`-d>4j%~%gr?J73u@xUZjcsi6rz?j9r;}LT zFpWJBCT~UwR^ShiNyM(Z=WA-Qw8^xDyP)Lol<6#@>#GE~27eIpP>mC!%yEJmG|*@j z^gRMCa)KHNtDh6}qe&{(pZM7szCD2|@(DM`30^CmJsnc24?z&9&CZOw`(9~&_>bY7OUeKW5t2wR!(RT74b@RnLQ{| zu*jrbc|_nS`WF&gwD0o^?!kznJTuwew7=7hbSqLw2Z z_rkv4`0>(N%-wI~jz)25Ro!u9Abvp{Ie&g-7Odx<>8Xx@_BbpTm2vt;iO4QeL~uJt zc65xQ!x{m)Uo5^6Ma7|n+IiA!mM^&*`1#qayEJ_d|7SLqtZh9p)~gB;v1BE*$KU6D z=CCjiv1C2|5uZATb?$vqFNC^dE@S9g7@ujIrY>&m}* zm<4x9@QH(UD|u@@23pKo0?S-wD)z1i&-7V(UF^ISUOMZ?Uw(vzvRC;2N3bSQB!1-) zwotmI=X2(==a*Raj+)N)po#Z7qk4;z)2>7?d+<}~aEcDfcC~J&v^O&d^XWz0jgG#^(gQCDBB!z95pCu zlNql&Cg&)mZa6x&x8lR{@yba8Vs_T6Qluvhs)0#heAAp}?BNelVZ*U8rD74D)xy|P!Z zH3SVWmfbpYJqnwl4A=1vkFigEd+luO{Hj+2zxfzjK7PTw!joRV`2u-MBSH+qG+h=y zFJR`UFin@0(SWfIqtv@B5h;lPEC4_*emILovA(=Ei$x5%^#B!y0$dyO$P!LASJ}*^ z>1fmz%8~boXQ_w4IbUykfWD15O?c;~)b8R*kF%K`S7G5)@}I}~p2ulLx{qIaob{bu z{1>bxVY-gsOi%5;;}Swz^j6sVT37n$vi+Rvw9^W#6LgejzGj66BKr|ooE!2TD?1B) z5TZOM%lZl(c1U)v}WDv0yZKVhYRXiC_sh`zsUr{ z7`F*tqr)ok1$k(JoE#z~V;=Gd>oB#iz&=XDo*0Z0Le>mXWlb+#vppXijshDbmvJ$L zKB+R-|8R!S*wHw4%c9-nj-q*8LM{ye$YAlVxih@lq;<*yyr;b64DZ(QU0vbTDWzv^DP$d78oG$*e$GM>`k!-^+H5GQ+-*rlmcsdH`8^}4CizZ12+6+ zvviY`r|?u8Zy)+05fkp8%3*(=`xFaqyNPrabWVkr+cBIzo1*mTz~6g{_3M*(OSCRq zYOu}HYwHDY)%oaVqWs)3ScZhWdB^c@p8(${Tzk*}ZSD-0^A<66nj{ILtRJ zfjM$N{wav=R&Yh7u0>=$_@=_)_9gqr7fE%Ups-yPa08ZB6cU89x; zzI7Qa_|F{Vx0bP99$&)B5n}_d{#o)9!A-mL`mM1hDz*MgSp>QocNJajhbp>#F!OAV z&b#g|bicWlj)MLWRRZ|C&$0>ZeSZ5{_$=g=@KMWIPxc~zYB}rQwffga6pc;zU+Q>q zIcWPT_geu|@3j4V@d}p2hVrr%tWWoU*SKPFvO?0kjjUj;%cRl-=CDo^VYGmVHQVLU z(KqA#ba5CI#vokGDfk2CZA3>GL1RcOM!ghPduB{-(7wRT-?SZbcv}kKU~p8q#*5)E z=CBU^V1H^)TdU2#6DfE_5D5p+xma_y80|^L4)Z2#_(B7df}I?^Vucf0b{qLM4K5Rv zz*^DOVSANStbBjt4n9ig1Rj{uKs|OZo?mV@;pt&f@$3dcX-3OAj;+$-EF5QqIJa3S zg&(v(UMb3to%q(3Y*I^7!Q-?2e&x)<+PB&6Scnh~Dhz_arYNuSUKZF@Z7t@@ENr;% z%o@@7hhM!%*f_SxsxsUkpXD9Vt=747gH`}YpYW_G4kByVksZ-oxVF%m1 z+|*uBlrE$O`!tt@wpu5T3`((?@|0f|@te79Q2&`<+yN)*5lSL87>c;m%;gXQGiQ^q zUFkU24@!iMF{U?+c{I?0<}HM7CsCTT4^O60Wln3~H?8_=3ZbBD%QjCrr1j7Y;vB;C=+ zV5`EGwzhV~YSSCEX>$gzb0JHZhsdqLwB6&A0mU|s{BCj>cbRSc%} zz^XI5mzy@S+CbvL5+&w0@Sy2bqRq5ct%}^5Z+?*t=d(C7wEMlfk&;WGPDbV7@Y(?g z`M3fW?!PWw+1&_{zZKs?JMe|8*jnbr{cWtfVaaOKMmorn$JtnC5OcPT1>m;S#zx?F z(8eO9^dI({ox+wUJqNBSo+5l)E3YQx?`G4WUKC9~1;+w=lD_65F z!)K$dZE0(4rge%h_Bxw*=TvIuR^pukjdxa?ekw4nUTylZz+_vE4VVIxtn|s`KdfdC zFa!6O*=)9rFOk_sX4vn)h7Dxc%pGrMZ$O@`v%~u-gnPWmeqk&4l^0o9r)Bu;;GA8? zd?Sr3W)P*}YbV+ANAu{H*r>6lTx_>F%(>{Dup(Ri7V*a^@Q1KjF z#g|wde{n6F$j0zXYgv$F%H?;~vR*-XisP$B7T#;i&juD_{z;XKEv7tC+&s0o1)G}{ zckx2mPDVfFru8g#-2W&vIi9A8LWA$65%JVEBBz`La_p5~rjLTX8`=F|#I0a=AJbkZ zzgODQa%;1BuFOnBNKIwU$ptgxwJ5QZpI>FQ?TMfFDjR7IypwJ@lBm8p~sY|NS8jIzu4r9#%N#RA!sx0d^?V}A4V zV569qcWz~T?q8Pdp=ix84Zm#8(@GxW7cGBC3~TO!d}w3;=0%9{v9!dCyc^jY)iPl8 zf%KRQ1F9u`Rm$=AKVuzRw#AwqBmBwpym%e!=KCM)uMrQbfzw`BmhfNJv11UscD~GZ zc5wf^sq*-?{4uqoVA(pp=oL2CYiUtaQ}au&u(n~;X9`$6MybTwcCwwA5_=8%P6$Us zBjS78SMaKr;H&;8!Ut-EC6>(ncm;okv&EjvprH|;c$DAZ?0Z(Q|BLl7k6`n7!>epN zJH$7?#{AjaeE)0g5lB%TUT4cfPkh#h3%f7yDZlwcZcd@jII1Bx=XISi?|r`abr$MP zg^@p`9%IX2#(#XBjqsr)i%~9rwf|B0LL+rR5sM#8UDWNWTKbwu^ZXoNZX83&31P1j6=rf zJ)kO)lPAaJ46vpnUg6)<0nYhA=mCC0-1XjCnUvYw9RhaEG5# zhBE%7vt9B}BP};42tvZ~)xpu`vAGt^L&Ibv=Am9ZHJ5dc_U?ZI2RxJ~>e-MBwNI8p{bU@U6(+%xFPsES#Xvi%9Il;(L3QzcK?#KyGg|1Ip$&^(R;(^tq; zBi-TKH?e-w>y`ZCCKjCh3+(LfTHn5lt!5YL1wS(fpfe~5%bXTrs&xHdgHk>J37@l> zMR+$wrJA-xSNQz(F@00OPZiFvRF&0tho#EFYuB(;-v?4ys)|5bX2Mald047qXEJtj zFQ8GdOF}VSUUeoz$gOHhe$JUpjM0?*xS9+XE@xP(56n1c0J91hMm<=I!crO3STGn> z_Pxn&H(xk=9a9(1^+(0RSsHbT581*x4Gq6UqU!zD%{w4b;*GX$F2Yl@b#rYs8v`4_f+m82)`($T1mm*m>jwDTSL zkgaTRivug}IQsF<+hF8x-^vXBOQ&M6X)BMwj8Ln)F)GLpD~ZSXj;*Ydh@q3`Jq~Ksh~1_9V@qQ2$9tmd(|wa!^;B=Tr+y zeAC-3)RYcxB?!bA!g@qiHMTPDW5`kii&xSxLuG|{ht)N(xq-D&vm^4>U%2Bad2kzd z-^P0L!|yW>H&nQA_CsAQ;X>*2YndRO=BX!_!%t7BecW{jQqXMw(+wM^rzj>#ZRvdi zLdi`8#6ZPj~<$IhI68fU1qxJ{|}uR=hnzx05o{}cyI{?!lMIVvhxLMWX)jF>Mn zzp;(^_{WG#kW(9F{du45%rCOtG-wXU>zg#ed3c?H^AkX1IPcpYOi^Bgj0(;z zAJxgBDwQm!KUT*Nrvc>p1Bh*+nK@qk2*-D<$L@%>+|-iIT`||@4+U1IJ!ByOg*_xw zwqvNyRFKDRCkQ1Ifg;qOKmvV-PMMFPS{U{a)i7sl_|52<)B(Pm!zB8bg-dHLq2RbYu9E&Gf1)fo4K3q^DNNd z687?l_gJTq|NEdvij@?aSwANZ9;#VEjoZ(M58zU&!+oF7B@0I%$rLALbbm0K3w*6dwK6YtX;5i z6+$kLmwofknf=6=>ORqy?qf^$%j~g=Ke7iK_En{P;~o|m^zjEyK@@#Acd+hY$d&|M zNyHZZ*&a5<@>O3fwvOv-o37Ea7}TPXqj69ZAocBOP>TjM1~rk= z8i3lM7S@18W@laT9vDu35U*QxaYi5!1CIB_^?7z`8XH0X!^#Txul>=IO-cp>zf=wA=B$@w$g`$+%v_brx48E^0TlSs=V3 z?%!q~vhYXrC1|s+VY@_4@g6>W-!?1O+N@XuJO@D0X6|%$LxgBEPqodwN8<^pmB!~9 z0Hr+f4UgZ)+70?+XHydn+>OdcY!yuE(u8MVSJAu)e|wkzYag2MZ8ftXz^|a%!Bh7% zq183B0Ne~ftqJ#CZ)(DaTySk9Ty^&};h?*4p$XHR$n;8<3$oTO$R-jpFSS-KO;`)> zn8sR(g0*zw@=!Vf*Zj4gpyND@OUCsYu5-9f;L=)1HP7kF$5i?&>Pr>Wh# z?m`UW#sJ0V-3B6I1A-R?r>P)0d%>6NL#we|?ue?G2!Tb)E&$ zeRXp;Q}I%osQ&B$7U5p-5&BI&-+q8~>5qw9EK;3xRr`aJu9?m0n*XJnuJ$87yaWn- z|AWkLU>Hn)K_EfNM|Yqe$~^?uVW@0Q;7)wjK^9>7YFiU=J5b(T_Yib1x@==!LHTna zoYpUXsc*_JyWlzlkb|f0p=ah@xDp)7^G;+qS_#N79OWE@mo~2cxF+J-jO!I#DoJHm zK87)Zm_3Gk1Ot!kL*V07z1&hs1c_XjS)Df;cBn*FLC${xDt-%sv)V5(oz_-iqRhU5 z=N2&_cWs%I-=2}LLc;c+huALs15Q~qDLMsy4oHncyRJAKekj=wXm1Jk z*0>^YwZrA?o{r3KU29dM)vD(q*pSZsm?s@#;TEP9pNg!WaH9`b=HDT8xB;30GI*{n zcb0Vb)}~Vbfo3^4`JPhCzM|AFg?ePvvB^}&r!s$w9E4regu3RuRC*y|8?;vrOq$aov!?36;@^JCU&L>b=Q?^=)a5?oYo`cG}qMSEOErfRM3!es1? zkJ%)ta67;LG2HGJZ08*gvrO#I<{f5T*#~^bVK$zX^P7iR7q`WKF&=z`wI4Hdn`mW7 zKA=Sm&Di?41TDnx(V)x0I#*hVVAe#rS(iu>D=`TF6qZ zkl!3(GuVebrj&J=BmDyEH3=)U-JetkCrjPiE(MxQ1*+UE3M77eZ9#!D;ZN5nZHeTq z-c+CgYJo(`v9}Nv9y$}R4;qODODat={k0StuhOHuQyKO&DtTHN$kl2qUr`3LmWx~X z&N8?kUg^Nkl(9vYO=a;q*XywMd=q!N)HaE3`iyoUdvgtdd<*d_?Me0aNnXlBpq^}~ z08MaT>SaqNp*P}QY2N}$WDOWi(myKZP=@1~L}hdR{e|Qdf`@D#_%^?Gl=b&}zo|^C z>Mm{C1enLzK_~m zBBeV||AdA3x$kXkt!CDcethF62uyRun>#*XBU_HWCQ8-6RP6KPfMY(V9D}pJnru%D zs;zYP?6TLJdUlJAC{3FQF96S}Gg0;cJm~X`ojFg&x3weNC=DyyNe-zqNOquYRbBB|PQsXIvz-UAO z${&qVP~=z`IK1!`C;oi+Nuu|`X|E2kux!|_hb3JdvY}ro>WvsiXUovr zmKv06{PjxK+w#o@m##UUkiPkpG0Es{l`8z*%|$_4;x)ZK&aW2br& z0U;*B^h6!ztaTLkQKjx}<37abQW#G>!Td(kdgbXDnt0;WdA{uFo_14x(z>SPqZM z@1}$9YwthhSfM&MT1`VvNI#u7APNnC%7tw5KUdz z^n1fA;IMjRqs<*{RXrG7+SqwTjtoEv_Ht7e08tZrMJZmeIuR48^N-rG{Xc9Rbr8p0 zn!;sRPn+p)+caCDhy1gq@z2TtF)skU#Pj`C|p(Cp2zu1 z9l6h07>r?{Io~sfVBLfBvouhxcG72q;YU_mh+-IX`rk^}JWzbK@)v@8Z6G3gTZZ!ID}n{ogRu&i zPitlQx@6~$9X%-mZ-zY1Ajd|Yxmi>8mxO4^N@qliuQxtV>y%Y@cI?>UX?&p+zvXdy zk%u7d`XvN>vaH&ztfsTuNd(o!lreM|#jSq2hkbMy!p%QVW(ujwmxUkzLYV8xe)u^g z+2g~k)s|}8=-aiJTT1Yn<{Wq!F4xQTa@*Livg0w5+Lr`D>8eyD&@vY)Tu~pRk3@;k z#E$SGO>9HO2z(H)N2qFiY!w|1iv7xbgyK1qYZ8NXc9U%(f|}}-(+^?~Q8(VnDaxJy z+{GpcuZpPt@*vE+Pfn6&>0_^D8!24K652*C4~o5JzA7gh1S#Y}k*9CgOyg0Xvvz#b zx2$#lFcJB892#BS`ok1nk%+#7GA5!dR2o+Uk9qhw_dL(~A`HpU^K4ews&B#1$v)Wp zXajeraJQkDfK!#fFAE}NMQ11r*73vV*)@;Rq|Ti`l9znJdi%ZW%6?9s{_6|&v^01v zfAUM#(f{lhz-y|9LzJ~t*mb8UoxbLqzJy`_bnJ5Toz};(E?|9@mDOw@(v}< zxQ>X^u*M&GVb$L%qx~DncKtN)6Nu8*|AnYnUM~DiYI=GLxpcw#H%b7ud)=F z_Y9mIWL@kZ=9!4|BzSWScr#w9h*x^Sh&=tijV(}z2^6uYcC4cW`@7MW5c^IC;%HE zUbG&*4EDu78S-hH`GVqo8e3zD)SDRkRf;l$&;Fj}_68lS%dzlFN|zVoe30cnPExe3 zY$C--!>pNLO?l)iKd{igw``*R&1}CSzG$gM40AEyR_J)og0upyqFT=c6hxX)iqWL8oc^8mB@SCB`($ zV9jE~7Nnh_@UWG(oGQ3+i^a9iI3HWiaT@|Ij>Py-iWnm}Pc|K!hhnvDUSiy5I>V1# zVgVivG)e_e=NB)rK^AAoGws-n)2R~?IpM*7VcYm6bOcytYEId(ziM>ICy~0U4?1g# z@R`hg1yxO|0#Dma=V@#;T@J@f98rR9Z1j!usfY`~v#vZY97a>6y2A^0z;H6>cuDq- zgjUXFq|sE**Z#;lPi>D)Mr=cC9aS`#cC+YlK1UI87$RfB|x!$0wEx{2VSn*)r zD2U&2)V`yE$OsOZC?E+6c_3W_NH$;Q-~Y%$S_F~};443|6w4Y~H&NIFfS}l3F!!=i z@H9kAtwe_j$?EM^pPr(0O{Q?k*@j3{CA>Dw!;uBZC<|Fb;%Gz-J}_&Zfd6wsGk7C> zxL2UQR8UW}Itajw`ETLC7|=pgio!yq0S}_Nn9sW*?0zM^fX4|%>vcOz4>;8AeC!|D zVY$ybqU|Fb=Jps#eC%WIAa-2K+`+w#c`tyxa#d`-`8%8?tD!|~xU+<{2h2aK&^~Y% zI2v1q17`&KByPXVI&@wvRw!PmIMD%d7vIEL5meS4usP5U(D3K01bdPlB+PtuM$^d} zvxBeQ>E_3;0}1QJeg4A&#?ENs-1|G`+R`q78f!rfWye~eZ@@7rw9^ke5-^WkY1~(j z9WZRL@}2)-T^}iV7xrHCNau(d_j#oqKmuxXv0yZU;W<;y1g56K=(sVp3D!-MyR?@$ z1RXx|z>=c4ujLUxBMQnau=Q(FjS0&U9u9chJ*eja_$CLk}D7$dzIgkJI zar_)=H4%|^Gb6DRrfz*Hhu5HM1wr^A@2N9(zd^RN^Ra(rhvE#wS<{Vqw05OXEUCCh zg$$i#e1UvH&xXu|c%&oAFk09E`6zm`^0Lx>nU;Cip#S{Ras#5xHh3Pmo zSUIB6k+tOWXKAubTkjH?>_bYqD>7Ln(rSt%e)tzGVYe+MbFs`KQHBf3cKaR2YNPRR znYFaR+A!Jp!fU{f_%cPcBEHND+`u!NH0WmmUGoztg0wAK+I&hoqo!@w(k4i@RFyNX9jxcxgPGXkm%gA3IyChnM~6KA}~W3SRT7 zfUYO#1nV6)mLVDji{0l4$ zxvPM(zoNScY5zFVWOHS$_d!A|$cL1~2{yAK7Drg-U;GDR31{^=DMgD1e0zry`8=FVn|g-)kf z;ilIDxO+XDHgFpBiZ|RP`#8OAe7Ki+kac;Sj(#D0bIU!RidP8Q0%tE{ErDRs!&Bw2 zAT~T2#(6!W%)H4zs%ODc*D(HdJ?q%E*ZxK^0Fk9s7vGjwSw#1OKjU>bURa0LWcG}? z*(2pdFMG0KVr-%hPD`m&#+xz6Bzgtmu;sR}JNY4;KXnyk%j_cd&?(g@pHy~2>X`_Q z)kVekD*yN@>l{&&wRxl6J^nS&X;ww-ku3Okw#i@+^aat`R zkf;$%Zs;Fm*}c`;P-Xl8i)b;*s>N!-YG6+7u%}&|h(%HukG_T`d-G4e;u;GL?gMf5 zKmoW#)1c*Hn54d>JD9tJAGn4xBt6G3T|9pGc zmw4_i7HG&EMR>ce;w}FK|73Qc#*Z-KIGc=cd!io^b)sCQly>5?Z?ZsXTPHs41{>H9 zF4u%_fQrv*8D+F5;(S`Q5nwgsr*s6-BviOFh9#^s%_XeSf!W`;lqzX!z`EA z97U_C2~#K`F&s6BOpLRq`pErkX?t-Hw~tM7M!YH4!|>8GOBY!VtI>R#C&ZLjMYYTv z5BvzTr^PP^`;;nQtQ1;VPwEe_?~}|?R!f{NyBo@AU+RY*=MTdKHFEo2lzpJ1ulyyd z`&6(X`6KDUq87Q^h#&u{x!HVKl?aQ@KYx$cJ$WTw=U*SMOUCsIu7|G1>-OO~i>tX7 z>j~FBo1*sF6E|76z%_rxJ9nN!Xy-{mtR*Gp2kzd$B7IXty-p$i2I_Q5*_FdbG+_7a z#uMPf1h1f4@Zl-&;VU3HdSp&D`0$j&d=Y$j3JZ{jP`)l`cac_%m=zBw*4@=KI* z*NnU#X*Emm$GUPftgC6_AZ^Myq`)X5|2>^tA|c0g@0pAC_H#1a~kK&&E+_sdSsa;=OLM_JhsfJK0p1h~+j4>&y|GQ~&gb zO{fS;ht-^r$j313lH>f%2c4|jzf8?=J&zr{iWvSDj*&!BpS|9*=FF&LEn#fDgV zs9h>uM~N}y|wZIhY%vUWeN7?SHd20So-@(^wU} z>TfjGZeH^@>*pSefoJ4XVqUSh%KaU>o)+`t5XpX(YXOh2SkUnLFN zTn-yg@j!9BYLcDBsk zN{ges^FPdg$Uzm>CqoIReltEi*zSiYvN~P=8@9>eUb!}60frAeirOIpK%9zMS#q3b z{lh{A;Yiz5`UWzB$6+3VUp@&kvce%^e7K+6WaeWn3nE;ojbH4|r?(O;FZ+kZgsye} zJ6?Cc>wAoM4qS!H454i{G>wW-XhdM?#K+x%*Ko2g4`otYwu66gheb&pALWK8t1=xq6|*|(-LaPf`cApS)(Q#ohwI~QWP)ijYBQB{c#)#Sxt5~0jL+y&`*D?&~;j+=b zkmkt^JcLPpvm0Pxfe%wCvO#hj4&W0@Sm;9z(5erWD}mo~fvKc65R|EX#abE-$ITRF z&RmS0aA<~xuCfA!0Xbgx7BZtLo8QxN|}m_dY1!gO5?G`derts#!};JNj#axa#S25nRV8s~7Y*skkKhVgr8Wf)b7rYve%KdmW=hnaN`$ zDXc>Xl9UB7HYA`5cY*)_LX*wS`7}0a3ld9A;9fUKuJ061pE+y0GfFaK<5dlQ~(v^F9p*n z+bmAF7ge}H1g8+(1r2Tq!R0!^9#z472<$q6)oEaGamT)*6D&>ztAU?FCffzD1`RBm z!1_7C+HGwoN;mNX>_O@QXA>T(pnx)*Kd6^_7!Sh)G5=il!-oxc8+ne0w2(4_rXaB* ze*sVt^$y116=0=OC()D65{@==0fc$$r*CK6;j0} ztRAUaJS6(4d;OjS-e(#M;FsK`Z(FanQyb8BT{hnGlOEDy*1$&?q&ePWaD)tXC$X{2 z`+7(n_@@S`mvns&|H~jvlHQoZlUw3Gb`IZyyT_li1w1dlj+55H)#0jaHt*#r^^@$g z`5aGaq%?Xqf7?@vmI7w;3!YM6>8Dv@0dMI$LtVgU`VcYf^;j$@fe+DEH2p*+-IO5M z9=Qzl7v6(c&G;thnyD_(dga$mP1|KoYpq@f8v7;Aog{|AdP$u} zcG*a)rVE)7P$*^w^*r!V^n)qMdkPAEi^amS}j!Yk0D^@>FlBd$$aNjdQ~K5bP8utOMVoZ+F*V7ZB_e zCoDFNbV@5H>>dsFn=LBdT!LNbgoUWAQx5Of$}mrZwX3iZYX~;a35%7JPFduH4boua zwjk_s=u8%th(R6eZK!k5GhU^fYn?fpgGyiXmY^6`F}j0@52jLVmrDBR z&tZ^4BzuPt6*&=w5u%cPT5)G-h+Y69L~je^3tLO>evdabZsWNne06JSpwGn|!IyTs zutwXR!K+$J{U<+Dea8XqzJXlUvY5k1&ZpOE^lNEugMQx>NF=3810p_bDxcM9+2CZqxo@F@I@`SZg0w6Ad!sZ) zGS~ASK2ij>#3uSkac!5w=PQ3Q_$&Y06;D|7B*yWDzS1llY=0ROd+iI{(^u-@d!;}! z?yIbA4UsLy3;R4*_$Xf~mR0jyUnvH+1HMvskFgl4O4j)BUwoy}ZDe0;BfbN65Y`i) zD&M}t2+E)3pXpzr>zxr6p2b9e=Z}v`gxAjHmcXHjj}D@QK2< zyxLFd6S$!@KJgK_Q#N1FY&3p-yU9kQo2A!5mu{(se{N0~+Tp4vHTh|5Kt^Imq*~j` z#_~SW*kl)hh1!4H<3{)y2zQ1!%6(A-Dxpp7vcLG@uUH!k>4>r2Ehk;X*2u_8>{+e4 zrEYp}7H`I=o8B$(!ny0+L)-Pv1wg9OaUw=ArzOQw%0reEPD2U@6vQM@CDBq6%2T`7 z7`tC3HohTZ*`E!vS<)^;k&3+@Th4h~&IJg8@<|LU#1=}2WbhhJszd1Z(fZh{GB(uk z*8Bv#EdPJnldi{(H$p*NoT%u^(Fbt6n5#7?dD4mP??lzb9P@3aJghqCENvuPpaEeMJb z!@wzsk%xWRw#I$qB}eXo>T@O>kKO{tYv$jmK7hfw;WNC_Gy2m2PM_tWfl?Pk_2(v> z?p{*G$KlDcw+iPmH;^uV#f}U|f8{ZJ7$yNHfWgtV7%vGAPGWArq49?Z*3=-U-9B+l zBxCo!MRl@pA4PIfgItDpt&W<*RQOVYM{3$_TT+89?KZ$o4Va2W9=VK=@YX?6V9+BO z;4QNON6+WJf++fvZ0@#nz4CDeABMNErB4fzx?3=rncJlwuE+P%m4pxo@Z~{MSKTR2 zLHh4A<%(HUCM>K2( zO60A7AW)Aw&xf~@0v|lopgw^{k#PQkhKd$na`VwVV z$c8hs#QFYT&i4r-Hv!Bifa^|xMNWXm_nmRta&(fDXjFC|ZPUj2TOF`1I z^L%xCDRRL-Fr3ezK!Hqo7;8i-t+FwV=~N1(uEyc==g`KPbVRYueA_-D2+|+MBOJB7 z3>34a8*J%b*n})d@U!_Ar0SJjK&X)zwpGvQ5x8<8##)De%sC#~K?<n7 z9IZ_nC|X~E@K{+bp3t1|sW{&CbA>Qh#=+joe>35?A+&=5ni88gJ)JnF_(M26wfQ0* zj)7cjh+Nw+P4>S5`|fn*ZLE>yVHZ<4wo}^Ar{k~f$w3u~KEqKer4S2tI{B7@ETX1r z4Q<1!fNQO7*k0Una}2s{U!q1c7@$_x66Oj;k8e?RQd1`(dY(P0LFvp_1*0ztd`Y(* zU&6D1#vs{%BLn+3eu>^-Tlm;a3@9fzppule18~loCPt2Ql5>?+UEwKf#uzqr>H&(^ zfXF7P_6gxi364&|@CLk|DFAy2U{j~&mmUyjRtp$Ql(G&$GiOlYKr-vHa9y^SV_`VJ zqXamjChAK*B6^LSgoSp9GHk4%wGF2$LE(8DBsUP>{)0j(|6pnp)dZaX|Ha+A$3<1G z|Kr0R1XOfDK~YgrQBhG*ydkJ44w#A#5+LQZ)Rb|1{C9Vx@y}xT2@we(sQi5 zP^sZ1z%)fOv(&V*&Z4nGE7KC^`+nBid)ULp%Q@%s`TSl#|B%^xul2m2XRT*F>shyF zAsxO04+PO_%h_lw((T(5Tf#Zd8+HaI*Un&TG57xVqi|lH9(~w0%uQ#5k=IZeyV^_b zBvrKrXazrQI_EWwO7RL?`6pTn)hfejF*i!*N?9X%q<^eJ>Doc+OcZYEAVnqzrKK2_ z<GWUu+RV`eNVYh%mYxNrv?9&4#Ia;AD!LL87P8B#@{%C9+cv+lAPYnk4DWm$YDN0LGJ8fHgRbtnW*x zfh1q5a%+doEEd)U2_Er%KaaSnDodgX2g9$gPoL&8u`9UZ25IpU?+i>LTDWE}Nhl9n zhY9g)Jjr`*Cx(;*9i5*Pj7c(8KpIb_YP^j4Im3vD>uGcVt&X9#$-xA+c%eCwMVt7c(i-BcuUR zn>Na_PEur~yX#TWlVVq+2qix=UZ0Xr$Gs?U6xI=%dP`>LVb#ESc7`B5X zZ{iNURH1}*mLhAv8{1jx78pOizsTpcaeIvX({j7#iaExzG}q&{O|rMJYgz8;K@iSa@aS3l(&or zNk`#i{Ex!$moM>aeJ<|lW}TlCg%E#&Azz3OZ=#uiL!u(wWGi(XAN$8{1G;@{C*1H} zIEI+ZX$7K@S-uZ% zL2Ji8oujmmG4;3wzu1&vG|_^o9qtg~4s^tzCOf$Ir)oZUO^By{Y!<@fpw}G&0@(FP z%{?P9|4-AxguG!@b|s^`)K^+IMtQuulr=I4N!hsz)ozP>z?Nt{I8)Ct19=0{9jUiJ zXJP-q+{b~+Rd@PuU(#v50^yzJ0Q~O`4LYoJh=R4l-%(N0M9J7vc`i!o+Qtv5u~m(| z3gvY8v%qltN%3W;+9`*lq)_RdHVTW99tAIIC3)%#t7486 zbhN41JG%CQvg=_fOnI=E)Y=|gh4lW}wY&nyFrq;8;)q@u+c+AXCVT9cYJO(PGuuoq z5Ii3ANU_7IM^YWwoFT>c5?e1Kmz*0Ad%`ib3Zwk7aUE^R@PrED@wzG-OgZdRub^w| z1ff{zd8ifQBe9J@Nz!7T`%V7FY(Oiy=oox)kB=2c-L|uYD)fTVWY&h5$_Xe|2&*q1 zL=p5o2bOaY#5+ipK5ly8Ly0+2lbs8EaM)l}m0U{Uh21!RG%BmTa5uzp0W6p}QUYAT zsPQu2BGfi5p^U^iy7+?+x~SlN^yQQq=st#&vhOM=6Y4P<%Vr)|^Ki5jraDelB3Wvn zBXAWJ=`k0)knu@WpnJjUft-ZmeI&sYypnB!@dL?Sv-O+HT|7jPT>dk}MFb!087x*M z${Ar{cCd%=8Wdx)ElSKaM@Sz5>gZvS&_|iwTQW(%9a9$emZEjc*aG3MeMYQ=6D!Y- zB37DkRsxGJ5i76a6^%J_gYwTv^3)Iv^R1KKF9%=j^k~d9ov`)G38Nu+jiMorOQ=pQje?dOuY5yZ-bEaqB$_YRPY0Yd7r{j zvpmqJ@;qVd099!R`WkV^WZyCa#qx4BjYW5z&TJ##P5}iQJk!_~7-itRf0BsAdl5N? z)h35sWsy_yC8biSRK0VRQvp{Gsx;0JjY~;f&|yN*=RhMHoSJ-8TrgLduIffl# zLRx%-MR9{r4?)P)l4SnpUXt0K#X|VoAlzDNu#V-;ldS*V658 z*cYHpNgZOKSUiURcg>Ldw9vUF!NOw0^z8fH|lFs-kxdWxveG$Vu+fsbSFBzlvM=?_X znPEu}#Mv&{nu~G5eCiwnT=Psjp9t_*_6(G|OQZdia|5Lb;V{zl5BurK0Q_Ov>hJPR zHf{B@uuFkT`XK4m*;jF=6X?hI6aChi&|XCR$3z#_60MUlSL8Bc&Lhk60VRMM<`{Z@ zg7wt&qEt6)GYqNA@W|#6$lhYE+y zRB!>R8CznZe_4nXMI%mkXpCAYrh?Cur9;s~OOOiP)KkUEOGBmBK}4v1K#a6oh_TDcqf+f*+>>2d|4 z0xi6_B!uKnbO#Tl*5McxAy%HafQVn}jNAI!xFUklC` zs17)+3>YT$ow9KZw??rga~!@MT%)rl;p%~(aV&z4Tn0KP_@v83JZ3>mRsJB{KMJz$cf^l$y?U)nDWsuDJG(mNaGronFjMdw-)U&FDjQuSOOVx=!A&kUDB z2mV-2E+Ji*Ad<7wNC<*7AIzG4E}T7-e4xu@+UDm>4Y6QmML98C>Sh1xH@CZ2>ETPF zc6sgC0-ec2ESc=1`aiY8r*$=-a;6jy-A-OlKXiJjo8{C9>RN0e@>E2XU*)i^bxg&! zjO{SXre(&2URK|P-nK@u?iBR3*wJPW`M?lkFm&0CV{-l+kF#CZywfH~)n zVHYma#(|9E7VL^M?m}{y1p_XIJ;=%#s>8e%OZ@KnF>I7Tz!WIr%Jv>fQ%hAeABxWo zTQN(*XG~I3Sosp{)Kh2o?5V{toi=Pn;j78hLD96W0!WUG;`rlMnmO9IN^l})6q~k{ zgCAmMjw2Ag-1fX&Ored6Kkl@Zm_|=smyd10IE+)`>QPX#q?c)Db9qm%viR}^5tDj- z388X)(%FY+!E;8jGubC^h`}_mchxe#1iS69JasKKIPCj-B+~F`OS@nGBN2v$P2~j< z@l^|bdsUI=E+;8$v=nY%56b|nhhgYuRtWbb_97KjmK;QP+z9q8X_HXcjtrj2ks8Dn zI>^b5am`lCG>exg2qBq9YZB6+jfEkOeWEV{+|#!-LUCYJ5e^^_h`s3Q(D@oPOb+E0 zv((0(yB+oD$iPX!MRM+D%OtFYtVUBjg~0%au&oj(h=>U4z$#0MK(!*)Y$W|*f5XN! zz|p}O`NLjnKY2$jwO^0# zPJF}1(_@$0MoV5DhK?R@}r18VSEZcXoIm#8*O$t*zS#pdarekDB-@I;EURks{jM)lgPt z;#jGR^w@pM@G)2&3p%AdIaZ1ceyCE6@D9mUpuj`| zvZm;ZarbknU1wYw-!47+u(UDt{W5BV*<7{4=tM1s6 zMD#{i*kgyuS%rVGK^VK<&0tDlOE+LYKHeDE_|K}{gJX}BnYY8Ob;s`Oe|A*S>@Q;7 zw1*<2A}+PW^!Oui9+vsWL$#YP>oRn&H6VpQ*?v>fEK(OcO{yFJ#H*o3Q^8(5Pz=cJ z_={c;uE3%%`I|ym3?5Q`Szx@a1Kn$a1B&1bjp887z@GNK?Jro*J5H?IBHiW+0DdwLfEg9BC?u2?`Cp~x zFVK&cSK?R=H#=FaV0O&g+2lx?8ysDHm1@(82h?Wl5ppqegRo=8w5=(5=;lrGF8dt# znBj8OocQyeIyG$@kUj^~wk~)!RIS_9Y8~3TrdrbvQMDH1t_)GF?XiMJ)w)@$*7k?1 z-TlEQ;vgd`e(D#o7i0H1Y~>@VVk_coxGn&z^#+q-oh0?J#~7@w&~4%DHTTm@JAGq} zmnu9sHtY*gw@<2dYuXn0h^-6l2QSC9GkMtP zZ)<`{xz@QeRgS?6lz%$W22h<;ge>vyL6p$MY}QbL`osttB|CT@;9Go+@y^Kr#v>9z|h) z>@%wN66+vbFd)NZ9%OTl@tW@{YJSsQ1_Lils`OYYP7uJXu&uoE1-0rELa6GC*|5%3 z^=dli7?zRbzkYokYcsL#^epyaG7d~y7$#idY|y+0KBQ+kk^@-A9T9UJ(7IE5RA&pQlNo&V6k+ea_m;=A?g19%ES!p2`@UVEX}~)*I|d10~t~qM9cgk zL+U2|RiQM`lsefhA7kZb?m8Il;-NdxMTLb!vC%tC4O6dFG6y;Metp>-l8(!FVD%X5 z0!*U&+NOiVxp9}+6N z0dfpUBJX-XyJ`h}jz1?Z$qySlcUY0S)W4U!KZOmMffJf2&{P9b-VKE(DATq}f;<6P zSL;Ds9r6(0hSPl$bmzo9;%9pTSI53GTAUS;VQvcov z>!H0X^^fUal2U5UHz22jb5>-2tcV|^V1o_{X`a(b(}feIg5%^K0p4A}CR~4>+!|I| zxCEb@C`~3y%}iYa;9_YkpI-3%ol>a1d;u-@Ci%^YH=ngOqyAAv#P+lO;KhekPlGuA zi&6wfu_bxA`G%Y(MO*>Ic@Rh8EPzbTX_ajcE_i@kqwf0 zMR`~lzW^awc(Jtx%g+M`tW(Lc{yc;Am?$jw7CK9UBVai`YOLBbp#a*SPUhE$TIHxDjnx9yJQw4mDDgri15#(021< zKCEb}sJtjHinXBfZy<~XCWMT$2I;c$O!>K+;E=7xoYU&Ogc!SZobJU{-xycChxM-> zAf=}!!qsHBE+KOAD*>0-hzLu zm*Dm^JtKxCjgv(Ry18trkB7vqoP=$Zyn{E>EyRz9#UVYw4;Qd?M+(F; z^00`j*K8dNFDl>6!3G1O;(D9dgyej<^9;Nu(Z)&5t!?shA2V26=I@}&g|mt#sDr*%Do?Whd!!VF>~RY~44R?tY8pZy<;HF}C_P!AgQ) zC)c{}DRPfeB5?NkVpa|YaIGVhMPNM$Ni#I$X6BV5x*rovS4oupH^H_Jt9rt_g% zd9J{WgI8&W@`lPq)d+?)GDWH8>vxD;u&w%D0cQx z?8!fnx#mqK2;NwcT5D{!hb%A*g5qdRtH~I9sGP;T0$1wHHrPqEKamP53b~=%tQ@Ps z7*ZUCMk9-6=XlfD&)()AZFdT)9(pT5ucrEjeyarKNPSxVW3r$)K~Q`Pw3M_QX>c>L zLZV-FtemfWI9(d;GynM+Qa7JjiqG9rrx4QVURB1=T*0L4s2Ul`rp%DqVKr*e3@O{5 zM-E4JPEdAElY$ed@?Ax|H>v#m50pz8{|y1K%wCcasik8dJ-GNXQfp1SwxK z8p4hH5AWrWrl|hI?-{~>@S`^T>R#bM6d;An{UxC*TrY3Nff%<}NTYWk`f{RP4#eF6 zC){wJ{8|o#YaVwXj?N<;xRwLqL-PD*4#Y!yhtTDPHh zAcjf}I1nK`i>c7df%s#tzRBHvs>Wj8tOK#ui&BUerJHdes8KZJKrBxel{aiRH!igu z2v63VbRd4f{Kkzt5LYpJB?rO)W<0(BhFx6y{u2jc9w*U&12MV>c=R|Bk58&`AYyqo zO%wjd9f*6bVrhu?;HB zX@Zsi*n!x-lPg@I-ho&Z+hYE`II}lZOs!FYMAfl0*9Eejpc%9YnK$P&0bsUISK(D6yLXQgv;@t^? z;+=xxf6;-s9g|l#<3Rik$I{4wSnW>^#BvP9iWk!a#6diB2O`kHN5j}guFXW?)_6Y1 zVo!vjaxno>lx?BIMl@$&_nbD$&t2u2Gzcu?GZr9jaABo|ZG*;{wv~u^4EGlDa@i!4 zAXbdoj5o;(G0k0G*BKG+8Qn$y_#EtE>Mw7h*@T*D0Xr+5K^2I8orX0*@$wvzW^r+->dWf~sGdu%%k(PL7SKe0{<_s!R@0CWi z+Q8qL3RfFyob(r_EAb1Y4*e>|iuOF8r?I7LlFXd$k>p2fYY*<&>B_PNQV(h2Sf%fM zQlj0>$A`|;wHWfZ#?eAiKKZ=%J7U`gn7ZQQ;EC9#V$syA!G?$6YRo36s*ZRl-4L^! zM*dW}3r6SV7T_QgRcMjF%{L*&+AMwtdEpBhVfLlKCqH+y9YgE2rRG8(46oPn;dLh7 z(o1?l!|P;mc{El$tXUki845KDpz3>?UcyM*lSdnAKduh6*Rls1i+*zKEo5b=W}gIF z?vv=4v?-v6r;>K6gYLa!)IJlHJ;Tm3pwr|f466CLt8rgOkvw&kDJ8Y2{}JPtjx_{4 zB%{!J-!j|d7>kbwi(}&V(^yiNTi7U6MxBIY?8bU# zsber|+Y381%3!N)F!rPA)HWEc$BN+#8I13Q!6?6ZgTb0&=EGwzQd5Ah8H_)-T+?8j z;Gb&@#vV)!G-NQ|toc^UV8C8{nT(8@y|_hZFdh~NwGGDYL`<`AYmiwp7-Kbtbq3?l z&7!614aV2E34_s0?!R~U>ZM(YeltdQluY%solmrAIP!T1PM4IYEx12*_9 z9Sp`l>^#H&xxwh&kipoGQz&FG_TVoWjJ@bA_9zL9qz=l~hoqozXB$+6Zm;aUv+=wi z_cX9|cPhglk~$AeYlSzdSDe17n5Qyezi1nr6UolTS&u9>kiPAA#IxH$X8JT7^4zyO z$iSXht30(B2&RXmmf@A;pXdEJY5Pg@%y7 zH^kL86J_5W$L8k1A;f|j?OmqL!lQ>v9Oqe3RLd$5XXw``(;i0bA*EdDJ;>Nv98T-~ zCn8Q^-uoQu{VF=(fd|rYIIYCubvSJ%f?x2%X^Y1nKuONbQLzO0azkJ zu#1Ug8T;uH!R~%d2V0F-0=9ZB!EPYf@%T^OLQF^T`f0FZRM_4s?E44;RG+j&gkZlR zl4UG|!}ip}&i260=CF$hHqsmRg+EnkH@z;T9jd~1Y5YT-Z~*|XBGB5)m=TXM1)}52w3MulJ*Wg>>9ih(yn=lq@@-Vdn=*I*$u!F5rSPxB+Jn{`|Dxv#VaB0y_9BP9>GR?!@lx|DsAzrLfXHnuss@pB_afyCSX7Q zjii0?Wu3I2?4R*2$dm&V4 z@;<7&dZZ;H1bhBP4m+E}4%Nfj@JdK)qcj7T6YLOg*tam=;0>tcWg+ch6}DdkutbDl zvjyy@=SkXESL>ubg;xUh)C&Y#M6d(AVS_c;C>8df9JbY5s=9iFB^rdfizt?{fgH7o z9@XqYHFMOpgqrA$x)^RW7xw9wgs@Ml!um7-OGF5^hk#vEMZ!M#H=VE>@k$80aTN)B z55b0c!+wJ!{2cbu8UZ^~z~;;&ZKy|DB0{jw5Xmyuh{JyMB49oJZHNaqgyXIw+|k~+ zV>I09DsHSQZUxm{J-9@Ka4#tw_nu!!+>wN=Rl>omOwpD^;Qom(I8ZVK((GDS#Qy!7T}eTwO|FwT282Qy-|yC zAfL;6=mjBbFM*oa04mWS)VV~ljGc55>O0TtWIcyh0`=T;gt~%IL%dNVG}OT=YBP>% zUr+T`kE}$4P%DUH85_+}+i+Ch;!QN=dn;a1$Pk3>3v?v=x@{rL%t1J;s5()I2HZT^ zP?Q|Sw#Fb~Df2;kjJXhNiDwVx+3nZJe`}z~zp)?Bzxzj?A7?>uKKneD>#x3zf0#om zOAex0pt9r;`h)oK{#qD&3pN7(whf`AbfM93)*bQHD84`-D6|I&@BA#OSj!NgOmrYJ zPxV%%&>?k zzWj4BC`r1TE(^O95vd4U7AoU00I!lsH`<3ujGF(Q{2j+}^=B7RPzpLX$k0sXFY;(r8Z zb1w`m^V>jjCiKTyWGyV4_T?N2(z(``{3St%A9n=>Y;BDEOrr1Zg(2)R4x@ULKp~za zp-$76yS%95(-@-uu|5-&8wx<-2jb$b39Rx+wHwP8v9TqY@*i{J{H+1`rP7=@Kij2V zf8baC4_7mp4Tn1%Mjk%{n^2iwwLzIE*ag-i6@i_2L~%6=%w&Wd zKTT&KbZwPVlNDfRu`p9RwY662+F@k|WZGF;tu(-A)@o0zy`W6ix1JRDc%^7oPl{PJ zDMn{9*Vpy%jmBd~Xr0=O<8FZ+^(E&`rh7f9pQr{AElBcKpR z`3?`YQS2OM|M5C7CJpA|at}<)tRo6JVkKvBD|A8SN?SW7jmxn5WSFnqPJ{y+l4I*LZ=5$i`h zc#CD{ak7qnUXNU@G^}ceUaqrss4+9?M<_i$l%xhO�TyIl#QN*ohy|5zd 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 delta 237294 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�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 oj-k$$ef&g5>d2j&{yPEN5UN;$1@|Nvo{k!@WZS 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 F{2z5_ZL0tP diff --git "a/\346\231\272\347\273\230\346\225\231/resource.h" "b/\346\231\272\347\273\230\346\225\231/resource.h" index 31478f88..4cf36821 100644 --- "a/\346\231\272\347\273\230\346\225\231/resource.h" +++ "b/\346\231\272\347\273\230\346\225\231/resource.h" @@ -56,12 +56,13 @@ #define IDR_JSON1 275 #define IDR_ZH_TW 275 #define IDB_PNG42 277 +#define IDB_PNG43 279 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 278 +#define _APS_NEXT_RESOURCE_VALUE 280 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 diff --git "a/\346\231\272\347\273\230\346\225\231/src/IslandCaller.png" "b/\346\231\272\347\273\230\346\225\231/src/IslandCaller.png" index 7645bbb0a3735bd4f3395229e6a0621ffa68b2f8..50a3cd47a800b93c59c4c51564eb4b120ac4ce7e 100644 GIT binary patch delta 2578 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 delta 845 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 N002ovPDHLkV1ntlR-wAbmiU!E)}|op{Wyi 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

?qR&xSWgWBw}5v|W}s!voPjSRURzy2)E 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 B;er4F delta 66355 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 diff --git "a/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.aps" "b/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.aps" index f497af2b4d94c7fa707d109b4dce86d703ce478d..d7efe5fd256042ca03ba9198046c751f4d8b3902 100644 GIT binary patch delta 107512 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

d$X!pV01EciMKyrD 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 UJF^iK(p+&xoqgDc2-N!j0BRJ)%m4rY delta 91498 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 Application true - v143 + v145 Unicode Application true - v143 + v145 Unicode Application true - v143 + v145 Unicode @@ -490,7 +490,6 @@ - @@ -511,6 +510,9 @@ + + + @@ -550,7 +552,6 @@ - @@ -605,6 +606,9 @@ + + + @@ -635,6 +639,7 @@ + @@ -659,6 +664,7 @@ + diff --git "a/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" "b/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" index a1be47d2..814a07f6 100644 --- "a/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" +++ "b/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" @@ -91,6 +91,21 @@ {a73c31b1-6419-42a0-a606-f17415f934a6} + + {771144f0-3bdf-4e57-ba9b-0016c21287ec} + + + {a27bfd09-a728-4430-b51d-a1f26a4b37e4} + + + {3991d6d2-ff04-4376-b628-94262baad76d} + + + {fcb88c83-503d-465c-8dee-2fb096808421} + + + {eb6c2d2d-03dd-42b3-bf93-06205242c7f7} + @@ -720,9 +735,6 @@ 头文件 - - 头文件 - 头文件\Launch @@ -738,6 +750,15 @@ 头文件\Inkeys\Json + + 头文件\Inkeys\Other + + + 头文件\Inkeys\Other + + + 头文件\Inkeys\Load + @@ -980,9 +1001,6 @@ 源文件 - - 源文件 - 源文件 @@ -995,6 +1013,15 @@ 源文件\SuperTop + + 源文件\Inkeys\Load + + + 源文件\Inkeys\Other + + + 源文件\Inkeys\Other + @@ -1227,6 +1254,12 @@ 资源文件 + + 资源文件 + + + 资源文件 + From 09459dce31f02b9a9b9e5d2843bbfddfe7704859 Mon Sep 17 00:00:00 2001 From: Alan-CRL Date: Fri, 2 Jan 2026 20:01:57 +0800 Subject: [PATCH 4/5] 20260102 2001 update --- PptCOM/.vs/PptCOM.csproj.dtbcache.json | 2 +- PptCOM/PptCOM.cs | 1376 ++++++++++++++--- PptCOM/PptCOM.csproj | 21 +- .../HiEasyX/HiDef.h" | 4 +- .../IdtConfiguration.cpp" | 4 +- .../IdtDrawpad.cpp" | 19 +- .../IdtFloating.cpp" | 8 +- .../IdtFreezeFrame.cpp" | 8 +- .../IdtMain.cpp" | 10 +- .../IdtMain.h" | 10 +- .../IdtPlug-in.cpp" | 102 +- .../IdtPlug-in.h" | 8 +- .../IdtState.h" | 4 +- .../PptCOM.dll" | Bin 14848 -> 40960 bytes .../PptCOM.tlb" | Bin 4876 -> 4988 bytes .../SuperTop/IdtToken.h" | 30 +- .../src/i18n/en-US.jsonc" | 4 +- .../src/i18n/zh-CN.jsonc" | 4 +- .../src/i18n/zh-TW.jsonc" | 4 +- ...2\347\273\230\346\225\231.vcxproj.filters" | 4 +- 20 files changed, 1279 insertions(+), 343 deletions(-) diff --git a/PptCOM/.vs/PptCOM.csproj.dtbcache.json b/PptCOM/.vs/PptCOM.csproj.dtbcache.json index 09dcc4bd..06283c9e 100644 --- a/PptCOM/.vs/PptCOM.csproj.dtbcache.json +++ b/PptCOM/.vs/PptCOM.csproj.dtbcache.json @@ -1 +1 @@ -{"RootPath":"D:\\Downloads\\Inkeys-inkeys2-final\\PptCOM","ProjectFileName":"PptCOM.csproj","Configuration":"Release|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"PptCOM.cs"},{"SourceFile":"obj\\Release\\.NETFramework,Version=v4.0.AssemblyAttributes.cs"}],"References":[{"Reference":"D:\\Downloads\\Inkeys-inkeys2-final\\packages\\Microsoft.Office.Interop.PowerPoint.15.0.4420.1018\\lib\\net20\\Microsoft.Office.Interop.PowerPoint.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"D:\\Downloads\\Inkeys-inkeys2-final\\packages\\MicrosoftOfficeCore.15.0.0\\lib\\net35\\Office.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Windows.Forms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"D:\\Downloads\\Inkeys-inkeys2-final\\PptCOM\\bin\\Release\\PptCOM.dll","OutputItemRelativePath":"PptCOM.dll"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file +{"RootPath":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\PptCOM","ProjectFileName":"PptCOM.csproj","Configuration":"Release|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"PptCOM.cs"},{"SourceFile":"obj\\Release\\.NETFramework,Version=v4.0.AssemblyAttributes.cs"}],"References":[{"Reference":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\packages\\Microsoft.Office.Interop.PowerPoint.15.0.4420.1018\\lib\\net20\\Microsoft.Office.Interop.PowerPoint.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\packages\\MicrosoftOfficeCore.15.0.0\\lib\\net35\\Office.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0\\System.Windows.Forms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"D:\\BaiduSyncdisk\\工程项目\\智绘教\\智绘教\\PptCOM\\bin\\Release\\PptCOM.dll","OutputItemRelativePath":"PptCOM.dll"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file diff --git a/PptCOM/PptCOM.cs b/PptCOM/PptCOM.cs index f21fe97d..5077b344 100644 --- a/PptCOM/PptCOM.cs +++ b/PptCOM/PptCOM.cs @@ -3,8 +3,8 @@ * @brief 智绘教项目 PPT 联动插件 * @note PPT 联动插件 相关模块 * - * @envir VisualStudio 2022 | .NET Framework 3.5 | Windows 11 - * @site https://github.com/Alan-CRL/Intelligent-Drawing-Teaching + * @envir .NET Framework 4.0 + * @site https://github.com/Alan-CRL/Inkeys * * @author Alan-CRL * @qq 2685549821 @@ -14,26 +14,33 @@ // 首次编译需要确认 .NET Framework 版本为 4.0,如果不一致请执行 <切换 .NET Framework 指南> ///////////////////////////////////////////////////////////////////////////// // 切换 .NET Framework 指南 -// .NET 版本默认为 .NET Framework 4.0 ,最低要求 .NET Framework 3.5 +// .NET 版本默认为 .NET Framework 4.0 ,最低要求 .NET Framework 4.0 // // 修改属性页中的指定框架 // -// 确认 PptCOM.manifest 中的 runtimeVersion 是你设置的版本全称(C:\Windows\Microsoft.NET\Framework),如 4.0.30319, 3.5 +// 确认 PptCOM.manifest 中的 runtimeVersion 是你设置的版本全称(C:\Windows\Microsoft.NET\Framework),如 4.0.30319 // 生成 -> 清理 PptCOM,然后点击重新生成解决方案 // // 其余疑问请咨询作者 QQ2685549821 ///////////////////////////////////////////////////////////////////////////// +using Microsoft.Office.Core; +using Microsoft.Office.Interop.PowerPoint; using System; -using System.Threading; -using System.Runtime.InteropServices; +using System.CodeDom; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; - +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading; using System.Windows.Forms; -using Microsoft.Office.Core; -using Microsoft.Office.Interop.PowerPoint; - namespace PptCOM { [ComVisible(true)] @@ -41,22 +48,23 @@ namespace PptCOM [Guid("65F6E9C1-63EC-4003-B89F-8F425A3C2FEA")] public interface IPptCOMServer { - unsafe bool Initialization(int* TotalPage, int* CurrentPage/*, bool autoCloseWPSTarget*/); + // 初始化函数 + unsafe bool Initialization(int* TotalPage, int* CurrentPage, int* OffSignal); string CheckCOM(); - unsafe int IsPptOpen(); - - // - string slideNameIndex(); - - unsafe void NextSlideShow(int check); - - unsafe void PreviousSlideShow(); + // 获取函数 + unsafe int PptComService(); + // 信息获取函数 + string SlideNameIndex(); IntPtr GetPptHwnd(); + // 操控函数 + void NextSlideShow(bool check); + void PreviousSlideShow(); void EndSlideShow(); void ViewSlideShow(); + void ActivateSildeShowWindow(); } [ComVisible(true)] @@ -64,30 +72,31 @@ public interface IPptCOMServer [Guid("C44270BE-9A52-400F-AD7C-ED42050A77D8")] public class PptCOMServer : IPptCOMServer { - private Microsoft.Office.Interop.PowerPoint.Application pptApp; - - private Microsoft.Office.Interop.PowerPoint.Presentation pptActDoc; - private Microsoft.Office.Interop.PowerPoint.SlideShowWindow pptActWindow; + private dynamic pptApplication; + private dynamic pptActivePresentation; + private dynamic pptSlideShowWindow; private unsafe int* pptTotalPage; private unsafe int* pptCurrentPage; + private unsafe int* offSignal; - private int polling = 0; // 结束界面轮询(0正常页 1/2末页或结束放映页)(2设定为运行一次不被检查的翻页,虽然我也不知道当时写这个是为了特判什么情况Hhh) - private DateTime updateTime; // 更新时间点 - private bool bindingEvents; + // 结束界面轮询(0正常页 1/2末页或结束放映页) + //(2设定为运行一次不被检查的翻页,虽然我也不知道当时写这个是为了特判什么情况 hhh) + private int polling = 0; + + private bool forcePolling = false; // 强制轮询标志 + private bool bindingEvents; // 是否已绑定事件 - //private bool autoCloseWPS = false; - //private bool hasWpsProcessID; - //private Process wpsProcess; + private DateTime updateTime; // 更新时间点 // 初始化函数 - public unsafe bool Initialization(int* TotalPage, int* CurrentPage/*, bool autoCloseWPSTarget*/) + public unsafe bool Initialization(int* TotalPage, int* CurrentPage, int* OffSignal) { try { pptTotalPage = TotalPage; pptCurrentPage = CurrentPage; - //autoCloseWPS = autoCloseWPSTarget; + offSignal = OffSignal; return true; } @@ -99,37 +108,246 @@ public unsafe bool Initialization(int* TotalPage, int* CurrentPage/*, bool autoC } public string CheckCOM() { - string ret = "20250830a"; + string ret = "20260102a"; + return ret; + } + + // 过程函数 + private static void CleanUpLoopObjects(IBindCtx bindCtx, IMoniker moniker, object comObject) + { + if (comObject != null && Marshal.IsComObject(comObject)) Marshal.ReleaseComObject(comObject); + if (moniker != null) Marshal.ReleaseComObject(moniker); + if (bindCtx != null) Marshal.ReleaseComObject(bindCtx); + } + private void SafeRelease(object comObj) + { + if (comObj == null) return; + if (Marshal.IsComObject(comObj)) + { + try + { + Marshal.ReleaseComObject(comObj); + } + catch { } + } + } + private void SafeFinalRelease(object comObj) + { + if (comObj == null) return; + + if (Marshal.IsComObject(comObj)) + { + try + { + Marshal.FinalReleaseComObject(comObj); + } + catch { } + } + } + private void UnbindEvents() + { try { - Microsoft.Office.Interop.PowerPoint.Application pptTest = new Microsoft.Office.Interop.PowerPoint.Application(); - Marshal.ReleaseComObject(pptTest); + if (bindingEvents && pptApplication != null) + { + try + { + Microsoft.Office.Interop.PowerPoint.Application app = pptApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) + { + app.SlideShowNextSlide -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); + app.SlideShowBegin -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); + app.SlideShowEnd -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); + app.PresentationBeforeClose -= new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); + } + } + catch (Exception ex) + { + // 忽略 COM 对象已分离的错误 + Console.WriteLine($"Unbind Error: {ex.Message}"); + } + + bindingEvents = false; + forcePolling = false; + } + } + catch { } + + Console.WriteLine("UnbindEventsCalled"); + } + private unsafe void FullCleanup(bool final = false) + { + UnbindEvents(); + + Console.WriteLine("try CLEAN"); + + if (final) + { + SafeFinalRelease(pptSlideShowWindow); + SafeFinalRelease(pptActivePresentation); + SafeFinalRelease(pptApplication); + } + else + { + SafeRelease(pptSlideShowWindow); + SafeRelease(pptActivePresentation); + SafeRelease(pptApplication); + } - // TODO 需要测试对于没有安装 Powerpoint 设备,或是只有 WPS 的设备是否工作正常 + pptSlideShowWindow = null; + pptActivePresentation = null; + pptApplication = null; + + // 重置指针为 -1 (外部程序约定的结束标志) + try + { + *pptTotalPage = -1; + *pptCurrentPage = -1; + } + catch { } + + // 强制 GC,这对 WPS 的资源释放至关重要 + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Console.WriteLine("CLEAN!"); + } + + // 判断函数 + private static bool AreComObjectsEqual(object o1, object o2) + { + if (o1 == null || o2 == null) return false; + if (ReferenceEquals(o1, o2)) return true; // 优化:引用相等直接返回 + + IntPtr pUnk1 = IntPtr.Zero; + IntPtr pUnk2 = IntPtr.Zero; + try + { + pUnk1 = Marshal.GetIUnknownForObject(o1); + pUnk2 = Marshal.GetIUnknownForObject(o2); + return pUnk1 == pUnk2; + } + catch { return false; } + finally + { + if (pUnk1 != IntPtr.Zero) Marshal.Release(pUnk1); + if (pUnk2 != IntPtr.Zero) Marshal.Release(pUnk2); + } + } + private bool IsSlideShowWindowActive(object sswObj) + { + try + { + dynamic ssw = sswObj; + + // 1. 获取前台窗口 (用户当前正在操作的窗口) + IntPtr foregroundHwnd = GetForegroundWindow(); + if (foregroundHwnd == IntPtr.Zero) return false; + + // 获取前台窗口的 PID + uint fgPid; + GetWindowThreadProcessId(foregroundHwnd, out fgPid); + + // 2. 获取 COM App 对象的 PID + IntPtr sswHwnd = IntPtr.Zero; + try + { + sswHwnd = GetPptHwndFromSlideShowWindow(sswObj); + // sswHwnd = GetSlideShowWindowHwnd(ssw); + } + catch { return false; } + if (sswHwnd == IntPtr.Zero) return false; + + uint sswPid; + GetWindowThreadProcessId(sswHwnd, out sswPid); + + // 匹配 + if (fgPid == sswPid) return true; + + // WPS 跨进程判定 + try + { + using (Process fgProc = Process.GetProcessById((int)fgPid)) + using (Process appProc = Process.GetProcessById((int)sswPid)) + { + string fgName = fgProc.ProcessName.ToLower(); + string appName = appProc.ProcessName.ToLower(); + + if (fgName.StartsWith("wps") && appName.StartsWith("wpp")) + { + return true; + } + } + } + catch (Exception ex) + { + Console.WriteLine($" Process Name Check Failed: {ex.Message}"); + } + + return false; + } + catch + { + return false; + } + } + private static bool LooksLikePresentationFile(string displayName) + { + if (string.IsNullOrEmpty(displayName)) + return false; + + string[] PptLikeExtensions = new[] + { + ".pptx", ".pptm", ".ppt", + ".ppsx", ".ppsm", ".pps", + ".potx", ".potm", ".pot", + ".dps", ".dpt", + }; + + string lower = displayName.ToLowerInvariant(); + foreach (var ext in PptLikeExtensions) + { + if (lower.Contains(ext)) + return true; + } + return false; + } + private static bool IsValidSlideShowWindow(dynamic pptSlideShowWindow) + { + if (pptSlideShowWindow == null) return false; + + bool ret = false; + + try + { + int temp = pptSlideShowWindow.Active; + ret = true; } catch (Exception ex) { - ret += "\n" + ex.Message; + ret = false; + Console.WriteLine($"不是ssw {ex.Message}"); } return ret; } - // 外部引用函数 - [DllImport("user32.dll")] - private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); - - // 事件查询函数 - private unsafe void SlideShowChange(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn) + // 事件函数 + private unsafe void SlideShowChange(object WnObj) { + Console.WriteLine("Change1"); + updateTime = DateTime.Now; try { - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + // 假设全局变量 pptSlideShowWindow 已经是 dynamic 类型,这里的调用会自动进行后期绑定 + *pptCurrentPage = GetCurrentSlideIndex(pptSlideShowWindow); - if (pptActWindow.View.Slide.SlideIndex >= pptActDoc.Slides.Count) polling = 1; + if (GetCurrentSlideIndex(pptSlideShowWindow) >= GetTotalSlideIndex(pptActivePresentation)) polling = 1; else polling = 0; } catch @@ -137,157 +355,585 @@ private unsafe void SlideShowChange(Microsoft.Office.Interop.PowerPoint.SlideSho *pptCurrentPage = -1; polling = 1; } - } - private unsafe void SlideShowBegin(Microsoft.Office.Interop.PowerPoint.SlideShowWindow Wn) + Console.WriteLine("Change2"); + } + private unsafe void SlideShowBegin(object WnObj) { + Console.WriteLine("Begin1"); + updateTime = DateTime.Now; - pptActWindow = Wn; + + // 【修改】直接赋值给 dynamic 类型的全局变量 + pptSlideShowWindow = WnObj; try { - if (pptActWindow.View.Slide.SlideIndex >= pptActDoc.Slides.Count) polling = 1; + if (GetCurrentSlideIndex(pptSlideShowWindow) >= GetTotalSlideIndex(pptActivePresentation)) polling = 1; else polling = 0; } catch { // Begin 事件定在结束放映页的小丑情况(虽然不可能有这种情况) polling = 1; + Console.WriteLine("Begin4"); } // 获取页数 try { - *pptTotalPage = pptActDoc.Slides.Count; - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + *pptTotalPage = GetTotalSlideIndex(pptActivePresentation); + *pptCurrentPage = GetCurrentSlideIndex(pptSlideShowWindow); } - catch { } + catch + { + Console.WriteLine("Begin3"); + } + Console.WriteLine("Begin2"); } - - private unsafe void SlideShowShowEnd(Microsoft.Office.Interop.PowerPoint.Presentation Wn) + private unsafe void SlideShowShowEnd(object WnObj) { + Console.WriteLine("END1"); + updateTime = DateTime.Now; *pptCurrentPage = -1; *pptTotalPage = -1; - } - private void PresentationBeforeClose(Microsoft.Office.Interop.PowerPoint.Presentation Wn, ref bool cancel) + Console.WriteLine("END2"); + } + private void PresentationBeforeClose(object WnObj, ref bool cancel) { - if (bindingEvents && Wn == pptActDoc) - { - pptApp.SlideShowNextSlide -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); - pptApp.SlideShowBegin -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); - pptApp.SlideShowEnd -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); - pptApp.PresentationBeforeClose -= new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); - bindingEvents = false; - } - // 对于延迟未关闭的 WPP,先记录进程 ID,待所有结束事件处理完毕后强制关闭 - //if (autoCloseWPS && Wn.Application.Path.Contains("Kingsoft\\WPS Office\\") && Wn.Application.Presentations.Count <= 1) - //{ - // uint processId; - // GetWindowThreadProcessId((IntPtr)Wn.Application.HWND, out processId); - // wpsProcess = Process.GetProcessById((int)processId); - // hasWpsProcessID = true; - //} + Console.WriteLine("PBCalled1"); + + dynamic Wn = WnObj; + + try + { + if (bindingEvents && pptApplication != null) + { + try + { + Microsoft.Office.Interop.PowerPoint.Application app = pptApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) + { + app.SlideShowNextSlide -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); + app.SlideShowBegin -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); + app.SlideShowEnd -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); + app.PresentationBeforeClose -= new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); + } + } + catch (Exception ex) + { + // 忽略 COM 对象已分离的错误 + Console.WriteLine($"Unbind Error: {ex.Message}"); + } + + bindingEvents = false; + forcePolling = false; + } + } + catch { } + cancel = false; + + Console.WriteLine("PBCalled2"); } - // 判断是否有 Ppt 文件被打开(并注册事件) - public unsafe int IsPptOpen() + // 获取函数 + private object GetAnyActivePowerPoint(object targetApp, out int bestPriority, out int targetPriority) { - int ret = 0; - bindingEvents = false; - //hasWpsProcessID = false; + IRunningObjectTable rot = null; + IEnumMoniker enumMoniker = null; + + object bestApp = null; + + bestPriority = 0; + targetPriority = 0; + int highestPriority = 0; + + System.Collections.Generic.List foundAppObjects = new System.Collections.Generic.List(); - // 通用尝试,获取 Active 的 Application 并检测是否正确 try { - // 获取活动的 Application 示例 - pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); + int hr = GetRunningObjectTable(0, out rot); + if (hr != 0 || rot == null) return null; + + rot.EnumRunning(out enumMoniker); + if (enumMoniker == null) return null; + + IMoniker[] moniker = new IMoniker[1]; + IntPtr fetched = IntPtr.Zero; + + while (enumMoniker.Next(1, moniker, fetched) == 0) + { + IBindCtx bindCtx = null; + object comObject = null; + + dynamic candidateApp = null; + string displayName = "Unknown"; + + dynamic activePres = null; + dynamic ssWindow = null; + + // 标记当前 candidateApp 是否需要“保活”供后续去重对比 + bool keepAlive = false; + + try + { + CreateBindCtx(0, out bindCtx); + moniker[0].GetDisplayName(bindCtx, null, out displayName); + + if (LooksLikePresentationFile(displayName) || displayName == "!{91493441-5A91-11CF-8700-00AA0060263B}") + { + rot.GetObject(moniker[0], out comObject); + if (comObject != null) + { + // 尝试通过 Presentation 对象获取 Application + try + { + // 使用反射获取 Application 属性 + object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null); + candidateApp = appObj; + } + catch { } + } + else { } + } + + // COM 对象去重检查 + bool isDuplicate = false; + if (candidateApp != null) + { + foreach (var processedApp in foundAppObjects) + { + if (AreComObjectsEqual((object)candidateApp, processedApp)) + { + isDuplicate = true; + + Console.WriteLine(" -> [Deduplication] Skip: This COM instance was already scanned."); + break; + } + } + + if (!isDuplicate) + { + // 如果不是重复项,加入列表并标记保活 + // 注意:必须保持此引用有效,后续循环才能进行对比 + foundAppObjects.Add(candidateApp); + keepAlive = true; + } + } + + if (candidateApp != null && !isDuplicate) + { + int currentPriority = 0; + bool isTarget = false; + + // 1. 检查是否是 Target + if (targetApp != null && AreComObjectsEqual((object)candidateApp, targetApp)) + { + isTarget = true; + } + + // 2. 计算优先级 + try + { + // 尝试获取 ActivePresentation + try + { + activePres = candidateApp.ActivePresentation; + } + catch { } + + if (activePres != null) + { + currentPriority = 1; + + // 检查 SlideShowWindows + try + { + ssWindow = activePres.SlideShowWindow; + } + catch + { + } + + if (ssWindow != null) + { + currentPriority = 2; + + try + { + // 判定 Active + // MS PPT 返回 int (-1), WPS 可能返回 bool (true) + bool isActive = false; + try + { + object val = ssWindow.Active; + if (val is int && (int)val == -1) isActive = true; // MsoTriState.msoTrue + else if (val is bool && (bool)val == true) isActive = true; + } + catch { } + + if (isActive) + { + currentPriority = 3; + } + else + { + // 针对 WPP 的 Active 在非全屏播放下不一定生效的情况 + if (IsSlideShowWindowActive(ssWindow)) + { + Console.WriteLine(" [Fix] App process has focus via PID check. Upgrading priority to 3."); + + currentPriority = 3; + } + } + } + catch { } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Check Priority Error: {ex.Message}"); + } + + // 3. 更新 Target Priority + if (isTarget) + { + targetPriority = currentPriority; + } + + // 4. 更新 Best App + if (currentPriority > 0) + { + if (currentPriority > highestPriority) + { + highestPriority = currentPriority; + + SafeRelease(bestApp); + + // 这里不需要转换,直接赋值 object + bestApp = candidateApp; + candidateApp = null; // 转移所有权,candidateApp 置空防止后续被错误释放 + + // 注意:虽然 candidateApp 变量置空了,但该对象引用仍存在于 foundAppObjects 中 + } + } + + Console.WriteLine($"{displayName}: {currentPriority}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Loop Error: {ex.Message}"); + } + finally + { + SafeRelease(ssWindow); + SafeRelease(activePres); + + // 如果是为了去重而暂存在列表中的新对象,不要在这里释放 + // 如果是重复对象(keepAlive=false),或者出现异常没加入列表,则正常释放 + // 如果 candidateApp 已经转移给 bestApp,它已经是 null,SafeRelease 安全 + if (!keepAlive) + { + SafeRelease(candidateApp); + } + + CleanUpLoopObjects(bindCtx, moniker[0], comObject); + } + } + + bestPriority = highestPriority; + } + catch (Exception ex) + { + Console.WriteLine($"ROT Scan Critical Error: {ex.Message}"); + } + finally + { + // 清理去重列表中的 COM 对象 + if (foundAppObjects != null) + { + foreach (var cachedApp in foundAppObjects) + { + // 关键:如果这个对象最终成为了 bestApp,千万不能释放,因为我们要返回它 + if (bestApp != null && ReferenceEquals(cachedApp, bestApp)) + continue; + + SafeRelease(cachedApp); + } + foundAppObjects.Clear(); + } - // 获取 PPT 文档实例个数(如果为 0 则是没有打开文件的 Application 实例,或是游离状态的 WPP) - ret = pptApp.Presentations.Count; + if (enumMoniker != null) Marshal.ReleaseComObject(enumMoniker); + if (rot != null) Marshal.ReleaseComObject(rot); } - catch { } - // 锁定 Application 并执行后续操作 - if (ret > 0) - { - try - { - pptActDoc = pptApp.ActivePresentation; - updateTime = DateTime.Now; + return bestApp; + } + public unsafe int PptComService() + { + Console.WriteLine("PPT Monitor ReStarted"); + + // 初始化 + bindingEvents = false; + *pptCurrentPage = -1; + *pptTotalPage = -1; + polling = 0; + + int tempTotalPage = -1; + + int bestPriority = 0; + int targetPriority = 0; + + try + { + while (true) + { + // 动态绑定/切换逻辑 + { + object bestApp = GetAnyActivePowerPoint(pptApplication, out bestPriority, out targetPriority); + bool needRebind = false; + + Console.WriteLine($"now: {targetPriority}, best: {bestPriority}"); + + if (pptApplication == null && bestApp != null) needRebind = true; + else if (pptApplication != null && bestApp != null && bestPriority > targetPriority) + { + // 完全不同 + if (!AreComObjectsEqual((object)pptApplication, bestApp)) + { + needRebind = true; + } + } + + if (needRebind == true) + { + bool wait = (pptApplication != null); + FullCleanup(); + + if (bestApp != null) + { + if (wait) Thread.Sleep(1000); + + pptApplication = bestApp; + + try + { + // dynamic 后期绑定 + pptActivePresentation = pptApplication.ActivePresentation; + updateTime = DateTime.Now; + + try + { + pptSlideShowWindow = pptActivePresentation.SlideShowWindow; + *pptTotalPage = tempTotalPage = GetTotalSlideIndex(pptActivePresentation); + } + catch + { + *pptTotalPage = tempTotalPage = -1; + } + + if (tempTotalPage == -1) + { + *pptCurrentPage = -1; + polling = 0; + } + else + { + try + { + *pptCurrentPage = GetCurrentSlideIndex(pptSlideShowWindow); + + if (GetCurrentSlideIndex(pptSlideShowWindow) >= GetTotalSlideIndex(pptActivePresentation)) polling = 1; + else polling = 0; + } + catch + { + *pptCurrentPage = -1; + polling = 1; + } + } + + try + { + // 关键修改:这里不要直接用 pptApplication +=,而是先强转 + Microsoft.Office.Interop.PowerPoint.Application app = pptApplication as Microsoft.Office.Interop.PowerPoint.Application; + + if (app != null) + { + app.SlideShowNextSlide += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); + app.SlideShowBegin += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); + app.SlideShowEnd += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); + + try + { + app.PresentationBeforeClose += new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); + } + catch + { + Console.WriteLine($"无法注册事件 2!"); + } + + bindingEvents = true; + forcePolling = false; + + Console.WriteLine($"事件注册成功!"); + } + else + { + bindingEvents = false; + forcePolling = true; + + Console.WriteLine($"转换 Application 接口失败,无法注册事件"); + } + } + catch (Exception ex) + { + bindingEvents = false; + forcePolling = true; + + Console.WriteLine($"无法注册事件 1! {ex.Message}"); + } + + bindingEvents = false; + forcePolling = true; - int tempTotalPage; - try - { - pptActWindow = pptActDoc.SlideShowWindow; - *pptTotalPage = tempTotalPage = pptActDoc.Slides.Count; - } - catch - { - *pptTotalPage = tempTotalPage = -1; + Console.WriteLine($"成功绑定! {pptApplication.Name}"); + } + catch + { + FullCleanup(); + } + } + } + else + { + if (bestApp != null && (pptApplication == null || !AreComObjectsEqual((object)pptApplication, bestApp))) + { + SafeRelease(bestApp); + bestApp = null; + } + } } - if (tempTotalPage == -1) - { - *pptCurrentPage = -1; - polling = 0; - } - else + // 状态监测与轮询 + if (pptApplication != null && pptActivePresentation != null) { + // 检查是否同进程切换文档 (dynamic 比较引用) + // 注意:如果报错 RuntimeBinderException 说明对象可能已失效 + dynamic activePersentation = null; + dynamic slideShowWindow = null; + try { - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + activePersentation = pptApplication.ActivePresentation; - if (pptActWindow.View.Slide.SlideIndex >= pptActDoc.Slides.Count) polling = 1; - else polling = 0; + if (!AreComObjectsEqual((object)pptActivePresentation, (object)activePersentation)) + { + Console.WriteLine("End in 1"); + break; + } } - catch + catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) { - *pptCurrentPage = -1; - polling = 1; + Console.WriteLine($"PowerPoint 忙,稍后重试"); + } + catch (Exception ex) + { + Console.WriteLine($"End in 2 {ex.ToString()}"); + break; + } + finally + { + SafeRelease(activePersentation); + activePersentation = null; } - } - - // 绑定事件 - bindingEvents = true; - pptApp.SlideShowNextSlide += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); - pptApp.SlideShowBegin += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); - pptApp.SlideShowEnd += new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); - pptApp.PresentationBeforeClose += new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); - try - { - while (true) + // ---------- + // 检测是否处于放映模式 + bool isSlideShowActive = false; + try { - if (pptActDoc != pptApp.ActivePresentation) break; - if (polling != 0) + activePersentation = pptApplication.ActivePresentation; + + // 检查 SlideShowWindows 集合 + if (activePersentation != null && GetSlideShowWindowsCount(pptApplication) > 0) { - try - { - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; - polling = 2; - } - catch + isSlideShowActive = true; + + slideShowWindow = activePersentation.SlideShowWindow; + if (pptSlideShowWindow == null || (pptSlideShowWindow != null && !IsValidSlideShowWindow(pptSlideShowWindow))) { - *pptCurrentPage = -1; + if (!AreComObjectsEqual((object)pptSlideShowWindow, (object)slideShowWindow)) + { + SafeRelease(pptSlideShowWindow); + + pptSlideShowWindow = slideShowWindow; + + Console.WriteLine($"发现窗口,成功设置 slideshowwindow"); + } + else + { + Console.WriteLine($"发现窗口,但无须设置 1"); + } } } + } + catch (COMException ex) when ((uint)ex.ErrorCode == 0x8001010A) + { + Console.WriteLine($"PowerPoint 忙,稍后重试"); + } + catch (Exception ex) + { + Console.WriteLine($"发现窗口失败 1: {ex.ToString()}"); + + // 如果这里报错,说明 App 可能挂了,或者 WPS 上下文丢失 + // 标记为非放映状态,后续逻辑会处理 + } + finally + { + SafeRelease(activePersentation); + activePersentation = null; + + if (!AreComObjectsEqual((object)pptSlideShowWindow, (object)slideShowWindow)) + { + SafeRelease(slideShowWindow); + slideShowWindow = null; + + Console.WriteLine($"slideShowWindow 被清理"); + } + } - // 计时轮询(超过3秒不刷新就轮询一次) - if ((DateTime.Now - updateTime).TotalMilliseconds > 3000) + if (isSlideShowActive) + { + if ((DateTime.Now - updateTime).TotalMilliseconds > 3000 || forcePolling) { + Console.WriteLine($"轮询"); + try { + slideShowWindow = pptActivePresentation.SlideShowWindow; + // 获取当前播放的PPT幻灯片窗口对象(保证当前处于放映状态) - if (pptActDoc.SlideShowWindow != null) *pptTotalPage = tempTotalPage = pptActDoc.Slides.Count; + if (slideShowWindow != null) *pptTotalPage = tempTotalPage = GetTotalSlideIndex(pptActivePresentation); else *pptTotalPage = tempTotalPage = -1; } - catch + catch (Exception ex) { *pptTotalPage = tempTotalPage = -1; + + Console.WriteLine($"获取总页数失败 {ex.Message}"); + } + finally + { + SafeRelease(slideShowWindow); + slideShowWindow = null; } if (tempTotalPage == -1) @@ -299,204 +945,363 @@ public unsafe int IsPptOpen() { try { - *pptCurrentPage = pptActWindow.View.Slide.SlideIndex; + int currentPage = GetCurrentSlideIndex(pptSlideShowWindow); + *pptCurrentPage = currentPage; - if (pptActWindow.View.Slide.SlideIndex >= pptActDoc.Slides.Count) polling = 1; + if (currentPage >= GetTotalSlideIndex(pptActivePresentation)) polling = 1; else polling = 0; } - catch + catch (Exception ex) { *pptCurrentPage = -1; polling = 1; + + Console.WriteLine($"获取当前页数失败 {ex.ToString()}"); } } updateTime = DateTime.Now; } - - Thread.Sleep(500); + if (polling != 0) + { + try + { + *pptCurrentPage = GetCurrentSlideIndex(pptSlideShowWindow); + polling = 2; + } + catch + { + *pptCurrentPage = -1; + } + } } - } - catch { } + else + { + *pptCurrentPage = -1; + *pptTotalPage = -1; - // 解绑事件 - if (bindingEvents) - { - pptApp.SlideShowNextSlide -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowNextSlideEventHandler(SlideShowChange); - pptApp.SlideShowBegin -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowBeginEventHandler(SlideShowBegin); - pptApp.SlideShowEnd -= new Microsoft.Office.Interop.PowerPoint.EApplication_SlideShowEndEventHandler(SlideShowShowEnd); - pptApp.PresentationBeforeClose -= new Microsoft.Office.Interop.PowerPoint.EApplication_PresentationBeforeCloseEventHandler(PresentationBeforeClose); - bindingEvents = false; - } - // 关闭未正确关闭的 WPP 进程 - //if (hasWpsProcessID == true && !wpsProcess.HasExited) - //{ - // wpsProcess.Kill(); - // hasWpsProcessID = false; - //} - - // 释放 COM - if (pptActWindow != null) - { - Marshal.ReleaseComObject(pptActWindow); - pptActWindow = null; + // 等待用户再次点击“开始放映”时能立即响应 SlideShowBegin 事件 + } } - if (pptActDoc != null) + else { - Marshal.ReleaseComObject(pptActDoc); - pptActDoc = null; + // 没有绑定对象 + *pptCurrentPage = -1; + *pptTotalPage = -1; + + break; } - if (pptApp != null) + + // 关闭信号检测 + if (*offSignal != 0) { - Marshal.ReleaseComObject(pptApp); - pptApp = null; + Console.WriteLine("offSignal Close"); + break; } - GC.Collect(); - GC.WaitForPendingFinalizers(); + + Thread.Sleep(500); } - catch { } } - // else:则找不到 Application 示例,或 Application 示例均不符合条件。 + catch (Exception ex) + { + Console.WriteLine($"Fail 991"); + Console.WriteLine($"异常信息: {ex.Message}"); + } + finally + { + FullCleanup(true); + } - *pptCurrentPage = -1; *pptTotalPage = -1; - return ret; + Console.WriteLine("PPT Monitor End"); + + return 0; } // 信息获取函数 - public string slideNameIndex() + public string SlideNameIndex() { string slidesName = ""; try { // 获取正在播放的PPT的名称 - slidesName += pptActDoc.FullName + "\n"; - slidesName += pptApp.Caption; + slidesName += pptActivePresentation.FullName + "\n"; + slidesName += pptApplication.Caption; } catch { // 获取PPT信息失败 + slidesName = ""; } return slidesName; } - public IntPtr GetPptHwnd() { - IntPtr hWnd = IntPtr.Zero; + IntPtr ret = IntPtr.Zero; + + // 尝试默认方法 + ret = GetPptHwndFromSlideShowWindow(pptSlideShowWindow); + + if (ret == IntPtr.Zero) + { + // 尝试备用方法 + ret = GetPptHwndWin32(pptActivePresentation.FullName, pptApplication.Name); + } + + return ret; + } + private IntPtr GetPptHwndFromSlideShowWindow(object pptSlideShowWindowObj) + { + IntPtr hwnd = IntPtr.Zero; + if (pptSlideShowWindowObj == null) return IntPtr.Zero; + + try + { + Microsoft.Office.Interop.PowerPoint.SlideShowWindow slideWindow = (Microsoft.Office.Interop.PowerPoint.SlideShowWindow)pptSlideShowWindowObj; + + int hwndVal = slideWindow.HWND; + + // 成功返回 + hwnd = new IntPtr(hwndVal); + } + catch { } + + if (hwnd == IntPtr.Zero) Console.WriteLine("啥都没有 GetPptHwndFromSSW"); + else Console.WriteLine("Got Hwnd GetPptHwndFromSSW"); + + return hwnd; + } + private IntPtr GetPptHwndWin32(string presFullName, string appName) + { + // 全局 try-catch 保证异常安全,失败一律返回 Zero try { - // 获取正在播放的PPT应用程序对象 - pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); - // 获取当前播放的PPT文档对象 - pptActDoc = pptApp.ActivePresentation; - // 获取当前播放的PPT幻灯片窗口对象 - pptActWindow = pptActDoc.SlideShowWindow; + // ------------------------------------------------- + // 步骤 A: 基础参数校验 + // ------------------------------------------------- + if (string.IsNullOrWhiteSpace(presFullName) || string.IsNullOrWhiteSpace(appName)) + { + return IntPtr.Zero; + } + + // ------------------------------------------------- + // 步骤 B: 提取关键信息 (应用类型 & 文件名) + // ------------------------------------------------- + string targetAppKeyword; + // 使用 OrdinalIgnoreCase 忽略大小写进行匹配,更安全 + if (appName.IndexOf("WPS", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "WPS"; + } + else if (appName.IndexOf("PowerPoint", StringComparison.OrdinalIgnoreCase) >= 0) + { + targetAppKeyword = "PowerPoint"; + } + else + { + // 既不是 WPS 也不是 PowerPoint,视为不支持 + return IntPtr.Zero; + } + + // 从路径中安全提取文件名(包含扩展名),如 "myppt.pptx" + string targetFileName = Path.GetFileName(presFullName); + if (string.IsNullOrWhiteSpace(targetFileName)) + { + return IntPtr.Zero; + } + + // ------------------------------------------------- + // 步骤 C: 枚举窗口并查找匹配项 + // ------------------------------------------------- + // 使用 List 暂存所有符合条件的句柄,用于后续判断是否唯一 + List candidates = new List(); + + // 调用 EnumWindows,使用 Lambda 表达式直接嵌入回调逻辑 + EnumWindows((hWnd, lParam) => + { + try + { + // [安全过滤] 1. 忽略不可见窗口 (避免匹配到后台挂起的进程或隐藏窗口) + if (!IsWindowVisible(hWnd)) return true; + + // [安全获取] 2. 获取窗口标题长度 + int length = GetWindowTextLength(hWnd); + if (length == 0) return true; + + // [安全获取] 3. 获取窗口标题文本 + StringBuilder sb = new StringBuilder(length + 1); + GetWindowText(hWnd, sb, sb.Capacity); + string title = sb.ToString(); - // 获取PPT窗口句柄 - hWnd = new IntPtr(pptActWindow.HWND); + if (string.IsNullOrWhiteSpace(title)) return true; + + // [核心匹配] 4. 判断标题是否同时包含 "文件名" 和 "应用关键字" + bool hasFileName = title.IndexOf(targetFileName, StringComparison.OrdinalIgnoreCase) >= 0; + bool hasAppKey = title.IndexOf(targetAppKeyword, StringComparison.OrdinalIgnoreCase) >= 0; + + if (hasFileName && hasAppKey) + { + candidates.Add(hWnd); + } + + // 继续枚举其他窗口 + return true; + } + catch + { + // 回调内部容错,忽略单个窗口获取信息的错误,继续枚举 + return true; + } + }, IntPtr.Zero); + + // ------------------------------------------------- + // 步骤 D: 结果判定 + // ------------------------------------------------- + // 只有当匹配到的窗口数量 唯一 (Count == 1) 时才返回句柄 + // 0 个表示没找到,>1 个表示有歧义(无法确定是哪一个),均视为失败 + if (candidates.Count == 1) + { + Console.WriteLine($"Got Hwnd GetPptHwndWin32 {candidates[0]}"); + return candidates[0]; + } + + return IntPtr.Zero; } catch { + // 发生任何不可预知的异常(如Path解析错误等),返回安全值 + return IntPtr.Zero; } - - return hWnd; } - // 未完善列表 - /* - public int GetSlideShowViewAdvanceMode() + private int GetCurrentSlideIndex(dynamic slideShowWindow) { - int AdvanceMode = -1; + dynamic view = null; + dynamic slide = null; try { - if (pptActDoc.SlideShowSettings.AdvanceMode == PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings) AdvanceMode = 1; - else AdvanceMode = 0; + view = slideShowWindow.View; + slide = view.Slide; + return (int)slide.SlideIndex; } - catch + finally { - } + SafeRelease(slide); + SafeRelease(view); - return AdvanceMode; + slide = null; + view = null; + } } - - public int SetSlideShowViewAdvanceMode(int AdvanceMode) + private int GetTotalSlideIndex(dynamic presentation) { + dynamic slides = null; try { - if (AdvanceMode == 1) pptActDoc.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings; + slides = presentation.Slides; + return (int)slides.Count; } - catch + finally { + SafeRelease(slides); + slides = null; } + } + private int GetSlideShowWindowsCount(dynamic application) + { + dynamic slideShowWindows = null; - return AdvanceMode; + try + { + slideShowWindows = application.SlideShowWindows; + return (int)slideShowWindows.Count; + } + finally + { + SafeRelease(slideShowWindows); + slideShowWindows = null; + } } - */ // 操控函数 - - public unsafe void NextSlideShow(int check) + public void NextSlideShow(bool check) { + // 如果为末页情况,则需要传入 check = true 才能继续下一页 + // TODO:还需要考虑没有结束放映页的情况 + // TODO:对于没有结束放映页的 PPT,我们还没有办法判定并拦截(其实是不好写 hhh) + + bool endPage = false; + try + { + var currentPage = GetCurrentSlideIndex(pptSlideShowWindow); + } + catch + { + Console.WriteLine("enter End page"); + endPage = true; + } + try { - int temp_SlideIndex = *pptCurrentPage; - if (temp_SlideIndex != check && check != -1) return; + if (endPage == true && check != true) + { + return; + } // 下一页 if (polling != 0) { if (polling == 2) { - pptActWindow.View.Next(); + pptSlideShowWindow.View.Next(); } else if (polling == 1) { int currentPageTemp = -1; try { - currentPageTemp = pptActWindow.View.Slide.SlideIndex; + currentPageTemp = GetCurrentSlideIndex(pptSlideShowWindow); } catch { currentPageTemp = -1; } + if (currentPageTemp != -1) { - pptActWindow.View.Next(); + pptSlideShowWindow.View.Next(); } } polling = 1; } else { - pptActWindow.View.Next(); + pptSlideShowWindow.View.Next(); } } catch { } } - - public unsafe void PreviousSlideShow() + public void PreviousSlideShow() { try { // 上一页 - pptActWindow.View.Previous(); + pptSlideShowWindow.View.Previous(); } catch { } return; } - public void EndSlideShow() { try { // 结束放映 - pptActWindow.View.Exit(); + pptSlideShowWindow.View.Exit(); } catch { @@ -506,12 +1311,145 @@ public void ViewSlideShow() { try { // 打开 ppt 浏览视图 - pptActWindow.SlideNavigation.Visible = true; + pptSlideShowWindow.SlideNavigation.Visible = true; } catch { } return; } + public void ActivateSildeShowWindow() + { + if (pptSlideShowWindow == null) return; + + try + { + pptSlideShowWindow.Activate(); + + Console.WriteLine("ActivateSildeShowWindow called"); + } + catch + { + } + } + + // 外部引用函数 + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + [DllImport("ole32.dll")] + private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot); + [DllImport("ole32.dll")] + private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetWindowTextLength(IntPtr hWnd); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + + // Test + private void DiagnoseComStatus() + { + Console.WriteLine("============== COM 对象存活诊断开始 =============="); + + // 1. 检查 Application + try + { + if (pptApplication == null) + { + Console.WriteLine("❌ pptApplication: NULL (空引用)"); + } + else + { + // 尝试访问一个简单属性,如 Caption 或 Version + string temp = pptApplication.Caption; + Console.WriteLine("✅ pptApplication: ALIVE (存活)"); + } + } + catch (Exception ex) + { + Console.WriteLine($"❌ pptApplication: DEAD (已失效) -> {ex.GetType().Name}"); + } + + // 2. 检查 ActivePresentation + try + { + if (pptActivePresentation == null) + { + Console.WriteLine("❌ pptActivePresentation: NULL (空引用)"); + } + else + { + // 尝试访问 Name + string temp = pptActivePresentation.Name; + Console.WriteLine("✅ pptActivePresentation: ALIVE (存活)"); + } + } + catch (Exception ex) + { + Console.WriteLine($"❌ pptActivePresentation: DEAD (已失效) -> {ex.GetType().Name}"); + } + + // 3. 检查 SlideShowWindow + try + { + if (pptSlideShowWindow == null) + { + Console.WriteLine("⚠️ pptSlideShowWindow: NULL (当前无窗口)"); + } + else + { + // 尝试访问 Height + int temp = pptSlideShowWindow.Active; + Console.WriteLine("✅ pptSlideShowWindow: ALIVE (存活)"); + } + } + catch (Exception ex) + { + Console.WriteLine($"❌ pptSlideShowWindow: DEAD (已失效) -> {ex.GetType().Name}"); + } + + Console.WriteLine("=================================================="); + } + } +} + +// 未完善列表 +/* +public int GetSlideShowViewAdvanceMode() +{ + int AdvanceMode = -1; + + try + { + if (pptActivePresentation.SlideShowSettings.AdvanceMode == PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings) AdvanceMode = 1; + else AdvanceMode = 0; + } + catch + { } -} \ No newline at end of file + + return AdvanceMode; +} + +public int SetSlideShowViewAdvanceMode(int AdvanceMode) +{ + try + { + if (AdvanceMode == 1) pptActivePresentation.SlideShowSettings.AdvanceMode = PpSlideShowAdvanceMode.ppSlideShowUseSlideTimings; + } + catch + { + } + + return AdvanceMode; +} +*/ \ No newline at end of file diff --git a/PptCOM/PptCOM.csproj b/PptCOM/PptCOM.csproj index 08898266..91685c4b 100644 --- a/PptCOM/PptCOM.csproj +++ b/PptCOM/PptCOM.csproj @@ -39,6 +39,7 @@ + ..\packages\Microsoft.Office.Interop.PowerPoint.15.0.4420.1018\lib\net20\Microsoft.Office.Interop.PowerPoint.dll True @@ -52,26 +53,6 @@ - - - {2DF8D04C-5BFA-101B-BDE5-00AA0044DE52} - 2 - 8 - 0 - primary - False - True - - - {00020430-0000-0000-C000-000000000046} - 2 - 0 - 0 - primary - False - True - - $(SolutionDir)智绘教\ diff --git "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiDef.h" "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiDef.h" index 24ed0e01..3384f370 100644 --- "a/\346\231\272\347\273\230\346\225\231/HiEasyX/HiDef.h" +++ "b/\346\231\272\347\273\230\346\225\231/HiEasyX/HiDef.h" @@ -8,10 +8,10 @@ #define _HIEASYX_VER_STR_ _T("Ver 0.4.1") // 使用原生 EasyX 窗口(默认为 HiWindow) -//#define _NATIVE_EASYX_ +// #define _NATIVE_EASYX_ // 取消 Release 模式的程序启动动画 #define _NO_START_ANIMATION_ // 取消系统控件的现代样式(仅在 MSVC 编译器下可以设置) -//#define _NO_MORDEN_SYSCTRL_ \ No newline at end of file +//#define _NO_MORDEN_SYSCTRL_ diff --git "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" index f1e4b361..f581c232 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" @@ -717,7 +717,7 @@ bool PptComWriteSetting() updateVal["BottomSideMiddleWidgetScale"] = Json::Value(pptComSetlist.bottomSideMiddleWidgetScale); updateVal["MiddleSideBothWidgetScale"] = Json::Value(pptComSetlist.middleSideBothWidgetScale); - //updateVal["AutoKillWpsProcess"] = Json::Value(pptComSetlist.autoKillWpsProcess); + // updateVal["AutoKillWpsProcess"] = Json::Value(pptComSetlist.autoKillWpsProcess); } HANDLE fileHandle = NULL; @@ -1075,4 +1075,4 @@ bool SetMemory() UnOccupyFile(&fileHandle); return true; -} \ No newline at end of file +} diff --git "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" index 0adffbc8..b4d7e41c 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" @@ -64,7 +64,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) bool checkEndShowIsChecking = CheckEndShow.isChecking; // PPT模式:按键反馈 - if (ppt_show != NULL && !checkEndShowIsChecking) + if (PptInfoState.TotalPage != -1 && !checkEndShowIsChecking) { // 检查按下的键 switch (pKeyInfo->vkCode) @@ -187,7 +187,7 @@ LRESULT CALLBACK DrawpadHookCallback(int nCode, WPARAM wParam, LPARAM lParam) case VK_ESCAPE: // 退出 { // PPT 模式下,拦截按键并进行转译:支持长安翻页 - if (ppt_show != NULL && !checkEndShowIsChecking) + if (PptInfoState.TotalPage != -1 && !checkEndShowIsChecking) { return 1; } @@ -269,7 +269,7 @@ void KeyboardInteraction() if (vkcode == VK_UP || vkcode == VK_LEFT || vkcode == VK_PRIOR || vkcode == VK_BACK) { // 上一页 - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); @@ -301,7 +301,7 @@ void KeyboardInteraction() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); @@ -439,6 +439,7 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l flags |= (0x00010000); return (LRESULT)flags; } + case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) @@ -917,14 +918,14 @@ void MultiFingerDrawing(LONG pid, TouchMode initialMode, StateModeClass stateInf if (setlist.paintDevice == 1) { // 鼠标和手写笔 - if (speed <= 30) trubbersize = max(25, speed * 2.33 + 2.33) * drawingScale; - else trubbersize = min(200, speed + 30) * drawingScale; + if (speed <= 30) trubbersize = max(25.0, speed * 2.33 + 2.33) * drawingScale; + else trubbersize = min(200.0, speed + 30) * drawingScale; } else { // 触摸设备 - if (speed <= 20) trubbersize = max(25, speed * 2.33 + 13.33) * drawingScale; - else trubbersize = min(200, 3 * speed) * drawingScale; + if (speed <= 20) trubbersize = max(25.0, speed * 2.33 + 13.33) * drawingScale; + else trubbersize = min(200.0, 3.0 * speed) * drawingScale; } } else rubbersize = setlist.eraserSetting.eraserSize; @@ -2216,4 +2217,4 @@ int drawpad_main() } threadStatus[L"drawpad_main"] = false; return 0; -} \ No newline at end of file +} diff --git "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" index 8bd954ef..7962aeb5 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" @@ -202,7 +202,7 @@ LRESULT CALLBACK FloatingHookCallback(int nCode, WPARAM wParam, LPARAM lParam) else if (wParam == WM_RBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_RBUTTON, true); else if (wParam == WM_RBUTTONUP) IdtInputs::SetKeyBoardDown(VK_RBUTTON, false); - if (wParam == WM_MOUSEWHEEL && stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && !penetrate.select && ppt_show != NULL) + if (wParam == WM_MOUSEWHEEL && stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && !penetrate.select && PptInfoState.TotalPage != -1) { MSLLHOOKSTRUCT* pMouseStruct = (MSLLHOOKSTRUCT*)lParam; @@ -5307,7 +5307,7 @@ void DrawScreen() } if ((int)state == 1) { - if (ppt_show == NULL && stateMode.StateModeSelect == StateModeSelectEnum::IdtSelection) + if (PptInfoState.TotalPage == -1 && stateMode.StateModeSelect == StateModeSelectEnum::IdtSelection) { if (setlist.SkinMode == 1 || setlist.SkinMode == 2) hiex::EasyX_Gdiplus_FillRoundRect((float)floating_windows.width - 96, (float)floating_windows.height - 256 + 44, 96, 51, 25, 25, RGB(150, 150, 150), BackgroundColorMode == 0 ? RGB(255, 255, 255) : RGB(30, 33, 41), 2, false, SmoothingModeHighQuality, &background); else if (setlist.SkinMode == 3) @@ -5655,7 +5655,7 @@ void MouseInteraction() } } // 窗口定格 - if (ppt_show == NULL && IsInRect(m.x, m.y, { floating_windows.width - 96 + 4, floating_windows.height - 256 + 50, floating_windows.width - 96 + 4 + 88, floating_windows.height - 256 + 50 + 40 })) + if (PptInfoState.TotalPage == -1 && IsInRect(m.x, m.y, { floating_windows.width - 96 + 4, floating_windows.height - 256 + 50, floating_windows.width - 96 + 4 + 88, floating_windows.height - 256 + 50 + 40 })) { if (m.message == WM_LBUTTONDOWN) { @@ -6637,4 +6637,4 @@ int floating_main() threadStatus[L"floating_main"] = false; return 0; -} \ No newline at end of file +} diff --git "a/\346\231\272\347\273\230\346\225\231/IdtFreezeFrame.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtFreezeFrame.cpp" index a93b239d..c5a7653a 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtFreezeFrame.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtFreezeFrame.cpp" @@ -101,7 +101,7 @@ void FreezeFrameWindow() while (!offSignal) { - if (FreezeFrame.mode != 1 || ppt_show != NULL) break; + if (FreezeFrame.mode != 1 || PptInfoState.TotalPage != -1) break; if (FreezeRecall > 0) { @@ -144,7 +144,7 @@ void FreezeFrameWindow() this_thread::sleep_for(chrono::milliseconds(20)); } - if (ppt_show != NULL) FreezeFrame.mode = 0; + if (PptInfoState.TotalPage != -1) FreezeFrame.mode = 0; FreezeFrame.update = true; } else if (show_freeze_window) @@ -211,7 +211,7 @@ void FreezeFrameWindow() ulwi.hdcSrc = GetImageHDC(&freeze_background); UpdateLayeredWindowIndirect(freeze_window, &ulwi); - //SetForegroundWindow(ppt_show); + //FocusPptShow(); { double delay = 1000.0 / 24.0 - chrono::duration(chrono::high_resolution_clock::now() - tRecord).count(); @@ -264,4 +264,4 @@ void FreezeFrameWindow() } } threadStatus[L"FreezeFrameWindow"] = false; -} \ No newline at end of file +} diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" index 08c39a11..e283f1aa 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" @@ -3,8 +3,8 @@ * @brief 智绘教项目中心源文件 * @note 用于初始化智绘教并调用相关模块 * - * @envir Visual Studio 2022 | MSVC v143 | .NET Framework 4.0 | EasyX_20240601 - * @site https://github.com/Alan-CRL/Intelligent-Drawing-Teaching + * @envir MSVC v143 | Windows SDK 10.0.26100 + * @site https://github.com/Alan-CRL/Inkeys * * @author Alan-CRL * @qq 2685549821 @@ -46,7 +46,7 @@ #pragma comment(lib, "netapi32.lib") wstring buildTime = __DATE__ L" " __TIME__; // 构建时间 -wstring editionDate = L"20251204a"; // 程序发布日期 +wstring editionDate = L"20260102a"; // 程序发布日期 wstring editionChannel = L"LTS"; // 程序发布通道 wstring userId; // 用户GUID @@ -56,7 +56,7 @@ wstring pluginPath; // 数据保存的路径 wstring programArchitecture = L"win32"; wstring targetArchitecture = L"win32"; -int offSignal = false; // 关闭指令 +int offSignal; // 关闭指令 map threadStatus; // 线程状态管理 void CloseProgram() @@ -1372,4 +1372,4 @@ void Testa(string t) { MessageBoxW(NULL, utf8ToUtf16(t).c_str(), L"字符标记", MB_OK | MB_SYSTEMMODAL); } -#endif \ No newline at end of file +#endif diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" index 34e029da..73eeb031 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" @@ -3,8 +3,8 @@ * @brief 智绘教项目中心头文件 * @note 用于声明中心头文件以及相关中心变量 * - * @envir Visual Studio 2022 | MSVC v143 | .NET Framework 3.5 | EasyX_20240601 - * @site https://github.com/Alan-CRL/Intelligent-Drawing-Teaching + * @envir MSVC v143 | Windows SDK 10.0.26100 + * @site https://github.com/Alan-CRL/Inkeys * * @author Alan-CRL * @qq 2685549821 @@ -12,7 +12,7 @@ */ // 程序入口点位于 IdtMain.cpp,各个文件的解释将于稍后编写,目前其名称对应作用 -// 编译提示:.NET 版本默认为 .NET Framework 4.0 ,最低要求 .NET Framework 3.5(如需更改请查看 PptCOM.cs) +// 编译提示:.NET 版本默认为 .NET Framework 4.0 ,最低要求 .NET Framework 4.0(如需更改请查看 PptCOM.cs) // 首次编译需要确认 .NET Framework 版本为 4.0,如果不一致请执行 位于 PptCOM.cs 的 <切换 .NET Framework 指南> #pragma once @@ -156,11 +156,11 @@ class IdtAtomic return value.compare_exchange_strong(expected_local, desired_val, success, failure); } - template >> + template >> T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept { return value.fetch_add(arg, order); } - template >> + template >> T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) noexcept { return value.fetch_sub(arg, order); } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" index 4d925554..a9b5921e 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" @@ -342,10 +342,7 @@ LRESULT CALLBACK PptWindowMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM { // 如果是触摸模拟出来的鼠标消息,就直接丢掉 DWORD extraInfo = GetMessageExtraInfo(); - if ((extraInfo & 0xFFFFFF00) == 0xFF515700) - { - return 0; - } + if ((extraInfo & 0xFFFFFF00) == 0xFF515700) return 0; // 否则当成真正的鼠标消息处理 // 您的鼠标处理逻辑 @@ -365,9 +362,7 @@ wstring GetPptTitle() try { - ret = bstrToWstring(PptCOMPto->slideNameIndex()); - - return ret; + ret = bstrToWstring(PptCOMPto->SlideNameIndex()); } catch (_com_error) { @@ -390,7 +385,7 @@ HWND GetPptShow() { } - return NULL; + return hWnd; } void GetPptState() { @@ -405,8 +400,9 @@ void GetPptState() { try { - //_com_util::CheckError(PptCOMPto.CreateInstance(_uuidof(PptCOMServer))); - rel = PptCOMPto->Initialization(&PptInfoState.TotalPage, &PptInfoState.CurrentPage/*, pptComSetlist.autoKillWpsProcess*/); + rel = PptCOMPto->Initialization(reinterpret_cast(&PptInfoState.TotalPage), + reinterpret_cast(&PptInfoState.CurrentPage), + reinterpret_cast(&offSignal)); } catch (_com_error err) { @@ -423,12 +419,14 @@ void GetPptState() try { - tmp = PptCOMPto->IsPptOpen(); + tmp = PptCOMPto->PptComService(); } catch (_com_error) { } + PptInfoState.TotalPage = PptInfoState.CurrentPage = -1; + if (tmp <= 0) { for (int i = 0; i <= 20 && !offSignal; i++) @@ -443,12 +441,12 @@ void NextPptSlides(int check) { try { - //cout << check << endl; - PptCOMPto->NextSlideShow(check); + PptCOMPto->NextSlideShow((bool)(check == -1)); } catch (_com_error) { } + return; } void PreviousPptSlides() @@ -460,58 +458,54 @@ void PreviousPptSlides() catch (_com_error) { } + return; } -bool EndPptShow() +void EndPptShow() { try { - //for (int i = 0; i < 5; i++) - //{ - // if (stateMode.StateModeSelectEcho = StateModeSelectEnum::IdtSelection) break; - // this_thread::sleep_for(chrono::milliseconds(100)); - //} - - //POINT cursorPos; - //GetCursorPos(&cursorPos); - //if (EuclideanDistance(cursorPos, POINT(0, 0)) >= EuclideanDistance(cursorPos, POINT(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight))) - //{ - // SetForegroundWindow(ppt_show); - // //SetCursorPos(0, 0); - // //SetCursorPos(cursorPos.x, cursorPos.y); - //} - //else - //{ - // SetForegroundWindow(ppt_show); - // //SetCursorPos(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight); - // //SetCursorPos(cursorPos.x, cursorPos.y); - //} - - SetForegroundWindow(ppt_show); + FocusPptShow(); PptCOMPto->EndSlideShow(); - - return true; } catch (_com_error) { } - return false; + return; } -bool ViewPptShow() +void ViewPptShow() { try { - SetForegroundWindow(ppt_show); + FocusPptShow(); PptCOMPto->ViewSlideShow(); - - return true; } catch (_com_error) { } - return false; + return; +} +void FocusPptShow() +{ + if (ppt_show != NULL) + { + SetForegroundWindow(ppt_show); + } + + // 都需要保证激活 + { + try + { + PptCOMPto->ActivateSildeShowWindow(); + } + catch (_com_error) + { + } + } + + return; } double PptBottomPageWidgetSeekBar(int firstX, int firstY, bool xReverse) @@ -3435,7 +3429,7 @@ void PptInteract() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); } hiex::flushmessage_win32(EM_MOUSE, ppt_window); @@ -3443,7 +3437,7 @@ void PptInteract() // 上一页 else { - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); hiex::flushmessage_win32(EM_MOUSE, ppt_window); @@ -3467,7 +3461,7 @@ void PptInteract() if (m.message == WM_LBUTTONDOWN) { - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3524,7 +3518,7 @@ void PptInteract() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_LeftPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3600,7 +3594,7 @@ void PptInteract() if (m.message == WM_LBUTTONDOWN) { - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3657,7 +3651,7 @@ void PptInteract() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); pptUiRoundRectWidget[PptUiRoundRectWidgetID::BottomSide_RightPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3735,7 +3729,7 @@ void PptInteract() if (m.message == WM_LBUTTONDOWN) { - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3792,7 +3786,7 @@ void PptInteract() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_LeftPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3871,7 +3865,7 @@ void PptInteract() if (m.message == WM_LBUTTONDOWN) { - SetForegroundWindow(ppt_show); + FocusPptShow(); PreviousPptSlides(); pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_PreviousPage].FillColor.v = RGBA(200, 200, 200, 255); @@ -3928,7 +3922,7 @@ void PptInteract() } else { - SetForegroundWindow(ppt_show); + FocusPptShow(); NextPptSlides(temp_currentpage); pptUiRoundRectWidget[PptUiRoundRectWidgetID::MiddleSide_RightPageWidget_NextPage].FillColor.v = RGBA(200, 200, 200, 255); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.h" "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.h" index d8620c6f..83a49a80 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.h" @@ -12,6 +12,7 @@ #include "IdtMain.h" #include "IdtD2DPreparation.h" +#include "SuperTop/IdtToken.h" // All function and variable descriptions should be in the corresponding cpp file. // 所有的函数和变量说明应该在对应的 cpp 文件中。 @@ -208,7 +209,7 @@ struct PptImgStruct extern PptImgStruct PptImg; struct PptInfoStateStruct { - long CurrentPage, TotalPage; + int CurrentPage, TotalPage; }; extern PptInfoStateStruct PptInfoStateBuffer; extern PptInfoStateStruct PptInfoState; @@ -235,7 +236,8 @@ extern PptUiWidgetStateEnum pptUiWidgetState; void NextPptSlides(int check); void PreviousPptSlides(); -bool EndPptShow(); +void EndPptShow(); +void FocusPptShow(); void PPTLinkageMain(); @@ -265,4 +267,4 @@ class ShortcutAssistantClass bool IsShortcutPointingToDirectory(const std::wstring& shortcutPath, const std::wstring& targetDirectory); bool CreateShortcut(const std::wstring& shortcutPath, const std::wstring& targetExePath); }; -extern ShortcutAssistantClass shortcutAssistant; \ No newline at end of file +extern ShortcutAssistantClass shortcutAssistant; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtState.h" "b/\346\231\272\347\273\230\346\225\231/IdtState.h" index c9a26366..779f6560 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtState.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtState.h" @@ -41,7 +41,7 @@ class StateModeClass Pen.Brush1.width = Pen.Brush1.widthPreset = 3; Pen.Brush1.color = RGBA(255, 16, 0, 255); Pen.Highlighter1.width = Pen.Highlighter1.widthPreset = 35; - Pen.Highlighter1.color = RGBA(255, 16, 0, 255); // TODO 后续添加透明度选项 + Pen.Highlighter1.color = RGBA(255, 30, 207, 255); // TODO 后续添加透明度选项 } { Shape.ModeSelect = ShapeModeSelectEnum::IdtShapeStraightLine1; @@ -117,4 +117,4 @@ struct StateModeStruct_Discard * 4 矩形 */ }; -bool GetStateMode_Discard(StateModeStruct_Discard* stateModeInfo); \ No newline at end of file +bool GetStateMode_Discard(StateModeStruct_Discard* stateModeInfo); diff --git "a/\346\231\272\347\273\230\346\225\231/PptCOM.dll" "b/\346\231\272\347\273\230\346\225\231/PptCOM.dll" index b0bdc2140859237739bf7b8a24a6639419346bcf..55574da115f2045f9478b19e6fc5991df42c8eb1 100644 GIT binary patch literal 40960 zcmeHwd3;>eb^m>DR*j^QW=57|TgGF&Als7A>V?&^yeW9Yk}-|}dn}D@6-gt%k+6;G z7r9Po5}*m82_Y*4Na6+(XlSz#@`I4l&?IfrHapgj#$+K&)9mG!)c$?H=e{@dwAhwJ zKcD{b`;Gk0z31F>&OP_sbC-ADoAH*{zg_v1Qhr?L&MEb2r2MQAcwsPt;^_R(M%9xA zUtRENWAj%RbRO!-CI_s{K`Ygl>`L|bX9kn|(@ATnKiSitT(_+w*_Y`~*A*8>7CEZ7 zuUBfb;ZryM^ob9_BGn>={kVEX7F z@E_j?fHG;TV>dAQB$e7%XJxG}U}D=d$grXB!R7g^L3hD~+&X{&k(7j=CAm*=xa zskI(dvd_}5_|P^}1ypiCsV{9%3SOMiXKuYI&4GN(^c{z_e$#(Ez$9=Sed(`=j0TpB zs`6#QWrp;NQnP0(-z-!)$AZ+rvJd!cO8nJTN=0fy{_1&1m-uQzKAkgbLZ;3wQ*-ML zHQTY$S8FWuX`v#Ab<~G?mZ4nNQ=|%J>3YsDcL^)IfLyWq8BBz0V>WE}*VdHJ@Aw+p zXjadMWX0VpQQ6}1^0MkA&<|BF0Ia?gV1(QvGQ1F|1eBPv@Nd4>0Lya4lB0Ic>dGfdYkL+E0&FtnOO_G=;6BNn=KAX@|0ka;#( ziP4#lv-_mkT`_%px2oaP9VT%^}z8CgI9@x#p4!(X4fiRu)+gp*WnWL;cm4 z6VwBYDzp4JI|E4=i3EW4EI6fjS!**Ig--;&coga1B1Kyiq6Inq8AXnl4=7`KZhe9J zAjcp+W_27USU++V`c^LnryWC8wbvv~qf)K7VvKm;-6I6E8(`vnQzXAX1Poz-a$bKafR zDe+w0!oXQUz?{_rXX@~F=Engu{%5Al!<@tSBW|HNX4d$8wPyJ;)7NBHEVD7eMIr(V z&1$TNk%&){!eFR+HPE8bx+UdBqf3LrQ+)+UHKAZN26$vi$V&@p+KXXKDhB3yrRJ#0 zMiqbq4HNl!D;4G(g*o0-zekD|#i}vq1S5`W)>acPPxw!N2gSj-Q4LMOB~nL`>8nlx zD;h0WLJqERVO@J(>_a5ReOfG3&=jr!=iMNxQ1w+%o(L4^G#CqHt_C54c`p(R=JSb= z)dnwSuB9o7Sjf6AhXtT)v~9E~7OV+DWDPi81Qq}maIiUvu+;&9*u?1wHjf5r`KXHd zmnj3{3M*NpnM)Y`b5vdVNxjsYYCv88tj;TSNFDqqHOHNo{{7!oko<{yzj_BWAR1z3 zbt|n!kIpZ=do2nVms@Xx6(3q$VZB$!*^=FR*^AGZE_?GiC<*Bay=LqUDKD_&vqJf=rv#U9l`&abr<9^cOviKPpGB&8?3p zxQ<)Bn2m{b8tW>#MMSTwf>uximcQbO^XUbbKg!tzdii6{bXGntcJx_!#KywouU%CB zwTa;pG+mKU0^X@KHLHP*k#2n9rHxSdLf zni;Hlk<4`fOR$rgQ&=9cDxfbkVMz-jZe)M;fO^~IhQ!3&`lwoEGM2F7EQXYff3q4( za^!e~+GWD9F&wARL~;qMZla)AZ~e7bR9Y=iK8@vU3k(>ctW7Yy6{zIgnR*)t(&kP( z$r`4-?IO2`=MCntyo+n;&LI<4fFi!zmh7)|=d5S=Nev z<_GhN3L=)~;=<0r+4Drzuif~1rz_%(FQ2SdHk>+*P9(R8=uQ-LAvIu(Pqz~(XA|g7 z6m$V~%`j*wYZGWkGdHFaFLiX!q!TG?6KLJcO}P`xmN=bfJ>>SoD7Wf<>cp+W6vj8X zgjJ7kK^IdJu9``6C~Fhw<}f#=Ih@m+EsKr&k11G4E@9Oc3VInOXV{sPwF$I^%y~Og z&p@NaOROjIKDGXrn_s5f`veY=ZbPXNT-b2R*g&G*348;t@0m&!JM=GDUv(9D2a2X*=1mViV-St4Q;_|!n6uq;tjmMAVyM9bsh>RPB-5(|{S zCl)S?7iM-qU97MgH#EW8Xspnh4T_&HI!BKrW?Q$?v$My0_U4$sW?gVA%ZX*-a=N9yvktPgigq(6<+OFs}$>Nx0Ukb3D|z^{ew4+Y24slOiA zM?jwgIoKwK{t5E;g7#k?I_)I=uR*H{DaCknKeLcoi|yAgT&^DS+rXC~eHf|RF4D<= z6KIEV-HbdxbL$f-GZ&|yynpy0x=Nn|InO}G-Fi630<$9JILNcFWn^uyw7v`}&jHVz z^FFIuTsc4HKERrt%T{ACj#PI6j8LPf9qtAyIox!i#s0VvH#2Dvv4iwmucF4VjN;I^ zv23@|s3}isr> zz0q!^s!1OaUU17oF_G0<7NW60LD^ZKyJOK6)bRX!74IqSc2+cHf0A27w9SI9qlOtQ ztCX_|v_F{}h{mEP`==En1-M)#sr)(y< zMMRrPG~ex5xMpG|#cTp?CUdbsD16o&*X=Rw-~!p8)?(M-kNH>sGJr6+juqpi8`J;f zC*7BF{E|ypwe5mpXF8+qr<_foyPr94_e;kma8OyDz~WP|x-^6dEL0Oo{@ABf^#NF8 z7U&0H_GVfzoh>#6L`v_ui0Z0k-=&|fc@D~L>}RI8o27tA=?aKC4^z?1h<=KvkLR!* zn!@21xoiS`_$BC8O3v_jjDWX?z-ly~^0fE7_U0xSS zgk#~=e}`c4yb6N-ihf}v79R7)$U#)&VZ*J6hix{d1O3X#IJVtm*sx!6-WeIkmL0=} zossj>2-$evyCCT7k?^KSh4)PntHmu@Ug^F=QX%hL+^( z#nEsNxNID9$vpE2kSU!NP(Y;gJVVqokyRgJo%H~x{~7(T#F%&Db+4zoN!M5RU}e%*nkXi+`T&_|EMo7XvKj2qC?~>ts1fxpDOjoz zBhzR#xkW^`ny9-}ciT5o)rw2zFUt4S#7B#6<2|QJloMgSRuhc{w3&Kc_M9&T^lMn5 zSS0@*7M|i2$cHwwZks^c z#+>8oD;~w9Vc$Y+lg3wmKkVi_&v7Xxvf50dv4D+xqZu`taw4p?5!K7Oa~Q_nli7Dwu3n7?XEtWUTT(CICxb#&tpwr0lS zTK|&riQt{H*V6Y>D-(%RV|U5w(5*EEdiD31^GT31rIfQ_((qKTUrP zM{=^XuF8IV}kDy+E}q>GA1Y{vf4zV7!%l0OM^=;G@Z>J z^B(D~h?Nn_iSTeQP|4Y8>cZCaRRosj;J}?8-AU22BFGT|w#ktZ%8CNL1tE$p2%77c zqr>t{AGqWd)%-|%9=8M)2qZ-C^!Di#5GfrLk$?TXr6dvx~~>dPK338iRw2s zb76oj$TY%}+%|y@1LoW?ScBcSZB0uXxkW^46Z9}OTx=r1F&>o=XDc@+5TSRnAi8|*2SNxt!N`~5|zobx1WVM+@V?n#0%3#{1`QbD9 z)2dAja&n7^jypjS*D`39ho(PhC~FhwLBpK)o<^I>&jdsdPvpizm?dC_W7Ih1Z78`#M2|~Bk5a=7mRri%1bQ$sH}-+#hv5t7O{ihkanYAK$z>Dh;|W1; zr{oNeS}1E1=%LBnm|N=W&}BGXS&qI$H~DArq-9Y82X!uH{ggo-n{We(z1g2a;I#}@ z%8L>mb3{Ec_u^#dJ>SJjtZbVVU(}W45?1X>L61{%hOcZ<)+W%dWX_#gw!w??%2pz- z52Ir7PWuGwnsQi@TSRnN3OYg!2+K*%d?wk%EP@f$SY{$4l(Pwju_i_&7mM3(ptc!? z5oJYzZadL2VT5)3yeBhD6N%ihoqhq-`Y*;*V#35Z=B>04>RA0)@~VrAn;W7w>abLtnZ>s?N~?B4o5 zXu@X&=IcKqZA5LwNOA5Z1vdicd$qSAuMJwIXP__*{k(B}cC9+)BYr;s**z7Hus)blRjQP}!ynbz|# zQhr?eKLVcv+|@(+F7W>x^luBN27z)svxLG^6-r^SN(0((Op|Nk7 zYGWxNQo6B3J?99XCgQ6_I!5piWEtMYtkN-r5LfdMLko2rNgO8dy$ent5yX|eE*Y`! zqFtCc*Pkz}u<%Ak$R(`9SlG0XeJ3R`ao&#>P4`BcvZ6pwoJ7Y=oHqN;^ZlyTYhbng zcGh@NTgfG?+Ezi|M#*)R);Gr5I{N}|3%KE|-w?o>78#)yn_zf^USQ6&aho!|D5ii& z>5e7piDLUN9DP$hXQzNjX?;Y!fpP+-X0$(-@jGl=5Eq!2oc{OKYA-KSzQGc#raZ?8 z7KXBa#tx9zo;37Tic} z*e_{Kp+sQe>3@Z=-`Wjjb2Avyk@CjkprFSg>R*1D-%5=wBdW_>756|z&{vH~CUP8Y z^9P3sbv63yc7>w@!7S#o$nj#9pAGJUM0N*&SW%5xIJhJ@%)$&N%}7Pl<<=3@qeVa;c=dit z(0eJ0iSF;GY6b;t0&NC!A$=%t9qK4L{S-RaekZlQh8us%ixPb|LDUS%L9hL;*QlEE zqC~4EY6kTga$#9SzE{dQ8Pl-*WWrMPe=ROHiyy|sRluVX6ZU>U*At%7xydKYy$09LElHw>G+ib zHi0&Sxp98|09C#Qzf#^N(0*m^!hS7O*ZP#gY7xV?P(R4pXL$QWSy7;O0YtsKfF+|P zORU)uXOY2|Ofg5-gnhMv(_g?~GKWLpU!BNhE7v@XezzZ_LCs0t8(1qL?Ae-3*jhGl zF30{XGD0a49mdu#B02j3YC>O4d2*(JNa?;Js!!tdbap2+ui6E>wCNRV_*LUsh4w?N z=c4znD(Hu)X*yv*0h>UZ%G`KUWoLzZ@Xhcl zJ|?ccxKg+4Q* zw6>8-Hc<%$Yy#a7<~$9lkT)=$pBZxZsvrGmzAwUvJAUj>u;MA7R+3vp^l7D_AEyS~ zOi%ZLCFN`aeTdGSZh_}JE;Yt7Wk1deoN&2&%HcvO5!K;B)N#w6_F(f*NMvEy~#h+FQ)&dqJQ1n%p9yJuT?pP{Ry7O*xxDdzv|Y7eCh1ueeRo zp8hmzyQrthWfN#m3;HQa!qZbe7NCGlpgql;=a@lG$Cg+PMQ(i0E3}RQ37yZ=pJn}1 z_B6R|0_|x*KSK>O@HFLY0_|z$Ce8pQ)(dV^w5LDE+Ag}OB9~2|H&uc@MM-#i%D;|6 z0h>U3nmNxTD;udLR;)PZ=_0EGNO*EjTkm%nm*FgLvrNA{ufuN>Ds=;{dB|Uafy$CW zK+>PVUcgHq1y1@h%uO!+60`bk7(dJ0bS}OGbT4$o@XyCXE_}xQr?M=#Dv&RIX+7c2&?2tM)B z68?gie{W5ZDJL$z49*_#ecu3nYlu+A0{OF(x4u>XcF@|6*R3^lv*kyIZfmZquWPDr zYFG_}u{-Jod~*fPJ8h+YgY?^R<-r2=xCLO{!cweH+jvxDYF97p^V1xHm%+#JX;zg-#y6 z6&kuh_IS`KeaZAPTwz@7NS4pS#j2EATu2(RO$8sqqmAbU&I!LhTB5#y76kFVs})5- z^+$oH1wK|l$}xfO5I7+8y6{P8_+KHyhlO%ycts?rDvMGjLG^5yP=!AeM`?(49SW@} z462Q#gr5ykYPV?kvgE%CJrQ+MINui}&#w5_!E<#n;mt*B3Zv>>C9g;R(EuqMgkEAK z{dcOhV)>3jN*Tr1#)7I>YI!`erZA%X25WCOnZI51|HL5YF9m)C^q_jHIMF#wC)E2XG`0^8e!?L0&5E6>SR$#aa@&`5UwaLDUPVR;txgRst%Nh8V$C?1HV&A ze^9*+wnfytiYWVmBKq^Ncw-MNL|szv&wR{xLurz8QBn<5B4PeWF?L@9vJy?pp za^bUpg{oPwH;%#HU#iQlR0ecDUHok6_2Aqfm=V?3Cc$oX%3defJr1^6bbh5oYuPH; z^{lckFV~@qWocB0o+ePl%4z@%2V9N%yhqe$aRefmrPGC2vlfZUjIP6qWb)#UP z6fEXoR|{u_gW+ZZoO2zF-$vZ27C2Z)!BT?V>0nrE zQFf1mt&ke-bISyKuY=*ouE6<02OARXqYm~jg8h|)Et47^b+E&NecZwDOAFBQ1TaH= zpl~E$s2>Xas~GdQ1Wp3pD)4_tn7=gg=YX>!Zw35(3E?Bby8}VBC3p|u&;5jFqkoBq zmiMdo0scaL9Pp&^j8MKV6vC4R;TeHzP39jE=t`0Qq;W=gegk;F3IwT*`I83oX9TV_ zS<3^a)~|U8Pa343k$Tsfq!_79TZ;nO-NTBs?y_DZRRl(lA^XdwL$Qr8Y2ZF|he z(ieTC&&4-jQH!+t5~=s2V*fd@^JbBHO4=CrvAw$m-mlg`^Dji|q`_KTo2l~wY26>B z_8n5oV?Oe@H44upHPVusrKL|vtK&X;V7IjTNy)Dle?BgPw22-y~9Bl6o(b zdfz1Qj97k!c=#=%;VV-6%_93Xk?j`QuSxt|E;ZgQ@TXGmT9LX(r2bWEPfO%{Tha5q z(j(tC7y~P%u6LRTVB4>yy{{Ag`<)13i+-VwqV%NkR=_jH`vBKUEf1Ka{J|u}jnBuB zcjK0_Yt7Gt@_^MP2P+n2{S#l2c)i{`^TbLz{RTZRH0tx z_6%j^!H11X^~P$A4aGL&cb=tn~T7 zP~pB%zU&Oj{@NLl)XPws}WreGYpd@b1vY9QLlj!=dZcAx}?l#%nz+K8e3~gf=5*sIAxoo9c(y zRT`=s)0nC51@z%YjdxSqP2Q7z9MDiH%y_2i2lT0L;6~a|e=l$%y&rdsroz2BI2S{| zq3#gQJ0hi}hI&QvbEU3#0~+d-P~MIkC`0`fZuJa>bH_=bsR}R+8tRipd_MVT+=Tx3 zqb@_;EBrS>gQypt=XpQ1=5i3FT@< z{xD5dI@F}rj#U>J8;rwkw9F~c9cMSaFdg$}A0jL#GvM!Qm>cLUyB`Uogr0;NnS zWkRWve3j%EVdQ>G)d*|`d=DhWYQs5#yKY_CVZ-nZGS5H)flU2dj)1jF*ZQ z7*~mgt3<;(NbNBem^(|;fX7SH#!~a6MH%Ciae1kQ{K@zcz_$UOGRi~8jaSui|2=?* zF-l|R$|&J*JArN+%pi+*18k4C3ZI)!qR zSl%u0J8E~V!0eUMUMU>}{afl!#m|PZEONknMV$!tn=#mS6ir=MYMZCj&x3cE$I!aF z%s*AvM&Dz;QQcGeF~AkE$IZJ$+g+mVFQvWj6UzI9^04TSnSU(#ompU95&JJ__%80o z9~GWQh384|Z1P=iK325bXPA57Tk_xHOPSlT3ZGKjBXQ$%!v8tp|Dx#pj=HSqO}+(2 zU-WIhuSn@vr1W2<^hKe(D3q5a|FYzNCi$O9{x`z;M}dBW{ab`ML2MX|jWVHB38hLX zi$H1jFEwQpm}mX$i$4~m{D;lfV2{5Bu@O`?#ve<&eKp3)!XbaN$To}YRgity|AO&* z^v*A}KTXcXr^NG1A#13m#%*!R-s^t`bIJAU+x`XWA@$AhWtd^V8D5HaQ@$9k!`|(a zp*odNp97q$z6Xf?2H!E+LnXu(4w^$_x_kpGz45BN#wc~0oh zf&PVp=Y(=bC}-4j>SqOK)R$F9#AmQZpYdh&WMrY_7b4$O*e3ZlRCY6^^njXGM-akDtZW<%Zr~A%5&g+EAq7JIpI7b^fRD8S$sw)K2vNk zX}{0>o*FIonbf&Z=nFxAYw<#%bP468z=s4rC-96wpO1PL3S8u4ueC|OOW;X?4+(ru z;2D8Fzeove6WAs2q`-#+KJ2Hq=Oq6{$-gY{f#6f>f$&R!r@|`ql=|!N8o;N*FNFz{ z1uWeM_}AeF08fQq0{mh)8DanK#oX10>#+LMKo-{!t|PdHalHZ830x;}y-C@DFm^rP z^>4zdP>=Cv#``c+)tW)yZs}o)zL>?(@7q zF6da=Z?V4^&$k9^^H|zH`Ja{>u{bm)aFyYjg{vG_1+Ge5vvJMAH5XSEu6ejF!8IRO z64wG;m*QH8YZ0!+xR&73b-g6k`|<38z2sLf8NBcGt7}bi_|^MOY}Jr_Bho zSX*x@n{BG!3+l%7;P!#RO-K5>clGo)HD0rSFR85ENg5iQs+fY@=-^Eb?xxKS-r}Sy z8dPUPu2GG99dd(eU*A8}m$p*-d(-kjoR3z4yFh9ZrZhV zooYLj?z**Y+ZKh9$E4#}b}-#n*S<~lWxFy~Z_j=;FtESNrMG2zd(#r2*}9GC{ql?xaL!o9?t=-+q zN@vs7ZRu{6>h9j#(c9CV?l_b=vc8`seVNPrtE%5yzL zmX)#E@Soqog@c`$!Bp?|)ImYohAb=HKPV_Ux4WlcMSFS<6-Nz{eObK=;Z9hMovO)=OmlS3P*cZcf}1o*&a%OeXyr5-Fa*vy(!h- zt#uIFU}gFo%H~Yw*6ik8qHU- zqKqZ2KS~?2FkU#|EJ>YUZ_8M4ed<7ZN4hti%If@vp8iy?OJMhVU9dKN05Wa888AD6 zc)nuY1F#AMU@)z=4D}B7bfvO`>rmH0QruNXtvlAA>g(xh9mJFL{X;0BXnS`aM@t9b zVoL@dL_I|thk88NSPzU*u>Ll5W6nAGqMiM>VK5))VW==<^Cj!j`-cu5Bt1`qB~BRS zX|368x^I8)vCf`BFKezYdVCUYE7hIuOIf$(i#la=ZmQs}cSXj|IKli8Z~ zZuk;D^U`%6Nd*X=)KST0^Kxw&>6|>lwGCsTd#G!0VnJJG;F#5O@X(}1eFLffV|f{8 zOw$NdyuYUx4#}6cXLIw5o(j0txGjxcN_YC`wgc`Qucu$kFA724mEMjipU8lda*lVV z8dt)pw$AAka(8C*3n7^4oy_*s;2};^&VqnBbaT4@AZQ(!E(S3Ju3U!8nc9-FvWHT=YTJPW9X$szj#Va?JHTPXtfSvOU^lpq)cDyk(8Bo zW-U-&pSChsCe!JG{3@=t^mJL7Z05jVoz{VEVsCF7dtqn*%RxG2p;=r;b#077C)bhB z$@@!dhkAOu)7Auybxw%Gy)1U;YzJ-MlIh1HiCQ=~T%^w-Vjm;+>owI<-fkYVf0+VT zZi8`tUT@cW)~PW9J7n`ThBUcV5}0H7+Vvpd#t04W%xumaK?;wu>5RgRrRur}G4pR9 zwA>h3*ONNfpUGnJ%gU&c{m_;k%q`h;SEj!^>oU5Ni7XtM0cQs`p_nnm#V}W{*D1_B z*yg4uu#eML2XRi}Sw(ko(0BKAV@b^_b}5u>=uI8WP7;z{y6%04lELn%r$1LOwkC(` z^4pFa-5pPL`E@F*cBOg-H)O2sXNlw=BIww0!vx7rf?-re#ILtN*RrfLR?0CShE5g<|pYO_1(ckSm`)}>f z9O;L&!wRC4%ZQQCFLw({ujs0CM>>n8RJFHq@^%o|N}3Z$7^W$!C)GbFG2(2K$Br0j znRJ_O<6ce3&jFbAv1PAwdc1Bs19W?)2RdP3E0#-H^*rQ**owAgupK5tZcf(g6k5-M z{w=AaIyYct2I)9N|Df7}TM!OOH+DOjhm!mnr#Hx%Bc#Gp>4O&Z@q7dKNbv0q=;`ip zRgx8hjOQTSijObF96p|ub#IIuz0yKNa|b6g-U~T&+)m^~>A~B2GDG=2fyzxT6j3+y zq>tpeVUvtlp|o?uKr@{V@(?AOp!wh5Ie+Q@FOsO6=s@ z`_o*PiRt@^?N(2QJ3O^NoyGm4gE)J5cU$$%#1pswjzI2p(Ar8YF<9?a;*i|K}h!FKd_!yx*qgyr7qu&U)xCI6g~}n z5Z^Q!ghU2;N*_RurERDuBRp!}PL%Bzt=%})TaR)}xUK-E7JEzAtJ@r{n46%v7k_Ei znHYr3G4Qvk&Ct+__NWCN;5dNt4WgUeJAhFlrE9KmImXe8^I8_*XG4zQ{~N`Ny^;># zt58YQ!!~B{B!KPA0^bUqeZXkJA*5Y+7Li2FDb%fPl-3m1K^K;8_*&)iefaqem+n%t z9*!*EVstoQhsb3-I+%Akm?v+yw%N&nR@@4k*c0@iDlXo7`2kvf1Xda)v^j-dIEbv+bSQQiRqdr-zu=|q?GNNBa;LBbYLIP9|G=6(shBpSj%PKx6>s8Z0)L4XAv z`F{MdwmQ77)QS|tWU)60&Noce{Crot1Amv)-Yw2km$##a3-fsEQ6qQK1$$&19>xF2 zk%R|13X-_Sjv_`O8^l?n3vOcmHdLKLx?Q!yeM!*W@l^+GCv+Ty7B-y@ItUuIa>gMX z6o(7~OQNUh@P&Bq_@oQQa*msQV#Da=X6R10A-G=)nly{e8=Fy{Ak_|*(2bwpeq9% zoORV@I?@?Rx(zkR?FSb_NK>UfiI>vf=>2i;UDw~a^>UWDsZKv08@p4TvPYHeOg# zHGRF~@$7xKpXn$%#}3ibaams9WCJm&KKAB7e{j$U^uO)i*d-v`sQ6cVmX&e1OvgNRQp0jG2JuPbRB5nh?RNHofz|-u_^3 z!NH`Oj5A+L2fkNhv}{D3gYbfD#Z;tJXctCX2fj=*S$wM1Q`4EZ*&Pe20MDc`N{?Wj z&wD}5%I7`nYH^;*eSs&I7vxJOiyUyz)2ljX4bDnj(K&PC2{39`7?FBEsA77V;4I9M z0!ggBM}c9EDWR=dT>H^qkn$`X9MP0Hh&0#dvHYw>DkQERPhnj4!j}Bb)Z6o%{TZ>& zu5ahnc+!5pKSs4)IA7h9y6eV0SN8qGosXr}-Y@S?o>zvtjMbYjqbC@vldtI<2V1f7 zY{p*_Z@Aum`>egyLrWnUn1W<0q}nmhtQT3@1?@<-*ZDzHOWVUrKYXrl<4m zySSD~Th^X$*~K+nuubXn#qNJ6U8#pQp8CYz_UAS~ecKcNx$iTJexv+J!wCDZzyy$q z#fU};Ou<_P?iQE|f*c=tx*%yX!M^}Db7>Me#~UeB_nb$j@UVzeL%r_{eX;bGihX2MvJ7eDRS}Rb~;^K~fS5+`eE! zmAVX%K>n0jRW+7SRaI384k<)FlJq5f36(GsW{wP*NWOw_(oC4-2J1M{^-1Dn0A=aK*%)X$D#@q(Ribs0q2KJvnqa!e^o)v7eeHeLXIwpAC3=|(!+2ud>#f?6-c6+pDs=Y zs-m$I3*sjhSIw&`NGeg7z!z~vu|J7-gU~x>nQNtZTGvp;R(kPV*XWQiJ>g+i1(h!f zlfy}0ILtOAL$ku+a1|m%!d6n49~-#`F)U4SstgBpKZCh=R@{S?CIg9p?(NcquQcIj z@Ps9fs$g??oH=2SIpHdd4pasA_kp!|ENj>k*vMm?MaOk$bgTf{HI{(R$&CST9*p_% z5QXB(af&PR#g%c_=YNCYz(}v614i;#QPQ9AYX>BJ{*YN2M#RQ9SF(%IsV(qFG^)d) zGLXc;K)DZH09FCua;x|Z=898Q1weI4w?O zFTExF__cfA5c@{t3PX*2pw1Y1{B=tZr&f*RNVV%jh&q zJ8tb6fF?X5)ep5W!x~kX8-i87U`f~v;-BdY#`?@)G~~D`#xy@1;qq}5&Vo4}XX4DW zxbx6>mI*T9cuq-fsL#rgyn`yQ+#OX3m(Md6#&dJ$z)u`EG7mrmztOuP<^+m9w&^$T>Kt`fjg{DvwWA88rYiY z&plbhLuuXoncQrP)9H?(@OyT=YMTQDB=rN+y zMnlc1+q%9p_v%88JB;`X3NSlbmMikS>rzUYWUeIHj7EG9FVy1K0t|K9&5XL4SKQ2Z z-3(dr9w%D~32Ce}VMWV^73){GHPo(XTHjXNTwmW*yLQ#;4YjK_G&i=iHnlZwSl_A} z+t%FNxUzoj`r6g4Esf;dP}{n0Wn1m~b*Q8R8Yxb|0t}V{fEoD!!g3f7IFqP-UYG{hpUpMXQbB~xN zK5ACkDc0IP^IGvpOr9UQ59jgaqFj()uqU_8yw2Qnwy`zmhu4ax3lA@Ro0y|~zoF`; zVnzOW@HE-+lk`*cM)UOSaHBfkXQ3@{t8pB3V24O zlApqLKPmYSUw`+0c66-kc=J!bb;;c7Lt9TSAAC#w3tz(CN~v{M+_W~;(>>IIr`LEt z@z$Gu@To_B^wB$CdGIqodhf??`r-Yb{n1nJ{^2|Czp1_d*7UJ#qm$mGm!X^RO#CM2 zeL{y+H_*Lb{S6FqKmWvh(#6K6&wBVU!Q!38b-le?@XCRHQ8%5I*ODBWbBm#7e6z;t z%VGQ$2bOlFMsg&{=oqV?gwEnkxNdw#scL?u!1+}B@carVJ019iMV{~MK&~BUP+Nhw z18zW`pQru5IE%YOT|C({O2Or>D81x5E82j+2&kk`G zD4$C`WjsOT^NHMvzQeOZ@w+r`eS8eYhg7t~b0!R4eIBe&?kHN1eNSHQ2FO{E)X$Sy z-hgL1^|=fG$Al^bKRv_qMGGm<3-i`^_46qy&l36BkMEQiIGN+~ZA!4E184^=JczS= zoOVt^OXB-FN&K}!g6(cYpYu#mKXY%w*__Vl@0u?|4coZ_z5(f5g}eWBuiAPo4OiXSW%&a!8&Q<-IUgj_q$2Kab@f@9puvUL`WEu$t$K jeR2xS$B!42V}1XlpU1Irg#j2HCC87P9@?HV^8M%jkNe;M{`cPhK4!iB9ru$$L>{CwXNaD`o1ZQL&kbh4uDRmb z8hSeL;<{&~Z7;4HI*=&@^QJLwY7@bfmdhE%;HVxnCv(9}F4(hkFgRhP^>B4{)p}d? zKrhiYNui%fr#|h}cABmXE|VfeQBYKi`rZM&gGeJtM71I>TXr*n?HA9(fS~iEP}lFW zDE}+JZInrPBG9{&Gb2QIvLhBhGennw_ArDx7W2-92Z{VHz90Bj7alI^hl{}PiU5Eo z+G^VkOguqY6*dcI3Yf5M1Q~8<9Le=`p}WGSo;4uIw$cm|>sn-R7g0B;6;B%N=Z9^R z$x9T952G>cwki)x3A(JgvA|MHM88J|U{FCuJqj zH4%kw03Bx6nJApEG8+I3Yly0v)-t#ppa^|((_4|-INz{LHHRVMGp=BE5TI(FSh=NJ zrFBPV~9A#79Y}Hj8OI4D5 zL^ytty=c`Nw|HJb=fGC@8offDH&YecO0Zbi-yi3 z`p6Q|OIh@qvxt6qi73XTTG;G}qEEREo)x>%xHV{70!~E_)vc(qS+e;WbTnbQSR?bO zXozTzv4z!OqN-KGv+9Z)eu1*G)mWn+jH5i7+%C1p*C6vCi;-})Ufi&3XucZ5jCl== zkgp+DR~mNKK-_!)W8jTD`^}EAvkY$xI`)#Qw^B>|TJG457v8apM9CVJN6zWDRn|0LR+{GXBm#Xb+!TZtn9P1Lwt?J)R$-y$ z)yBP6Z*Fn+0kW_Qv8(Pq2=G-^XAiQj*=xm7*93Dfqur>o5U!}Qv_H6Mpj_t%&<@Oh zJC5F@gjJ;>EYBacqr!;wUKOi%hfX*}-F%(JS{aKqVD`c{i#HRz?`hzzNkh2SEx@In z4a5?Ly&T)?@eQ zl}3BHkh7m#zTC3yt#T9A+^Va)2XE?@?A;{qO%KJxk#H;$Yr$TNu;0uAegms{<9o1O z#ZtY#3i-jJnaPcdE!%!<;MQaCG~O~uGjFl>?u}b+>4yPyKkz46zB_A-vN}8%&eGng z&o%J8qF+ieb`XjahJe_YVyzP6s}XM$B^XKXLt2i+au@^RjWI3u?=Z>P&k8!rpbk8b zdO6P}p75;ntF+0>@Tl@F-=)-zYE=59GOemK>|xj?V6B()-xv9R74T&t^MLYiK9#N% z&?o3WQ%1Zh-Ky?^&h;LKDW$GTrQ2ne{DP3YSL7oi|DXP+AU`HE{8N?T^InENg{}IC zpnu)NWj`pOU%nIc*M)|yLc@=R%epeUM_XJ(AEr@Dtbm`$_bU*@-tB? zByEA@Nn-i0q7?h(c?R&AL?6Qe>~Y^uZA=381h994Q$-)c`1Gm0=&k_#%-FpZ*uQ(- zyt;se1xbSQZ}9a7?nb%I0(-BGg#`9fXkp$p0;{)qEux06`YkOnfyHf|7IvduyM-O9 zkbKg{T1C0_b~y{1lPsMUw$j#OVaIIA4pHt=8@o|p*V;OJ1*Y2Nwg~Jh8`~kU2kaV# z;J5Fp6L7$P&~1PZ1||VdNz;ITDd0ZuQREGI2Jl(`oz^g=liqFW`$4gulimdy6_K3r zliriw`)~?(cyg7S_g4YQCHWTtwRGA(=`Bhhlz6W2bJF`v1@FfK8wfJ*?|QyGJn)(S0;%U^Lxo-AXKe8KlM zhev%R!D`zwn}@?YTP=7GNe*U}v(_w&_X)|tES|N_IOQBnU3DugTV?62Jz1Rz+$Y%> z>Z{~kCV$)EK{6;eIOQOD2ee>8Vave#fb<>7#=x5e4-1OL^A^zWW$b0dN*jaZgB6mO zlqGFjO695#2VRlN0fX#XyNwZ zp2TWHb*uUl$;QCD7d+gY*t`LG)P;RW9VOhN*u0Oc*7X|qpEb(GR1~$h`~Ok0G4OUk zr0AN&5-X`R` zXf-Icv`)Yu&?(%KZj$bh_EL-VbN@Sm|5zHOPU-vpG5Rvy7nq9|BgMxA^eE|6s{T|>! z`XXR~zAh(;=}Eem{#Z$Z`&p$Qb#*BzK~K>K=?>&m^mj@I^nT?y{Q>Mb4oVH+EOh`r z0m|K?)U4png8O05te`&zdRmzi^f}O9@XQJNDbUv{3xd8t&(Tjk3p7WA-d90yR}_hB zQKUI~+8dCbq6wu@P#QsrshdDKq$C9;3Cd$?KPa$MP*R{=?#qC3mvUTCj!P+e(05$= zI(->XSPSlQWma%!rRT6?n?MOnK$#QtIh4A`KPPb;=R~Pf;C@tD5ZncDpGM?) zndSjm=d0k(DvB&Dms9i;zan#uip<b@d9tPZ?oCXXlGhT)#0GA^?GQA6Mtuh1HuABhu zQcepP^zoP(0o<+30N$#c01P86uuK$_(IL%EJPl z23)QLtGJIw06(fc3^=P?jyZG%(mJF@q^poNBZZKzL5d-@BDEuRB3+AgJsp&8L`onf zf%oFQ1#ds%@+s=2e!3gu^t1E@@*x%y_Fx>JKPTbrj`_@6su$%T;+TZlC?kR?)PN|r z0@5oH^H$+2#A>nonFzd&;_KyK0nSNh0PhKSWTJUu`bsr`yd`tUUxR$7x*SlGR{?%X zWq7TBE#Nx^<sD}+)8+1TRWhiHlj_nbQPeC zSn3016H+biy8WPZA^OVrEV2x+2YW&pJC;iUw;&$N@B_E+X0%SGZD^ZB+W}?7!qtGg z5QStK#EQ#z7FW<8qSDo*Vvp^aA>lVT%6|Ct<8ctYPteZ=R!MsZ?X#cZJ;l-N7A@a0 z+!7%NiMo-P8)+Gimc?Rj1ZCXiqa_4IO8KZOkJ2T2%W$lekGk@);kb+5(osTCxPz|S zZlou(`VF)tNn7;d&~#q!GmVMlwn3cR$Mxdy*1bD=D0x6n9Zc@rP6PQOza|PP!^~zz zDW5MUQpFy8&O+h6nOxeK8aiO=T6(}VQhK4#pQda+JypgmE^V32q)AJshX=Env_5#i znCi`?$XMZG^)C~CQsE^HQMD%J16H$02HxRNpl zjTthET6RDi*STfMNz>GGMS-#_wxdDJDw&i#pyhz~=MEVM@#@V@PUxmKnzgLhlPP34 zUqDeOzk4!Q%uML)=B-*T4K7;ZINhdjRXel^OG)2kHY<=GG;#<R7vPom2KUdVv zF)bxjQZlQ-=M|*E=|WMT2qz6w7akcf^#U5D6*ESzTOR{CnKcT!f$~)i?pew^$E>9 zSY{1rCOW7OU(NL?!_9DN=z|mX=w<=_EteY9Q`uVnVwTB@Hox$^@r! zOyKlns<@PqH1gACX8gceSrd6JH(f4c4}LZR6^~}Ja7dYJ4^%5a;7*b9MmEcDn_-s@ z+rAYXarsCoj!v{)GYbc_EG0P%WC=fM;MZh9H)GLoI-9j)m&jTwX>X2rN)5tH>MtOm z?9A)A%DB<3k7shuOiBoP6)$lx|Vc z4~Z$w)a^+(X3#ry(?E37^}Gn|`8*}kX~DKalS%F-SH$THkSYb*H(7=DtjhhZW)yI@s#d0*zw922c3NsK_WDk>m0Ulw%)4CCT!27Orv0o6~jBn#^ArO z?auIkF{PUWMkZGj-Hf&#+1D^vGycFwlsTf8g?8d68l`V zxJ)SxSKfFKt1k0Oeb=8$>xZepn;2fDE!3WuPS=TD;_@g(@WY{2zz&>$Bcu`acjDe4 zj-74{Fphi(`~+~$wcsy_9c~En?U0Wk$1s6Eezze-0b|$^OA5Y^Y#78BnhfqLutA{% zWY84tK>jclfjb^!W3?X#eEZyVm06YJ+ z^|yIQ@~?&N8kLk9JlNdVX|~A6RH3Ja)vc|c`4iQr`2BSm6j@t?gGgPbPOH;Uwi*-| zi1Pz#sqvBwJ;<$+)m3=;{eOP@{ynSXr|+J5C@js~xoA|lCv?~uD&$wL#i2G-IgNo%F$idtWb)PXdlsF(U>)vu^^nykw3f_$m}Qtk*&t8h3A ztf~zNj}HmhA^hG^EBd8=y`VJ<*d$<(`(y2D6qY?fs^7|BfWbBf*D>f|u#drz;zvDj zv&YY(00RuRF}RLF2beXS*~ehWFX3>=&s+H}a7bRU*VOVmj9h7*!-m3`DoBu%h1@cH z3$WG!eAL9zR(!O)qO^|*zVKKun8<3mSD-Pu+PUei0`Js~$t`rVxYwc_6?M$|W+G4%QP&^Weg}OUB`$8Rk@n~xz zmW=lGCM;Jb134ZI zwI(`SLM<&x=xmQfLXk)!5s9=#qHVEmqG5^NVNW(|b^33eLT*c1Bj>iHY;*EFHjhaZ zxqu}w?B3~IbOEA~vJ+vtCY+cWbDXGYwi8F&F4h)%6{q)HU)iR~>nv@6Dr+te>bBUqhreq5nq*X> z@P$}W-VQiKv%?wH zcK8=3jmz5$^keLztXS&Cw#50}*w@zD(aQ@+U$iF{!a5NRB@*$@P`o$U(%Rb7nn<*_ zxt-VAd48SN0@8Y(aqH^D^XTMG6t{EMN=Xzv`0S&c=dH#*q`%&Y)WVeVWB<&nMA$vp zGx(+W<=(yBA4xv``ouRs`pejhyyEq|ZGVq3mCG7hy0E`LcTk@$gfik44UO?}ZojpS z*>8-#d%t}mvq|B6dX)AJl&T<;^T9Ii&N=_Dz@iP~rm!cQ-LB!vxt8$F;iKk6>}qJp|jjpqP&eNpuuTJ;ilsF97OS_Tl z$9Iz*!21FFkmu)l&p-Xz`c}fDlOOASqlJiXCe{|)Cg9a0r1s#fXF^W~CqLYi<1EN0 zMt=B95YZ608mI;E%O^`AKu>z^f>H@K6(Pmv$nmpiN(&i27h8XE{ALM0z%N1AA4$|U zfj{f)TCf!3i&UAH7xho0wVIH#MR306uWF9vJtv`(Ocou*B@NYpReDC5DJeS~> z=4o13IF4I@B4n1*g7g?BauBC+_IC~vyP+e9`vksIh~cKexvk(j@IOr|>aco??-?dg z+Qc_{$D{b2pJ>NUyMFF1w#xAzW84ds{&Ke`Ed0%P4F*~{318!~r&Ox49NQKbey)^X w?Cr(A?hs`Xu$pf(Cg8&?JQl>*uk5A8<+#58(PzN=KVx_D!2a#?|IP#d1#qX;R{#J2 diff --git "a/\346\231\272\347\273\230\346\225\231/PptCOM.tlb" "b/\346\231\272\347\273\230\346\225\231/PptCOM.tlb" index d8169457d08449861a704be227c45aab61bb9669..77dd89d470d52e7a399df3efbe70b8bc27407b6d 100644 GIT binary patch delta 699 zcmYjOKWGzi6n?+UCG>tF7cU7UXQS4pwiPPaHj5BMF@r+Tzy)q`NJy^cAb(Vj)*(U% z1%(btpF=_1Iy#wvf{TJUb#QQS5fK#Ktb0J;OE0GnzPrc!zTbE6ci+z9c(H$8I}QLj z1;86zcqtwYe$OiTIO^hLq7(=CC;uBuGCcYas*nVry-ISjgr%Cmv$YlhkIEA`lzmg3^?@6#KrGKB|nW>;p+eCTM- zT=u`-!&R9U6?`jC#SZ8^^ISceBt;V+%XR!9)$oY80;}+wENErl;QP>5w%ciRw}xNzhl+WV z0=PrbBFjhs^ delta 601 zcmYjOzb^z)5T4n6EBk|oyW)1^$Q|cLp>SM5Y(#MdiNr}H*It4|6mmkTxZ)a#!kNY& zpit;ef{0F`P$>KXgn|l*Qo@YeyO`vA`SzP{X5O3q9@-x|7;}68_(6bNZR#B7YM(vE zmnMyUvGz`Y2TuHOFonl1n!#~gr7eu3Lr)l|Bkad9y21e43C&^p#xP(5H5Ga4N$W!CTZ4yk$oylV4Y%*m^#k8*D5AnYo};XrP$Ab4ddc(=6R}7n zqb7~7##x{}cfwv}*Q=Cdy%fE;A(dPktAuWXX1x)0CaEoNnq{kg>g5fxbtWwh9LPV< zZx_lti{-V=VzJn~r-p;~ZCcU^s^|i(YoG4*Xg$?gHG)ZAWyrn&f{NHXU>BS`fJxWg zJ(kT%NN?Q(7*a<}&TMR8O`hX&CIhNf*9&=Ylmc$Y?>8%QN#Q%zbd10(X@sAB@)Kl3vT9kqguJ4}s4`9%cZ?T?&Gjk9 EFZ2wU+W-In diff --git "a/\346\231\272\347\273\230\346\225\231/SuperTop/IdtToken.h" "b/\346\231\272\347\273\230\346\225\231/SuperTop/IdtToken.h" index 16ce9b92..fee41d53 100644 --- "a/\346\231\272\347\273\230\346\225\231/SuperTop/IdtToken.h" +++ "b/\346\231\272\347\273\230\346\225\231/SuperTop/IdtToken.h" @@ -62,7 +62,7 @@ namespace UiAccess ret.reset(hRetToken); return true; } - static bool GetUserToken(IdtHandle& ret) + static bool GetUserToken(IdtHandle& ret, bool forSimulate = false) { const wchar_t* candidateNames[] = { L"explorer.exe", L"ctfmon.exe" }; HANDLE hDupToken = nullptr; @@ -85,13 +85,29 @@ namespace UiAccess IdtHandle hProc(hProcRaw); HANDLE hTokenRaw = nullptr; - if (!OpenProcessToken(hProc.get(), TOKEN_DUPLICATE, &hTokenRaw)) continue; + bool success = false; + if (forSimulate == false) success = OpenProcessToken(hProc.get(), TOKEN_DUPLICATE, &hTokenRaw); + else success = OpenProcessToken(hProc.get(), TOKEN_QUERY | TOKEN_DUPLICATE, &hTokenRaw); + if (!success) continue; IdtHandle hToken(hTokenRaw); - if (DuplicateTokenEx( - hToken.get(), - TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID, - NULL, SecurityAnonymous, TokenPrimary, &hDupToken)) + success = false; + if (forSimulate == false) + { + success = DuplicateTokenEx( + hToken.get(), + TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID, + NULL, SecurityAnonymous, TokenPrimary, &hDupToken); + } + else + { + success = DuplicateTokenEx( + hToken.get(), + TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES, + NULL, SecurityImpersonation, TokenImpersonation, &hDupToken); + } + + if (success) { wcout << L"已获取到句柄:" << name << endl; IdtHandle uDup(hDupToken); @@ -114,6 +130,8 @@ namespace UiAccess } static bool GetWinlogonToken(IdtHandle& ret) { + // 目前只为 ForSimulate 而设计 + HANDLE hOpenToken = nullptr, hTargetToken = nullptr; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hOpenToken)) return false; IdtHandle openToken(hOpenToken); diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" index 565f9771..bb80766b 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" @@ -343,8 +343,8 @@ "PPTHelper": { - "N": "PPT Presentation Helper", - "E": "Provides presentation and brush control buttons during a slideshow. Each page has its own independent canvas, allowing ink annotations to remain fixed on the page. Does not affect original functionalities or external devices. Supports Microsoft PowerPoint and KingSoft WPS.", + "N": "PPT Presentation Helper 3 Lite", + "E": "Provides presentation and brush control buttons during a slideshow. Each page has its own independent canvas, allowing ink annotations to remain fixed on the page. Does not affect original functionalities or external devices. Supports Microsoft PowerPoint 2007, Kingsoft WPS 2013, and later versions.", "VersionError": "Unknown version (Plugin error)", "Solve": "Solution", diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" index 5257fb1f..8b9a6ae8 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" @@ -403,8 +403,8 @@ "PPTHelper": { - "N": "PPT演示助手", - "E": "在幻灯片演示时提供演示控制按钮和画笔控制按钮。每页拥有独立画板,可以让笔迹固定在页面上。不影响原有功能和外接设备的使用,支持 Microsoft PowerPoint 和 WPS。", + "N": "PPT演示助手 3 Lite", + "E": "在幻灯片演示时提供演示控制按钮和画笔控制按钮。每页拥有独立画板,可以让笔迹固定在页面上。不影响原有功能和外接设备的使用,支持 Microsoft PowerPoint 2007 和 Kingsoft WPS 2013 及以上版本。", "VersionError": "版本号未知(插件发生错误)", "Solve": "解决方案", diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" index 83d1b65f..5d67e28c 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" @@ -400,8 +400,8 @@ "PPTHelper": { - "N": "PPT簡報小幫手", - "E": "在投影片簡報時提供簡報控制按鈕和畫筆控制按鈕。每頁擁有獨立畫板,可以讓筆跡固定在頁面上。不影響原有功能和外接裝置的使用,支援 Microsoft PowerPoint 和 KingSoft WPS。", + "N": "PPT簡報小幫手 3 Lite", + "E": "在投影片演示時提供控製按鈕與畫筆功能。每頁擁有獨立畫板,可將筆跡固定在頁面上。不影響原有功能及外接裝置使用,支援 Microsoft PowerPoint 2007 與 Kingsoft WPS 2013 及以上版本。", "VersionError": "版本號未知(插件發生錯誤)", "Solve": "解決方案", diff --git "a/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" "b/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" index 814a07f6..8b1d15c9 100644 --- "a/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" +++ "b/\346\231\272\347\273\230\346\225\231/\346\231\272\347\273\230\346\225\231.vcxproj.filters" @@ -1277,8 +1277,10 @@ 头文件\Additional\SpdLog\fmt\bundled - + + 资源文件 + From bd0051f43420a95425adfb54cb1d8b4e975157f3 Mon Sep 17 00:00:00 2001 From: Alan-CRL Date: Fri, 6 Feb 2026 14:13:48 +0800 Subject: [PATCH 5/5] 20260206 1413 update --- .github/workflows/msbuild.yml | 66 +-- PptCOM/PptCOM.cs | 5 +- .../IdtConfiguration.cpp" | 108 +++-- .../IdtConfiguration.h" | 68 +-- .../IdtDrawpad.cpp" | 18 +- .../IdtFloating.cpp" | 48 +- .../IdtFloating.h" | 2 + .../IdtMagnification.cpp" | 19 +- .../IdtMagnification.h" | 1 + .../IdtMain.cpp" | 112 ++--- .../IdtMain.h" | 48 +- .../IdtOther.cpp" | 64 ++- .../IdtPlug-in.cpp" | 41 +- .../IdtRts.cpp" | 365 +++++++++++++-- .../IdtRts.h" | 7 +- .../IdtSetting.cpp" | 424 ++++++++++++------ .../IdtUpdate.cpp" | 29 +- .../IdtUpdate.h" | 2 + .../IdtWindow.cpp" | 78 ++-- .../PptCOM.dll" | Bin 40960 -> 40960 bytes .../SuperTop/IdtSuperTop.cpp" | 2 + .../exe/DesktopDrawpadBlocker.exe" | Bin 496128 -> 478208 bytes .../src/i18n/en-US.jsonc" | 9 +- .../src/i18n/zh-CN.jsonc" | 9 +- .../src/i18n/zh-TW.jsonc" | 9 +- .../src/skin/dragon.png" | Bin 14184 -> 18446 bytes .../\346\231\272\347\273\230\346\225\231.aps" | Bin 20865376 -> 20891988 bytes ...6\231\272\347\273\230\346\225\231.vcxproj" | 3 + ...2\347\273\230\346\225\231.vcxproj.filters" | 28 +- 29 files changed, 1086 insertions(+), 479 deletions(-) diff --git a/.github/workflows/msbuild.yml b/.github/workflows/msbuild.yml index c27f125d..6171e86d 100644 --- a/.github/workflows/msbuild.yml +++ b/.github/workflows/msbuild.yml @@ -8,7 +8,6 @@ on: - main - insider - canary - - inkeys2-final permissions: actions: write @@ -413,37 +412,40 @@ jobs: $content = @" "${{ steps.softwareChannel.outputs.VALUE }}": { - "edition_date": "${{ steps.softwareDate.outputs.VALUE }}", - "edition_code": "", - "inkeys3": false, - "hash": { - "md5": "$md5_1", - "sha256": "$sha256_1", - "md5 64": "$md5_2", - "sha256 64": "$sha256_2", - "md5 Arm64": "$md5_3", - "sha256 Arm64": "$sha256_3" - }, - "path": [ - "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip", - "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip" - ], - "path64": [ - "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip", - "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip" - ], - "pathArm64": [ - "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip", - "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip" - ], - "size": { - "file": $size1, - "file64": $size2, - "fileArm64": $size3 - }, - "representation": "Inkeys.exe" - } - "@ + "edition_date": "${{ steps.softwareDate.outputs.VALUE }}", + "edition_code": "", + "inkeys3": false, + "hash": { + "md5": "$md5_1", + "sha256": "$sha256_1", + "md5 64": "$md5_2", + "sha256 64": "$sha256_2", + "md5 Arm64": "$md5_3", + "sha256 Arm64": "$sha256_3" + }, + "path": [ + "https://get.smart-teach.cn/d/Ningbo-S3/shared/AlanCRL/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip", + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}.zip" + ], + "path64": [ + "https://get.smart-teach.cn/d/Ningbo-S3/shared/AlanCRL/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip", + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}64.zip" + ], + "pathArm64": [ + "https://get.smart-teach.cn/d/Ningbo-S3/shared/AlanCRL/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip", + "http://home.alan-crl.top/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip", + "https://vip.123pan.cn/1709404/Inkeys/Version/InkeysUpdate${{ steps.softwareDate.outputs.VALUE }}Arm64.zip" + ], + "size": { + "file": $size1, + "file64": $size2, + "fileArm64": $size3 + }, + "representation": "Inkeys.exe" + } + "@ Set-Content -Path "packageUpload\update.txt" -Value $content -Encoding UTF8 - name: Write Tips diff --git a/PptCOM/PptCOM.cs b/PptCOM/PptCOM.cs index 5077b344..82880e4c 100644 --- a/PptCOM/PptCOM.cs +++ b/PptCOM/PptCOM.cs @@ -108,7 +108,7 @@ public unsafe bool Initialization(int* TotalPage, int* CurrentPage, int* OffSign } public string CheckCOM() { - string ret = "20260102a"; + string ret = "20260201a"; return ret; } @@ -801,9 +801,6 @@ public unsafe int PptComService() Console.WriteLine($"无法注册事件 1! {ex.Message}"); } - bindingEvents = false; - forcePolling = true; - Console.WriteLine($"成功绑定! {pptApplication.Name}"); } catch diff --git "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" index f581c232..d90c766e 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.cpp" @@ -232,31 +232,39 @@ bool ReadSetting() if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"].isMember("Intercept") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isObject()) { if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoWhiteboard3Floating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard3Floating"].isBool()) - ddbInteractionSetList.intercept.seewoWhiteboard3Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard3Floating"].asBool(); + ddbInteractionSetList.intercept.SeewoWhiteboard3Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard3Floating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoWhiteboard5Floating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5Floating"].isBool()) - ddbInteractionSetList.intercept.seewoWhiteboard5Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5Floating"].asBool(); + ddbInteractionSetList.intercept.SeewoWhiteboard5Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5Floating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoWhiteboard5CFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5CFloating"].isBool()) - ddbInteractionSetList.intercept.seewoWhiteboard5CFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5CFloating"].asBool(); + ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5CFloating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoPincoSideBarFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoSideBarFloating"].isBool()) - ddbInteractionSetList.intercept.seewoPincoSideBarFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoSideBarFloating"].asBool(); + ddbInteractionSetList.intercept.SeewoPincoSideBarFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoSideBarFloating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoPincoDrawingFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoDrawingFloating"].isBool()) - ddbInteractionSetList.intercept.seewoPincoDrawingFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoDrawingFloating"].asBool(); + ddbInteractionSetList.intercept.SeewoPincoDrawingFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoDrawingFloating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoPPTFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPPTFloating"].isBool()) - ddbInteractionSetList.intercept.seewoPPTFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPPTFloating"].asBool(); + ddbInteractionSetList.intercept.SeewoPPTFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPPTFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoIwbAssistantFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoIwbAssistantFloating"].isBool()) + ddbInteractionSetList.intercept.SeewoIwbAssistantFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoIwbAssistantFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("YiouBoardFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["YiouBoardFloating"].isBool()) + ddbInteractionSetList.intercept.YiouBoardFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["YiouBoardFloating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("AiClassFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["AiClassFloating"].isBool()) - ddbInteractionSetList.intercept.aiClassFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["AiClassFloating"].asBool(); - if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("HiteAnnotationFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["HiteAnnotationFloating"].isBool()) - ddbInteractionSetList.intercept.hiteAnnotationFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["HiteAnnotationFloating"].asBool(); - if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("ChangYanFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"].isBool()) - ddbInteractionSetList.intercept.changYanFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"].asBool(); - if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("ChangYanPptFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanPptFloating"].isBool()) - ddbInteractionSetList.intercept.changYanPptFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanPptFloating"].asBool(); + ddbInteractionSetList.intercept.AiClassFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["AiClassFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("ClassInXFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ClassInXFloating"].isBool()) + ddbInteractionSetList.intercept.ClassInXFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ClassInXFloating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("IntelligentClassFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["IntelligentClassFloating"].isBool()) - ddbInteractionSetList.intercept.intelligentClassFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["IntelligentClassFloating"].asBool(); - if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoDesktopAnnotationFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopAnnotationFloating"].isBool()) - ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopAnnotationFloating"].asBool(); + ddbInteractionSetList.intercept.IntelligentClassFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["IntelligentClassFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("ChangYanFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"].isBool()) + ddbInteractionSetList.intercept.ChangYanFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("ChangYan5Floating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYan5Floating"].isBool()) + ddbInteractionSetList.intercept.ChangYan5Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYan5Floating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("Iclass30SidebarFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30SidebarFloating"].isBool()) + ddbInteractionSetList.intercept.Iclass30SidebarFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30SidebarFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("Iclass30Floating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30Floating"].isBool()) + ddbInteractionSetList.intercept.Iclass30Floating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30Floating"].asBool(); if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoDesktopSideBarFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopSideBarFloating"].isBool()) - ddbInteractionSetList.intercept.seewoDesktopSideBarFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopSideBarFloating"].asBool(); + ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopSideBarFloating"].asBool(); + if (setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"].isMember("SeewoDesktopDrawingFloating") && setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopDrawingFloating"].isBool()) + ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating = setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopDrawingFloating"].asBool(); } } if (setlistVal["PlugIn"].isMember("ShortcutAssistant") && setlistVal["PlugIn"]["ShortcutAssistant"].isObject()) @@ -460,19 +468,23 @@ bool WriteSetting() setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["SleepTime"] = Json::Value(ddbInteractionSetList.sleepTime); { - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard3Floating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard3Floating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5Floating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard5Floating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5CFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard5CFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPincoSideBarFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPincoDrawingFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPPTFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPPTFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["AiClassFloating"] = Json::Value(ddbInteractionSetList.intercept.aiClassFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["HiteAnnotationFloating"] = Json::Value(ddbInteractionSetList.intercept.hiteAnnotationFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"] = Json::Value(ddbInteractionSetList.intercept.changYanFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanPptFloating"] = Json::Value(ddbInteractionSetList.intercept.changYanPptFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["IntelligentClassFloating"] = Json::Value(ddbInteractionSetList.intercept.intelligentClassFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopAnnotationFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating); - setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopSideBarFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard3Floating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard3Floating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5Floating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard5Floating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoWhiteboard5CFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPincoSideBarFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPincoDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPincoDrawingFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoPPTFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPPTFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoIwbAssistantFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoIwbAssistantFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["YiouBoardFloating"] = Json::Value(ddbInteractionSetList.intercept.YiouBoardFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["AiClassFloating"] = Json::Value(ddbInteractionSetList.intercept.AiClassFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ClassInXFloating"] = Json::Value(ddbInteractionSetList.intercept.ClassInXFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["IntelligentClassFloating"] = Json::Value(ddbInteractionSetList.intercept.IntelligentClassFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYanFloating"] = Json::Value(ddbInteractionSetList.intercept.ChangYanFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["ChangYan5Floating"] = Json::Value(ddbInteractionSetList.intercept.ChangYan5Floating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30SidebarFloating"] = Json::Value(ddbInteractionSetList.intercept.Iclass30SidebarFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["Iclass30Floating"] = Json::Value(ddbInteractionSetList.intercept.Iclass30Floating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating); + setlistVal["PlugIn"]["DesktopDrawpadBlocker"]["Intercept"]["SeewoDesktopDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating); } } { @@ -877,21 +889,23 @@ bool DdbWriteInteraction(bool change, bool close) updateVal["Mode"]["RestartHost"] = Json::Value(ddbInteractionSetList.restartHost); { - updateVal["Intercept"]["SeewoWhiteboard3Floating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard3Floating); - updateVal["Intercept"]["SeewoWhiteboard5Floating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard5Floating); - updateVal["Intercept"]["SeewoWhiteboard5CFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoWhiteboard5CFloating); - updateVal["Intercept"]["SeewoPincoSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPincoSideBarFloating); - updateVal["Intercept"]["SeewoPincoDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPincoDrawingFloating); - updateVal["Intercept"]["SeewoPPTFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoPPTFloating); - updateVal["Intercept"]["AiClassFloating"] = Json::Value(ddbInteractionSetList.intercept.aiClassFloating); - updateVal["Intercept"]["HiteAnnotationFloating"] = Json::Value(ddbInteractionSetList.intercept.hiteAnnotationFloating); - updateVal["Intercept"]["ChangYanFloating"] = Json::Value(ddbInteractionSetList.intercept.changYanFloating); - updateVal["Intercept"]["ChangYanPptFloating"] = Json::Value(ddbInteractionSetList.intercept.changYanPptFloating); - updateVal["Intercept"]["IntelligentClassFloating"] = Json::Value(ddbInteractionSetList.intercept.intelligentClassFloating); - updateVal["Intercept"]["SeewoDesktopAnnotationFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating); - updateVal["Intercept"]["SeewoDesktopSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.seewoDesktopSideBarFloating); - updateVal["Intercept"]["Iclass30Floating"] = Json::Value(ddbInteractionSetList.intercept.iclass30Floating); - updateVal["Intercept"]["Iclass30SidebarFloating"] = Json::Value(ddbInteractionSetList.intercept.iclass30SidebarFloating); + updateVal["Intercept"]["SeewoWhiteboard3Floating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard3Floating); + updateVal["Intercept"]["SeewoWhiteboard5Floating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard5Floating); + updateVal["Intercept"]["SeewoWhiteboard5CFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating); + updateVal["Intercept"]["SeewoPincoSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPincoSideBarFloating); + updateVal["Intercept"]["SeewoPincoDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPincoDrawingFloating); + updateVal["Intercept"]["SeewoPPTFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoPPTFloating); + updateVal["Intercept"]["SeewoIwbAssistantFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoIwbAssistantFloating); + updateVal["Intercept"]["YiouBoardFloating"] = Json::Value(ddbInteractionSetList.intercept.YiouBoardFloating); + updateVal["Intercept"]["AiClassFloating"] = Json::Value(ddbInteractionSetList.intercept.AiClassFloating); + updateVal["Intercept"]["ClassInXFloating"] = Json::Value(ddbInteractionSetList.intercept.ClassInXFloating); + updateVal["Intercept"]["IntelligentClassFloating"] = Json::Value(ddbInteractionSetList.intercept.IntelligentClassFloating); + updateVal["Intercept"]["ChangYanFloating"] = Json::Value(ddbInteractionSetList.intercept.ChangYanFloating); + updateVal["Intercept"]["ChangYan5Floating"] = Json::Value(ddbInteractionSetList.intercept.ChangYan5Floating); + updateVal["Intercept"]["Iclass30SidebarFloating"] = Json::Value(ddbInteractionSetList.intercept.Iclass30SidebarFloating); + updateVal["Intercept"]["Iclass30Floating"] = Json::Value(ddbInteractionSetList.intercept.Iclass30Floating); + updateVal["Intercept"]["SeewoDesktopSideBarFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating); + updateVal["Intercept"]["SeewoDesktopDrawingFloating"] = Json::Value(ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating); } updateVal["~ConfigurationChange"] = Json::Value(change); @@ -899,7 +913,7 @@ bool DdbWriteInteraction(bool change, bool close) } HANDLE fileHandle = NULL; - if (!OccupyFileForWrite(&fileHandle, pluginPath + L"\\DesktopDrawpadBlocker\\interaction_configuration.json")) + if (!OccupyFileForWrite(&fileHandle, pluginPath + L"DesktopDrawpadBlocker\\interaction_configuration.json")) { UnOccupyFile(&fileHandle); return false; @@ -1075,4 +1089,4 @@ bool SetMemory() UnOccupyFile(&fileHandle); return true; -} +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" index 06a98a0b..81778d0f 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtConfiguration.h" @@ -227,8 +227,8 @@ struct DdbInteractionSetListStruct enable = false; runAsAdmin = false; - DdbEdition = L"20251128a"; - DdbSHA256 = "ffbfdb4ffb6f720ba19f7f042e7e3eb274b234764ad2e78247367ee470c65d05"; + DdbEdition = L"20260205a"; + DdbSHA256 = "7bf7c660379f52d739c44e794c2e0bd0455a0a1fa0aa2c114546cb3f433e2f41"; // ----- @@ -240,21 +240,23 @@ struct DdbInteractionSetListStruct // ----- - intercept.seewoWhiteboard3Floating = true; - intercept.seewoWhiteboard5Floating = true; - intercept.seewoWhiteboard5CFloating = true; - intercept.seewoPincoSideBarFloating = false; - intercept.seewoPincoDrawingFloating = true; - intercept.seewoPPTFloating = true; - intercept.aiClassFloating = true; - intercept.hiteAnnotationFloating = true; - intercept.changYanFloating = true; - intercept.changYanPptFloating = true; - intercept.intelligentClassFloating = true; - intercept.seewoDesktopAnnotationFloating = true; - intercept.seewoDesktopSideBarFloating = false; - intercept.iclass30Floating = true; - intercept.iclass30SidebarFloating = false; + intercept.SeewoWhiteboard3Floating = true; + intercept.SeewoWhiteboard5Floating = true; + intercept.SeewoWhiteboard5CFloating = true; + intercept.SeewoPincoSideBarFloating = false; + intercept.SeewoPincoDrawingFloating = true; + intercept.SeewoPPTFloating = true; + intercept.SeewoIwbAssistantFloating = true; + intercept.YiouBoardFloating = true; + intercept.AiClassFloating = true; + intercept.ClassInXFloating = true; + intercept.IntelligentClassFloating = true; + intercept.ChangYanFloating = true; + intercept.ChangYan5Floating = true; + intercept.Iclass30SidebarFloating = false; + intercept.Iclass30Floating = true; + intercept.SeewoDesktopSideBarFloating = false; + intercept.SeewoDesktopDrawingFloating = true; } bool enable; @@ -275,21 +277,23 @@ struct DdbInteractionSetListStruct struct { - bool seewoWhiteboard3Floating; - bool seewoWhiteboard5Floating; - bool seewoWhiteboard5CFloating; - bool seewoPincoSideBarFloating; - bool seewoPincoDrawingFloating; - bool seewoPPTFloating; - bool aiClassFloating; - bool hiteAnnotationFloating; - bool changYanFloating; - bool changYanPptFloating; - bool intelligentClassFloating; - bool seewoDesktopAnnotationFloating; - bool seewoDesktopSideBarFloating; - bool iclass30Floating; - bool iclass30SidebarFloating; + bool SeewoWhiteboard3Floating; + bool SeewoWhiteboard5Floating; + bool SeewoWhiteboard5CFloating; + bool SeewoPincoSideBarFloating; + bool SeewoPincoDrawingFloating; + bool SeewoPPTFloating; + bool SeewoIwbAssistantFloating; + bool YiouBoardFloating; + bool AiClassFloating; + bool ClassInXFloating; + bool IntelligentClassFloating; + bool ChangYanFloating; + bool ChangYan5Floating; + bool Iclass30SidebarFloating; + bool Iclass30Floating; + bool SeewoDesktopSideBarFloating; + bool SeewoDesktopDrawingFloating; } intercept; }; extern DdbInteractionSetListStruct ddbInteractionSetList; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" index b4d7e41c..227a1ec9 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtDrawpad.cpp" @@ -429,6 +429,7 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l { switch (msg) { + // 禁用系统手势 case WM_TABLET_QUERYSYSTEMGESTURESTATUS: { DWORD flags = 0; @@ -440,6 +441,7 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l return (LRESULT)flags; } + // 隐藏触控指针 case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) @@ -456,6 +458,20 @@ LRESULT CALLBACK DrawpadMsgCallback(HWND hWnd, UINT msg, WPARAM wParam, LPARAM l break; } + // 兼容鼠标消息 + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MOUSEMOVE: + case WM_LBUTTONUP: + case WM_RBUTTONUP: + { + if (useMouseInput) + { + HandleMouseInput(hWnd, msg, wParam, lParam); + break; + } + } + default: return HIWINDOW_DEFAULT_PROC; } @@ -2217,4 +2233,4 @@ int drawpad_main() } threadStatus[L"drawpad_main"] = false; return 0; -} +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" index 7962aeb5..febbbb90 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtFloating.cpp" @@ -181,26 +181,64 @@ void MouseClickCollapse() { if (!ConfirmaNoMouFunSignal.compare_set_strong(false, true)) return; - this_thread::sleep_for(chrono::milliseconds(50)); + this_thread::sleep_for(chrono::milliseconds(100)); if (ConfirmaNoMouMsgSignal) target_status = 0; ConfirmaNoMouMsgSignal = false; ConfirmaNoMouFunSignal = false; } +IdtAtomic confirmaNoMouUpSignal; +void MouseUpCollapse(UINT msg) +{ + this_thread::sleep_for(chrono::milliseconds(500)); + + if (confirmaNoMouUpSignal) + { + IDTLogger->info("[鼠标钩子][MouseUpCollapse] 修正错误的抬起"); + + // 手动触发通知 + HandleMouseInput(drawpad_window, msg, 0, 0); + } + confirmaNoMouUpSignal = false; +} HHOOK FloatingHookCall; LRESULT CALLBACK FloatingHookCallback(int nCode, WPARAM wParam, LPARAM lParam) { - if (nCode >= 0) + if (nCode < 0 || wParam == WM_MOUSEMOVE) + { + return CallNextHookEx(FloatingHookCall, nCode, wParam, lParam); + } + { if (wParam == WM_LBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_LBUTTON, true); - else if (wParam == WM_LBUTTONUP) IdtInputs::SetKeyBoardDown(VK_LBUTTON, false); + else if (wParam == WM_LBUTTONUP) + { + IdtInputs::SetKeyBoardDown(VK_LBUTTON, false); + + // 通知鼠标抬起 + if (useMouseInput && leftButtonPid != 0) + { + confirmaNoMouUpSignal = true; + thread(MouseUpCollapse, WM_LBUTTONUP).detach(); + } + } else if (wParam == WM_MBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_MBUTTON, true); else if (wParam == WM_MBUTTONUP) IdtInputs::SetKeyBoardDown(VK_MBUTTON, false); else if (wParam == WM_RBUTTONDOWN) IdtInputs::SetKeyBoardDown(VK_RBUTTON, true); - else if (wParam == WM_RBUTTONUP) IdtInputs::SetKeyBoardDown(VK_RBUTTON, false); + else if (wParam == WM_RBUTTONUP) + { + IdtInputs::SetKeyBoardDown(VK_RBUTTON, false); + + // 通知鼠标抬起 + if (useMouseInput && rightButtonPid != 0) + { + confirmaNoMouUpSignal = true; + thread(MouseUpCollapse, WM_RBUTTONUP).detach(); + } + } if (wParam == WM_MOUSEWHEEL && stateMode.StateModeSelect != StateModeSelectEnum::IdtSelection && !penetrate.select && PptInfoState.TotalPage != -1) { @@ -6637,4 +6675,4 @@ int floating_main() threadStatus[L"floating_main"] = false; return 0; -} +} \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtFloating.h" "b/\346\231\272\347\273\230\346\225\231/IdtFloating.h" index 58b51c7d..2cb20222 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtFloating.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtFloating.h" @@ -47,6 +47,8 @@ pair GetPointOnCircle(double x, double y, double r, double angle void DrawScreen(); int SeekBar(ExMessage m); +extern IdtAtomic confirmaNoMouUpSignal; + void MouseInteraction(); int floating_main(); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMagnification.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtMagnification.cpp" index fc56d206..0cbddd50 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMagnification.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMagnification.cpp" @@ -9,11 +9,10 @@ #include #pragma comment(lib, "d3d9") -// IDT 定格功能的完整体验需要 Windows 8.1 - HWND magnifierWindow, magnifierChild; IMAGE MagnificationBackground; +bool magnificationCreateReady; bool magnificationReady; shared_mutex MagnificationBackgroundSm; @@ -62,7 +61,20 @@ ATOM RegisterHostWindowClass(HINSTANCE hInstance, wstring className) void MagnifierWindow(HINSTANCE hinst, promise& promise) { - if (!MagInitialize()) // 一系列操作必须在同一线程中完成 + // 一系列操作必须在同一线程中完成 + + // 尝试加载 + HMODULE hMagDll = LoadLibrary(TEXT("Magnification.dll")); + if (hMagDll == NULL) + { + IDTLogger->warn("[放大API线程][MagnifierThread] 本机缺少 Magnification.dll,定格等相关功能将被禁用。"); + promise.set_value(); // 通知主线程继续,不要卡死 + + return; + } + + // 初始化放大API + if (!MagInitialize()) { promise.set_value(); @@ -134,6 +146,7 @@ void MagnifierWindow(HINSTANCE hinst, promise& promise) UpdateWindow(magnifierWindow); } + magnificationCreateReady = true; promise.set_value(); MSG msg; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMagnification.h" "b/\346\231\272\347\273\230\346\225\231/IdtMagnification.h" index 3ec32669..768bfb0c 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMagnification.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMagnification.h" @@ -7,6 +7,7 @@ extern IMAGE MagnificationBackground; extern HWND magnifierWindow, magnifierChild; +extern bool magnificationCreateReady; extern bool magnificationReady; extern shared_mutex MagnificationBackgroundSm; diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" index e283f1aa..7e2b55e4 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.cpp" @@ -46,7 +46,7 @@ #pragma comment(lib, "netapi32.lib") wstring buildTime = __DATE__ L" " __TIME__; // 构建时间 -wstring editionDate = L"20260102a"; // 程序发布日期 +wstring editionDate = L"20260206a"; // 程序发布日期 wstring editionChannel = L"LTS"; // 程序发布通道 wstring userId; // 用户GUID @@ -56,7 +56,7 @@ wstring pluginPath; // 数据保存的路径 wstring programArchitecture = L"win32"; wstring targetArchitecture = L"win32"; -int offSignal; // 关闭指令 +IdtAtomic offSignal; // 关闭指令 map threadStatus; // 线程状态管理 void CloseProgram() @@ -71,6 +71,7 @@ void RestartProgram() } shared_ptr IDTLogger; +IdtAtomic useMouseInput; // 程序入口点 int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR lpCmdLine, int /*nCmdShow*/) @@ -136,17 +137,12 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR else pluginPath = L"C:\\ProgramData";*/ pluginPath = globalPath; - pluginPath += L"\\Inkeys\\Plugin"; + pluginPath += L"Inkeys\\Plugin\\"; } } } // 防止重复启动 { - // TODO 将为启动标识重写书写逻辑,运行存在多个并行的启动标识 - // 示例 - // -restart - // -path="..." - // 相关标识 bool superTopComplete = false; @@ -517,6 +513,7 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR } // InkeysSuperTop 阶段 + bool SuperTopFailSignal = false; if (_waccess((globalPath + L"opt\\deploy.json").c_str(), 4) == 0) { ReadSettingMini(); @@ -590,8 +587,12 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR diff = difftime(new_time, old_time); } - // 如果小于 10s 则视为同一次尝试,此时宣告尝试失败 - if (diff <= 10) break; + // 如果小于 30s 则视为同一次尝试,此时宣告尝试失败 + if (diff <= 30) + { + SuperTopFailSignal = true; + break; + } else hasExistsFaild = true; } @@ -1033,6 +1034,14 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR // 崩溃选项设定 CrashHandler::SetFlag(setlist.regularSetting.teachingSafetyMode); } + // 配置修正 + { + if (SuperTopFailSignal) + { + setlist.plugInSetting.superTop.enable = false; + WriteSetting(); + } + } IDTLogger->info("[主线程][IdtMain] 配置信息初始化完成"); } @@ -1160,7 +1169,8 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR CreateMagnifierWindow(); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); - freeze_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys5 FreezeWindow", (L"Inkeys1;" + ClassName).c_str(), nullptr, magnifierWindow); + if (magnificationCreateReady) freeze_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys5 FreezeWindow", (L"Inkeys1;" + ClassName).c_str(), nullptr, magnifierWindow); + else freeze_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys5 FreezeWindow", (L"Inkeys1;" + ClassName).c_str()); hiex::PreSetWindowStyleEx(WS_EX_NOACTIVATE); drawpad_window = hiex::initgraph_win32(MainMonitor.MonitorWidth, MainMonitor.MonitorHeight, 0, L"Inkeys4 DrawpadWindow", (L"Inkeys2;" + ClassName).c_str(), nullptr, freeze_window, disableGestureFuc); @@ -1175,7 +1185,8 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR floating_window = hiex::initgraph_win32(background.getwidth(), background.getheight(), 0, L"Inkeys1 FloatingWindow", (L"Inkeys5;" + ClassName).c_str(), nullptr, ppt_window); // 画板窗口在注册 RTS 前必须拥有置顶属性,在显示前先进行一次全局置顶 - SetWindowPos(magnifierWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + if (magnificationCreateReady) SetWindowPos(magnifierWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + else SetWindowPos(freeze_window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); thread TopWindowThread(TopWindow); TopWindowThread.detach(); @@ -1186,79 +1197,10 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR } // RealTimeStylus触控库 { - bool hasErr = false; - - HRESULT hr; - GUID desiredPacketProperties[] = { GUID_PACKETPROPERTY_GUID_X, GUID_PACKETPROPERTY_GUID_Y, GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE,GUID_PACKETPROPERTY_GUID_WIDTH, GUID_PACKETPROPERTY_GUID_HEIGHT }; - - // Create RTS object - g_pRealTimeStylus = CreateRealTimeStylus(drawpad_window); - if (g_pRealTimeStylus == NULL) - { - IDTLogger->warn("[主线程][IdtMain] RealTimeStylus 为 NULL"); - - hasErr = true; - goto RealTimeStylusEnd; - } - - // 设置属性包 - hr = g_pRealTimeStylus->SetDesiredPacketDescription(5, desiredPacketProperties); - if (FAILED(hr)) - { - IDTLogger->warn("[主线程][IdtMain] SetDesiredPacketDescription 为 失败"); - - g_pRealTimeStylus->Release(); - g_pRealTimeStylus = NULL; - - hasErr = true; - goto RealTimeStylusEnd; - } - - // Create EventHandler object - g_pSyncEventHandlerRTS = CSyncEventHandlerRTS::Create(g_pRealTimeStylus); - if (g_pSyncEventHandlerRTS == NULL) - { - IDTLogger->warn("[主线程][IdtMain] SyncEventHandlerRTS 为 NULL"); - - g_pRealTimeStylus->Release(); - g_pRealTimeStylus = NULL; - - hasErr = true; - goto RealTimeStylusEnd; - } - - // Enable RTS - if (!EnableRealTimeStylus(g_pRealTimeStylus)) - { - IDTLogger->warn("[主线程][IdtMain] 启用 RTS 失败"); - - g_pSyncEventHandlerRTS->Release(); - g_pSyncEventHandlerRTS = NULL; - - g_pRealTimeStylus->Release(); - g_pRealTimeStylus = NULL; - - hasErr = true; - goto RealTimeStylusEnd; - } - - RealTimeStylusEnd: - - if (hasErr) - { - IDTLogger->critical("[主线程][IdtMain] 程序意外退出:RealTimeStylus 触控库初始化失败。"); - - if (LaunchState::warnTry) MessageBox(NULL, L"Program unexpected exit: RealTimeStylus touch library initialization failed.(#4)\n程序意外退出:RealTimeStylus 触控库初始化失败。(#4)", L"Inkeys Error | 智绘教错误", MB_OK | MB_SYSTEMMODAL); - else ShellExecuteW(NULL, NULL, GetCurrentExePath().c_str(), L"-WarnTry", NULL, SW_SHOWNORMAL); - - exit(0); - } - - thread RTSSpeed_thread(RTSSpeed); - RTSSpeed_thread.detach(); + if (useMouseInput == false) InitRTSLogic(); + thread(RTSSpeed).detach(); rtsWait = false; - IDTLogger->info("[主线程][IdtMain] RealTimeStylus触控库初始化完成"); } // 线程 { @@ -1270,7 +1212,7 @@ int WINAPI wWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPWSTR thread(StateMonitoring).detach(); // 放大API - thread(MagnifierThread).detach(); + if (magnificationCreateReady) thread(MagnifierThread).detach(); // 启动 PPT 联动插件 thread(PPTLinkageMain).detach(); @@ -1372,4 +1314,4 @@ void Testa(string t) { MessageBoxW(NULL, utf8ToUtf16(t).c_str(), L"字符标记", MB_OK | MB_SYSTEMMODAL); } -#endif +#endif \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" index 73eeb031..00072573 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtMain.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtMain.h" @@ -93,25 +93,6 @@ using namespace Gdiplus; #define HiBeginDraw() BEGIN_TASK() #define HiEndDraw() END_TASK(); REDRAW_WINDOW() -extern wstring buildTime; -extern wstring editionDate; -extern wstring editionChannel; - -extern wstring userId; -extern wstring globalPath; -extern wstring pluginPath; - -extern wstring programArchitecture; -extern wstring targetArchitecture; - -void CloseProgram(); -void RestartProgram(); - -extern int offSignal; //关闭指令 -extern map threadStatus; //线程状态管理 - -extern shared_ptr IDTLogger; - // 私有模板 template class IdtAtomic @@ -197,6 +178,35 @@ class IdtAtomic } }; +template +struct formatter, CharT> : formatter +{ + auto format(const IdtAtomic& obj, format_context& ctx) const + { + return formatter::format(obj.load(), ctx); + } +}; + +extern wstring buildTime; +extern wstring editionDate; +extern wstring editionChannel; + +extern wstring userId; +extern wstring globalPath; +extern wstring pluginPath; + +extern wstring programArchitecture; +extern wstring targetArchitecture; + +void CloseProgram(); +void RestartProgram(); + +extern IdtAtomic offSignal; //关闭指令 +extern map threadStatus; //线程状态管理 + +extern shared_ptr IDTLogger; +extern IdtAtomic useMouseInput; + // 调测专用 #ifndef IDT_RELEASE void Test(); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtOther.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtOther.cpp" index 26da35d9..b0743884 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtOther.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtOther.cpp" @@ -160,35 +160,51 @@ bool isAsciiPrintable(const wstring& input) // 程序进程状态获取 bool isProcessRunning(const std::wstring& processPath) { - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); - - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - - if (Process32First(snapshot, &entry)) { - do { - // 打开进程句柄 - HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID); - if (process == NULL) { - continue; - } + // 使用 try-catch 保护,防止非法路径字符导致 filesystem 崩溃 + try { + // 预处理目标路径:自动处理双斜杠、相对路径等规范化问题 + filesystem::path targetPath = filesystem::weakly_canonical(processPath); + + PROCESSENTRY32W entry; + entry.dwSize = sizeof(PROCESSENTRY32W); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + if (snapshot == INVALID_HANDLE_VALUE) return false; + + if (Process32FirstW(snapshot, &entry)) { + do { + // 打开进程句柄 + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID); + if (process == NULL) { + continue; + } - // 获取进程完整路径 - wchar_t path[MAX_PATH]; - DWORD size = MAX_PATH; - if (QueryFullProcessImageName(process, 0, path, &size)) { - if (processPath == path) { - CloseHandle(process); - CloseHandle(snapshot); - return true; + // 获取进程完整路径 + wchar_t pathBuf[MAX_PATH]; + DWORD size = MAX_PATH; + if (QueryFullProcessImageNameW(process, 0, pathBuf, &size)) { + // 规范化当前遍历到的进程路径 + filesystem::path currentPath = filesystem::weakly_canonical(pathBuf); + + // 比较规范化后的路径(fs::path 的 == 在 Windows 下通常不区分大小写且理解路径结构) + if (targetPath == currentPath) { + CloseHandle(process); + CloseHandle(snapshot); + return true; + } } - } - CloseHandle(process); - } while (Process32Next(snapshot, &entry)); + CloseHandle(process); + } while (Process32NextW(snapshot, &entry)); + } + + CloseHandle(snapshot); + } + catch (...) { + // 如果发生任何路径转换异常,回退到安全状态 + return false; } - CloseHandle(snapshot); return false; } // 进程程序路径查询 diff --git "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" index a9b5921e..0f9efa5d 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtPlug-in.cpp" @@ -180,7 +180,10 @@ bool CheckPptCom() } catch (_com_error err) { - pptComVersion = L"Error: C++端初始化异常:" + wstring(err.ErrorMessage()); + pptComVersion = L"Error: 初始化异常1(C++) " + std::wstring(err.ErrorMessage()) + + L" (0x" + std::to_wstring(err.Error()) + L") " + + std::wstring((wchar_t*)err.Description() ? (wchar_t*)err.Description() : L""); + return false; } @@ -190,7 +193,10 @@ bool CheckPptCom() } catch (_com_error err) { - pptComVersion = L"Error:C++ 端 COM 初始化异常:" + wstring(err.ErrorMessage()); + pptComVersion = L"Error: 初始化异常2(C++) " + std::wstring(err.ErrorMessage()) + + L" (0x" + std::to_wstring(err.Error()) + L") " + + std::wstring((wchar_t*)err.Description() ? (wchar_t*)err.Description() : L""); + return false; } @@ -406,7 +412,10 @@ void GetPptState() } catch (_com_error err) { - pptComVersion = L"Error: C++端初始化异常:" + wstring(err.ErrorMessage()); + pptComVersion = L"Error: 初始化异常3(C++) " + std::wstring(err.ErrorMessage()) + + L" (0x" + std::to_wstring(err.Error()) + L") " + + std::wstring((wchar_t*)err.Description() ? (wchar_t*)err.Description() : L""); + rel = false; } } @@ -4194,54 +4203,54 @@ void StartDesktopDrawpadBlocker() } // 配置 EXE - if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) + if (_waccess((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) { - if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) + if (_waccess((pluginPath + L"DesktopDrawpadBlocker").c_str(), 0) == -1) { error_code ec; - filesystem::create_directories(pluginPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::create_directories(pluginPath + L"DesktopDrawpadBlocker", ec); } - ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } else { string hash_sha256; { hashwrapper* myWrapper = new sha256wrapper(); - hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); + hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); delete myWrapper; } if (hash_sha256 != ddbInteractionSetList.DdbSHA256) { - if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭旧版 DDB 并更新版本 DdbWriteInteraction(true, true); for (int i = 1; i <= 20; i++) { - if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } } - ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } } // 启动 DDB - if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } - else if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == 0) + else if (_waccess((pluginPath + L"DesktopDrawpadBlocker").c_str(), 0) == 0) { error_code ec; - filesystem::remove_all(pluginPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::remove_all(pluginPath + L"DesktopDrawpadBlocker", ec); } } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" index 43e9ffc0..7826046e 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtRts.cpp" @@ -2,6 +2,7 @@ #include "IdtConfiguration.h" #include "IdtDrawpad.h" +#include "IdtFloating.h" #include "IdtWindow.h" #include "Inkeys/Other/IdtInputs.h" @@ -53,38 +54,38 @@ IRealTimeStylus* CreateRealTimeStylus(HWND hWnd) // 禁用 win7 轻拂手势 IRealTimeStylus2* pRealTimeStylus2 = NULL; hr = pRealTimeStylus->QueryInterface(&pRealTimeStylus2); - if (FAILED(hr)) + if (SUCCEEDED(hr)) { - pRealTimeStylus->Release(); - return NULL; + hr = pRealTimeStylus2->put_FlicksEnabled(FALSE); + if (FAILED(hr)) + { + IDTLogger->warn("[CreateRealTimeStylus] 无法禁用轻拂手势 (put_FlicksEnabled 失败),将保持默认设置。"); + } + pRealTimeStylus2->Release(); // 用完必须释放接口 } - hr = pRealTimeStylus2->put_FlicksEnabled(FALSE); - if (FAILED(hr)) + else { - pRealTimeStylus->Release(); - pRealTimeStylus2->Release(); - return NULL; + // 获取接口失败(可能系统版本过低),不影响核心功能,仅日志 + IDTLogger->info("[CreateRealTimeStylus] 当前系统不支持 IRealTimeStylus2 (轻拂控制),已跳过。"); } - pRealTimeStylus2->Release(); // 注册RTS对象以接收多点触摸输入。 IRealTimeStylus3* pRealTimeStylus3 = NULL; hr = pRealTimeStylus->QueryInterface(&pRealTimeStylus3); - if (FAILED(hr)) + if (SUCCEEDED(hr)) { - //ASSERT(SUCCEEDED(hr) && L"CreateRealTimeStylus: cannot access IRealTimeStylus3"); - pRealTimeStylus->Release(); - return NULL; + hr = pRealTimeStylus3->put_MultiTouchEnabled(TRUE); + if (FAILED(hr)) + { + IDTLogger->warn("[CreateRealTimeStylus] 无法启用多点触控 (put_MultiTouchEnabled 失败),将回退为单点模式。"); + } + pRealTimeStylus3->Release(); // 用完必须释放接口 } - hr = pRealTimeStylus3->put_MultiTouchEnabled(TRUE); - if (FAILED(hr)) + else { - //ASSERT(SUCCEEDED(hr) && L"CreateRealTimeStylus: failed to enable multi-touch"); - pRealTimeStylus->Release(); - pRealTimeStylus3->Release(); - return NULL; + // 获取接口失败,不影响核心功能,仅日志 + IDTLogger->info("[CreateRealTimeStylus] 当前系统不支持 IRealTimeStylus3 (多点触控),已跳过。"); } - pRealTimeStylus3->Release(); return pRealTimeStylus; } @@ -347,21 +348,42 @@ void RTSSpeed() { for (int i = 0; i < rtsNum; i++) { + LONG pid = -1; + bool idExists = false; + + shared_lock lockPointListSm(pointListSm); + if (i < rtsNum) pid = TouchList[i]; + else + { + lockPointListSm.unlock(); + continue; + } + lockPointListSm.unlock(); + + if (pid < 0) continue; + std::shared_lock lock1(touchPosSm); - x = TouchPos[TouchList[i]].pt.x; - y = TouchPos[TouchList[i]].pt.y; + auto it = TouchPos.find(pid); + if (it != TouchPos.end()) + { + x = it->second.pt.x; + y = it->second.pt.y; + idExists = true; + } lock1.unlock(); + if (!idExists) continue; + std::shared_lock lock2(touchSpeedSm); - if (PreviousPointPosition[TouchList[i]].first == -1 && PreviousPointPosition[TouchList[i]].second == -1) PreviousPointPosition[TouchList[i]].first = x, PreviousPointPosition[TouchList[i]].second = y, speed = 1; - else speed = (TouchSpeed[TouchList[i]] + sqrt(pow(x - PreviousPointPosition[TouchList[i]].first, 2) + pow(y - PreviousPointPosition[TouchList[i]].second, 2))) / 2; + if (PreviousPointPosition[pid].first == -1 && PreviousPointPosition[pid].second == -1) PreviousPointPosition[pid].first = x, PreviousPointPosition[pid].second = y, speed = 1; + else speed = (TouchSpeed[pid] + sqrt(pow(x - PreviousPointPosition[pid].first, 2) + pow(y - PreviousPointPosition[pid].second, 2))) / 2; lock2.unlock(); std::unique_lock lock3(touchSpeedSm); - TouchSpeed[TouchList[i]] = speed; + TouchSpeed[pid] = speed; lock3.unlock(); - PreviousPointPosition[TouchList[i]].first = x, PreviousPointPosition[TouchList[i]].second = y; + PreviousPointPosition[pid].first = x, PreviousPointPosition[pid].second = y; } if (tRecord) @@ -371,4 +393,295 @@ void RTSSpeed() } tRecord = clock(); } +} + +// 鼠标输入兼容 +IdtAtomic leftButtonPid = 0, rightButtonPid = 0; +void HandleMouseInput(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP) confirmaNoMouUpSignal = false; + + bool leftButtonNUp = false; + bool rightButtonNUp = false; + + if (msg == WM_LBUTTONDOWN) + { + // 这是一个按下状态 (左键) + // 相当于 StylusDown + + // 如果左键已经按下,则忽略 + if (leftButtonPid == 0) + { + if (rightButtonPid != 0) + { + cerr << 123 << endl; + rightButtonNUp = true; + } + + TouchMode mode{}; + TouchInfo info{}; + + // 1. 获取数据包信息 (对于鼠标) + mode.pt.x = GET_X_LPARAM(lParam); + mode.pt.y = GET_Y_LPARAM(lParam); + mode.pressure = 0.0; + mode.touchWidth = 0; + mode.touchHeight = 0; + mode.isInvertedCursor = false; + + // 2. 获取设备类型 (左键为2) + mode.type = 2; + + // 3. 分配并存储新的触摸点信息 + LONG touchCnt = static_cast(++TouchCnt); + leftButtonPid = touchCnt; // 记录左键的ID + info.pid = touchCnt; + + std::unique_lock lock1(touchPosSm); + TouchPos[touchCnt] = mode; + lock1.unlock(); + + std::unique_lock lock2(touchSpeedSm); + TouchSpeed[touchCnt] = 0; + PreviousPointPosition[touchCnt] = { -1, -1 }; + lock2.unlock(); + + std::unique_lock lock3(pointListSm); + TouchList.push_back(touchCnt); + lock3.unlock(); + + info.mode = mode; + + std::unique_lock lock4(touchTempSm); + TouchTemp.push_back(info); + lock4.unlock(); + + // 4. 更新全局状态 + rtsNum++; + rtsDown = true; + + // 光标隐藏提前指令(因为有可能是由触摸引起的鼠标消息) + if (setlist.hideTouchPointer) SendMessage(drawpad_window, WM_SETCURSOR, (WPARAM)drawpad_window, MAKELPARAM(HTCLIENT, WM_MOUSEMOVE)); + } + } + if (msg == WM_RBUTTONDOWN) + { + // 这是一个按下状态 (右键) + // 相当于 StylusDown + + // 如果右键已经按下,则忽略 + if (rightButtonPid == 0) + { + if (leftButtonPid != 0) + { + leftButtonNUp = true; + } + + TouchMode mode{}; + TouchInfo info{}; + + // 1. 获取数据包信息 (对于鼠标) + mode.pt.x = GET_X_LPARAM(lParam); + mode.pt.y = GET_Y_LPARAM(lParam); + mode.pressure = 0.0; + mode.touchWidth = 0; + mode.touchHeight = 0; + mode.isInvertedCursor = false; + + // 2. 获取设备类型 (右键为3) + mode.type = 3; + + // 3. 分配并存储新的触摸点信息 + LONG touchCnt = static_cast(++TouchCnt); + rightButtonPid = touchCnt; // 记录右键的ID + info.pid = touchCnt; + + std::unique_lock lock1(touchPosSm); + TouchPos[touchCnt] = mode; + lock1.unlock(); + + std::unique_lock lock2(touchSpeedSm); + TouchSpeed[touchCnt] = 0; + PreviousPointPosition[touchCnt] = { -1, -1 }; + lock2.unlock(); + + std::unique_lock lock3(pointListSm); + TouchList.push_back(touchCnt); + lock3.unlock(); + + info.mode = mode; + + std::unique_lock lock4(touchTempSm); + TouchTemp.push_back(info); + lock4.unlock(); + + // 4. 更新全局状态 + rtsNum++; + rtsDown = true; + } + } + + if (msg == WM_MOUSEMOVE) + { + // 这是一个移动状态 + // 相当于 Packets + + // 检查左键是否按下并移动 + if (wParam & MK_LBUTTON) + { + if (leftButtonPid != 0) + { + unique_lock lock(touchPosSm); + // 检查 TouchPos 中是否还存在该点 (以防万一) + if (TouchPos.count(leftButtonPid)) { + TouchPos[leftButtonPid].pt.x = GET_X_LPARAM(lParam); + TouchPos[leftButtonPid].pt.y = GET_Y_LPARAM(lParam); + } + } + } + else if (leftButtonPid != 0) leftButtonNUp = true; + + // 检查右键是否按下并移动 + if (wParam & MK_RBUTTON) + { + if (rightButtonPid != 0) + { + unique_lock lock(touchPosSm); + if (TouchPos.count(rightButtonPid)) { + TouchPos[rightButtonPid].pt.x = GET_X_LPARAM(lParam); + TouchPos[rightButtonPid].pt.y = GET_Y_LPARAM(lParam); + } + lock.unlock(); + } + } + else if (rightButtonPid != 0) rightButtonNUp = true; + } + + if (msg == WM_LBUTTONUP || leftButtonNUp) + { + // 这是一个抬起状态 (左键) + // 相当于 StylusUp + + // 如果左键没有被记录为按下,则忽略 + if (leftButtonPid != 0) + { + LONG pid = leftButtonPid; + leftButtonPid = 0; // 重置ID + leftButtonNUp = false; + + // 1. 更新全局计数器 + rtsNum = max(0, rtsNum - 1); + if (rtsNum == 0) rtsDown = false; + + // 2. 从列表中移除触摸点 + unique_lock lockPointListSm(pointListSm); + auto it = std::find(TouchList.begin(), TouchList.end(), pid); + if (it != TouchList.end()) + { + TouchList.erase(it); + } + lockPointListSm.unlock(); + + // 3. 如果没有活动的触摸/输入点,则清空所有数据 + if (rtsNum == 0) + { + unique_lock lockTouchPosSm(touchPosSm); + TouchPos.clear(); + lockTouchPosSm.unlock(); + + touchNum = 0; + inkNum = 0; + } + } + } + if (msg == WM_RBUTTONUP || rightButtonNUp) + { + // 这是一个抬起状态 (右键) + // 相当于 StylusUp + + cerr << 3 << endl; + // 如果右键没有被记录为按下,则忽略 + if (rightButtonPid != 0) + { + cerr << 456 << endl; + + LONG pid = rightButtonPid; + rightButtonPid = 0; // 重置ID + rightButtonNUp = false; + + // 1. 更新全局计数器 + rtsNum = max(0, rtsNum - 1); + if (rtsNum == 0) rtsDown = false; + + // 2. 从列表中移除触摸点 + unique_lock lockPointListSm(pointListSm); + auto it = std::find(TouchList.begin(), TouchList.end(), pid); + if (it != TouchList.end()) + { + TouchList.erase(it); + } + lockPointListSm.unlock(); + + // 3. 如果没有活动的触摸/输入点,则清空所有数据 + if (rtsNum == 0) + { + unique_lock lockTouchPosSm(touchPosSm); + TouchPos.clear(); + lockTouchPosSm.unlock(); + + touchNum = 0; + inkNum = 0; + } + } + } +} + +HRESULT SafeRTSInit(HWND drawpad_window, IRealTimeStylus** ppRTS, IStylusSyncPlugin** ppPlugin) +{ + HRESULT hr = S_OK; + __try { + *ppRTS = CreateRealTimeStylus(drawpad_window); + if (*ppRTS == NULL) return E_FAIL; + + GUID props[] = { GUID_PACKETPROPERTY_GUID_X, GUID_PACKETPROPERTY_GUID_Y, + GUID_PACKETPROPERTY_GUID_NORMAL_PRESSURE, GUID_PACKETPROPERTY_GUID_WIDTH, + GUID_PACKETPROPERTY_GUID_HEIGHT }; + + hr = (*ppRTS)->SetDesiredPacketDescription(5, props); + if (FAILED(hr)) return hr; + + *ppPlugin = CSyncEventHandlerRTS::Create(*ppRTS); + if (*ppPlugin == NULL) return E_FAIL; + + if (!EnableRealTimeStylus(*ppRTS)) return E_FAIL; + + return S_OK; + } + __except (EXCEPTION_EXECUTE_HANDLER) { + return E_UNEXPECTED; // 捕获到硬件/底层崩溃 + } +} +void InitRTSLogic() +{ + IRealTimeStylus* pRTS = NULL; + IStylusSyncPlugin* pPlugin = NULL; + + // 调用包装函数,这里可以安全使用 try/catch 或其他 C++ 对象 + HRESULT hr = SafeRTSInit(drawpad_window, &pRTS, &pPlugin); + + if (FAILED(hr)) + { + g_pRealTimeStylus = NULL; + g_pSyncEventHandlerRTS = NULL; + + IDTLogger->warn("[主线程] RTS 初始化失败,错误码: {}", hr); + useMouseInput = true; + } + else + { + g_pRealTimeStylus = pRTS; + g_pSyncEventHandlerRTS = pPlugin; + + IDTLogger->info("[主线程] RTS 初始化完成"); + } } \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtRts.h" "b/\346\231\272\347\273\230\346\225\231/IdtRts.h" index 07589665..dca7bfc2 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtRts.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtRts.h" @@ -123,4 +123,9 @@ class CSyncEventHandlerRTS : public IStylusSyncPlugin int m_nContacts; // number of fingers currently in the contact with the touch digitizer }; -void RTSSpeed(); \ No newline at end of file +void RTSSpeed(); + +extern IdtAtomic leftButtonPid, rightButtonPid; +void HandleMouseInput(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +void InitRTSLogic(); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" index 9767a50a..1a90d972 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtSetting.cpp" @@ -797,21 +797,23 @@ void SettingMain() struct { - bool SeewoWhiteboard3Floating = ddbInteractionSetList.intercept.seewoWhiteboard3Floating; - bool SeewoWhiteboard5Floating = ddbInteractionSetList.intercept.seewoWhiteboard5Floating; - bool SeewoWhiteboard5CFloating = ddbInteractionSetList.intercept.seewoWhiteboard5CFloating; - bool SeewoPincoSideBarFloating = ddbInteractionSetList.intercept.seewoPincoSideBarFloating; - bool SeewoPincoDrawingFloating = ddbInteractionSetList.intercept.seewoPincoDrawingFloating; - bool SeewoPPTFloating = ddbInteractionSetList.intercept.seewoPPTFloating; - bool AiClassFloating = ddbInteractionSetList.intercept.aiClassFloating; - bool HiteAnnotationFloating = ddbInteractionSetList.intercept.hiteAnnotationFloating; - bool ChangYanFloating = ddbInteractionSetList.intercept.changYanFloating; - bool ChangYanPptFloating = ddbInteractionSetList.intercept.changYanPptFloating; - bool IntelligentClassFloating = ddbInteractionSetList.intercept.intelligentClassFloating; - bool SeewoDesktopAnnotationFloating = ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating; - bool SeewoDesktopSideBarFloating = ddbInteractionSetList.intercept.seewoDesktopSideBarFloating; - bool Iclass30Floating = ddbInteractionSetList.intercept.iclass30Floating; - bool Iclass30SidebarFloating = ddbInteractionSetList.intercept.iclass30SidebarFloating; + bool SeewoWhiteboard3Floating = ddbInteractionSetList.intercept.SeewoWhiteboard3Floating; + bool SeewoWhiteboard5Floating = ddbInteractionSetList.intercept.SeewoWhiteboard5Floating; + bool SeewoWhiteboard5CFloating = ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating; + bool SeewoPincoSideBarFloating = ddbInteractionSetList.intercept.SeewoPincoSideBarFloating; + bool SeewoPincoDrawingFloating = ddbInteractionSetList.intercept.SeewoPincoDrawingFloating; + bool SeewoPPTFloating = ddbInteractionSetList.intercept.SeewoPPTFloating; + bool SeewoIwbAssistantFloating = ddbInteractionSetList.intercept.SeewoIwbAssistantFloating; + bool YiouBoardFloating = ddbInteractionSetList.intercept.YiouBoardFloating; + bool AiClassFloating = ddbInteractionSetList.intercept.AiClassFloating; + bool ClassInXFloating = ddbInteractionSetList.intercept.ClassInXFloating; + bool IntelligentClassFloating = ddbInteractionSetList.intercept.IntelligentClassFloating; + bool ChangYanFloating = ddbInteractionSetList.intercept.ChangYanFloating; + bool ChangYan5Floating = ddbInteractionSetList.intercept.ChangYan5Floating; + bool Iclass30SidebarFloating = ddbInteractionSetList.intercept.Iclass30SidebarFloating; + bool Iclass30Floating = ddbInteractionSetList.intercept.Iclass30Floating; + bool SeewoDesktopSideBarFloating = ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating; + bool SeewoDesktopDrawingFloating = ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating; }intercept; } Ddb; @@ -2853,6 +2855,11 @@ void SettingMain() else setlist.updateArchitecture = "win32"; WriteSetting(); + + if (AutomaticUpdateState != AutomaticUpdateStateEnum::UpdateNotStarted) + { + AutomaticUpdateState = AutomaticUpdateStateEnum::UpdateObtainInformation; + } } } if (is_selected) ImGui::SetItemDefaultFocus(); @@ -5171,7 +5178,7 @@ void SettingMain() ImGui::SetCursorPos({ 60.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("20250715a"); + ImGui::TextUnformatted("20260202a"); } { ImGui::SetCursorPos({ 630.0f * settingGlobalScale, 20.0f * settingGlobalScale }); @@ -5396,7 +5403,51 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(243, 243, 243, 255)); ImGui::BeginChild("PPT演示助手主栏", { (750.0f + 30.0f) * settingGlobalScale,555.0f * settingGlobalScale }, false); + if (pptComVersion.substr(0, 7) == L"Error: ") { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 244, 206, 255)); + ImGui::BeginChild("PPT演示助手#12", { 750.0f * settingGlobalScale,80.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(157, 93, 0, 255)); + ImGui::TextUnformatted("\ue814"); + } + { + ImGui::SetCursorPos({ 60.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); + ImGui::BeginChild("PPT演示助手-提示0", { 670.0f * settingGlobalScale,40.0f * settingGlobalScale }, false); + + { + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextWrapped((IA("SettingsUI/PlugIn/PPTHelper/Error") + utf16ToUtf8(pptComVersion)).c_str()); + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + + { + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); ImGui::BeginChild("PPT演示助手#1", { 750.0f * settingGlobalScale,80.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); @@ -5452,7 +5503,7 @@ void SettingMain() } if (pptComSetlist.setAdmin) { - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f * settingGlobalScale); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 244, 206, 255)); ImGui::BeginChild("PPT演示助手#11", { 750.0f * settingGlobalScale,100.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); @@ -6518,7 +6569,7 @@ void SettingMain() ImGui::SetCursorPos({ 40.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("20250715a"); // 主界面还有版本号 + ImGui::TextUnformatted("20260202a"); // 主界面还有版本号 } } } @@ -7120,48 +7171,48 @@ void SettingMain() if (ddbInteractionSetList.enable) { ddbInteractionSetList.hostPath = GetCurrentExePath(); - if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) + if (_waccess((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), 0) == -1) { - if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker").c_str(), 0) == -1) + if (_waccess((pluginPath + L"DesktopDrawpadBlocker").c_str(), 0) == -1) { error_code ec; - filesystem::create_directories(pluginPath + L"\\DesktopDrawpadBlocker", ec); + filesystem::create_directories(pluginPath + L"DesktopDrawpadBlocker", ec); } - ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } else { string hash_sha256; { hashwrapper* myWrapper = new sha256wrapper(); - hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); + hash_sha256 = myWrapper->getHashFromFileW(pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe"); delete myWrapper; } if (hash_sha256 != ddbInteractionSetList.DdbSHA256) { - if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭旧版 DDB 并更新版本 DdbWriteInteraction(true, true); for (int i = 1; i <= 20; i++) { - if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } } - ExtractResource((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); + ExtractResource((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), L"EXE", MAKEINTRESOURCE(237)); } } // 启动 DDB - if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } else @@ -7171,13 +7222,13 @@ void SettingMain() // 历史遗留问题处理 { // 取消开机自动启动 - SetStartupState(false, pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe", L"$Inkeys_DesktopDrawpadBlocker"); + SetStartupState(false, pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe", L"$Inkeys_DesktopDrawpadBlocker"); // 移除开机自启标识 - if (_waccess((pluginPath + L"\\DesktopDrawpadBlocker\\start_up.signal").c_str(), 0) == 0) + if (_waccess((pluginPath + L"DesktopDrawpadBlocker\\start_up.signal").c_str(), 0) == 0) { error_code ec; - filesystem::remove(pluginPath + L"\\DesktopDrawpadBlocker\\start_up.signal", ec); + filesystem::remove(pluginPath + L"DesktopDrawpadBlocker\\start_up.signal", ec); } } } @@ -7255,20 +7306,20 @@ void SettingMain() ddbInteractionSetList.runAsAdmin = Ddb.RunAsAdmin; WriteSetting(); - if (isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) { // 需要关闭 DDB 并重新启动 DdbWriteInteraction(true, true); - for (int i = 1; i <= 20; i++) + for (int i = 1; i <= 25; i++) { - if (!isProcessRunning((pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) + if (!isProcessRunning((pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str())) break; this_thread::sleep_for(chrono::milliseconds(500)); } DdbWriteInteraction(true, false); - if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); - else ShellExecuteW(NULL, NULL, (pluginPath + L"\\DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + if (ddbInteractionSetList.runAsAdmin) ShellExecuteW(NULL, L"runas", (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); + else ShellExecuteW(NULL, NULL, (pluginPath + L"DesktopDrawpadBlocker\\DesktopDrawpadBlocker.exe").c_str(), NULL, NULL, SW_SHOWNORMAL); } } } @@ -7389,7 +7440,7 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 0.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(255, 255, 255, 0)); - ImGui::BeginChild("同类软件悬浮窗拦截助手#3", { 750.0f * settingGlobalScale,1060.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("同类软件悬浮窗拦截助手#3", { 750.0f * settingGlobalScale,1200.0f * settingGlobalScale }, false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); { ImGui::SetCursorPos({ 0.0f * settingGlobalScale, 0.0f * settingGlobalScale }); @@ -7410,7 +7461,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃白板3 桌面悬浮窗"); + ImGui::TextUnformatted("希沃白板3 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); @@ -7428,11 +7479,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃白板3 桌面悬浮窗", &Ddb.intercept.SeewoWhiteboard3Floating, config); + ImGui::Toggle("##希沃白板3 桌面画笔悬浮窗", &Ddb.intercept.SeewoWhiteboard3Floating, config); - if (ddbInteractionSetList.intercept.seewoWhiteboard3Floating != Ddb.intercept.SeewoWhiteboard3Floating) + if (ddbInteractionSetList.intercept.SeewoWhiteboard3Floating != Ddb.intercept.SeewoWhiteboard3Floating) { - ddbInteractionSetList.intercept.seewoWhiteboard3Floating = Ddb.intercept.SeewoWhiteboard3Floating; + ddbInteractionSetList.intercept.SeewoWhiteboard3Floating = Ddb.intercept.SeewoWhiteboard3Floating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7458,7 +7509,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃白板5 桌面悬浮窗"); + ImGui::TextUnformatted("希沃白板5 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); @@ -7476,11 +7527,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃白板5 桌面悬浮窗", &Ddb.intercept.SeewoWhiteboard5Floating, config); + ImGui::Toggle("##希沃白板5 桌面画笔悬浮窗", &Ddb.intercept.SeewoWhiteboard5Floating, config); - if (ddbInteractionSetList.intercept.seewoWhiteboard5Floating != Ddb.intercept.SeewoWhiteboard5Floating) + if (ddbInteractionSetList.intercept.SeewoWhiteboard5Floating != Ddb.intercept.SeewoWhiteboard5Floating) { - ddbInteractionSetList.intercept.seewoWhiteboard5Floating = Ddb.intercept.SeewoWhiteboard5Floating; + ddbInteractionSetList.intercept.SeewoWhiteboard5Floating = Ddb.intercept.SeewoWhiteboard5Floating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7506,7 +7557,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃白板轻白板 桌面悬浮窗"); + ImGui::TextUnformatted("希沃轻白板(5C) 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); @@ -7524,11 +7575,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃白板轻白板 桌面悬浮窗", &Ddb.intercept.SeewoWhiteboard5CFloating, config); + ImGui::Toggle("##希沃轻白板(5C) 桌面画笔悬浮窗", &Ddb.intercept.SeewoWhiteboard5CFloating, config); - if (ddbInteractionSetList.intercept.seewoWhiteboard5CFloating != Ddb.intercept.SeewoWhiteboard5CFloating) + if (ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating != Ddb.intercept.SeewoWhiteboard5CFloating) { - ddbInteractionSetList.intercept.seewoWhiteboard5CFloating = Ddb.intercept.SeewoWhiteboard5CFloating; + ddbInteractionSetList.intercept.SeewoWhiteboard5CFloating = Ddb.intercept.SeewoWhiteboard5CFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7554,7 +7605,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃品课教师端 桌面悬浮窗"); + ImGui::TextUnformatted("希沃品课教师端 侧栏悬浮窗"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); @@ -7572,11 +7623,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃品课教师端 桌面悬浮窗", &Ddb.intercept.SeewoPincoSideBarFloating, config); + ImGui::Toggle("##希沃品课教师端 侧栏悬浮窗", &Ddb.intercept.SeewoPincoSideBarFloating, config); - if (ddbInteractionSetList.intercept.seewoPincoSideBarFloating != Ddb.intercept.SeewoPincoSideBarFloating) + if (ddbInteractionSetList.intercept.SeewoPincoSideBarFloating != Ddb.intercept.SeewoPincoSideBarFloating) { - ddbInteractionSetList.intercept.seewoPincoSideBarFloating = Ddb.intercept.SeewoPincoSideBarFloating; + ddbInteractionSetList.intercept.SeewoPincoSideBarFloating = Ddb.intercept.SeewoPincoSideBarFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7596,7 +7647,7 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃品课教师端 画笔悬浮窗"); + ImGui::TextUnformatted("希沃品课教师端 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); @@ -7620,11 +7671,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃品课教师端 画笔悬浮窗", &Ddb.intercept.SeewoPincoDrawingFloating, config); + ImGui::Toggle("##希沃品课教师端 桌面画笔悬浮窗", &Ddb.intercept.SeewoPincoDrawingFloating, config); - if (ddbInteractionSetList.intercept.seewoPincoDrawingFloating != Ddb.intercept.SeewoPincoDrawingFloating) + if (ddbInteractionSetList.intercept.SeewoPincoDrawingFloating != Ddb.intercept.SeewoPincoDrawingFloating) { - ddbInteractionSetList.intercept.seewoPincoDrawingFloating = Ddb.intercept.SeewoPincoDrawingFloating; + ddbInteractionSetList.intercept.SeewoPincoDrawingFloating = Ddb.intercept.SeewoPincoDrawingFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7670,9 +7721,9 @@ void SettingMain() } ImGui::Toggle("##希沃PPT小工具", &Ddb.intercept.SeewoPPTFloating, config); - if (ddbInteractionSetList.intercept.seewoPPTFloating != Ddb.intercept.SeewoPPTFloating) + if (ddbInteractionSetList.intercept.SeewoPPTFloating != Ddb.intercept.SeewoPPTFloating) { - ddbInteractionSetList.intercept.seewoPPTFloating = Ddb.intercept.SeewoPPTFloating; + ddbInteractionSetList.intercept.SeewoPPTFloating = Ddb.intercept.SeewoPPTFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7691,14 +7742,14 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#6", { 750.0f * settingGlobalScale,60.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#51", { 750.0f * settingGlobalScale,60.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("AiClass 桌面悬浮窗"); + ImGui::TextUnformatted("希沃课堂助手 PPT小工具"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); @@ -7706,7 +7757,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.AiClassFloating) + if (!Ddb.intercept.SeewoIwbAssistantFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7716,11 +7767,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##AiClass 桌面悬浮窗", &Ddb.intercept.AiClassFloating, config); + ImGui::Toggle("##希沃课堂助手 PPT小工具", &Ddb.intercept.SeewoIwbAssistantFloating, config); - if (ddbInteractionSetList.intercept.aiClassFloating != Ddb.intercept.AiClassFloating) + if (ddbInteractionSetList.intercept.SeewoIwbAssistantFloating != Ddb.intercept.SeewoIwbAssistantFloating) { - ddbInteractionSetList.intercept.aiClassFloating = Ddb.intercept.AiClassFloating; + ddbInteractionSetList.intercept.SeewoIwbAssistantFloating = Ddb.intercept.SeewoIwbAssistantFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7739,22 +7790,28 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#7", { 750.0f * settingGlobalScale,60.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#52", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { - ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("鸿合屏幕书写"); + ImGui::TextUnformatted("欧帝白板 桌面画笔悬浮窗"); } { - ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("包括PPT控件。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.HiteAnnotationFloating) + if (!Ddb.intercept.YiouBoardFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7764,11 +7821,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##鸿合屏幕书写", &Ddb.intercept.HiteAnnotationFloating, config); + ImGui::Toggle("##欧帝白板 桌面画笔悬浮窗", &Ddb.intercept.YiouBoardFloating, config); - if (ddbInteractionSetList.intercept.hiteAnnotationFloating != Ddb.intercept.HiteAnnotationFloating) + if (ddbInteractionSetList.intercept.YiouBoardFloating != Ddb.intercept.YiouBoardFloating) { - ddbInteractionSetList.intercept.hiteAnnotationFloating = Ddb.intercept.HiteAnnotationFloating; + ddbInteractionSetList.intercept.YiouBoardFloating = Ddb.intercept.YiouBoardFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7787,28 +7844,70 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#8", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#6", { 750.0f * settingGlobalScale,60.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { - ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("畅言智慧课堂 桌面悬浮窗"); + ImGui::TextUnformatted("AiClass 桌面画笔悬浮窗"); } { - ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); - ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("支持在白板时自动恢复,需要管理员权限。"); + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!Ddb.intercept.AiClassFloating) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##AiClass 桌面画笔悬浮窗", &Ddb.intercept.AiClassFloating, config); + + if (ddbInteractionSetList.intercept.AiClassFloating != Ddb.intercept.AiClassFloating) + { + ddbInteractionSetList.intercept.AiClassFloating = Ddb.intercept.AiClassFloating; + WriteSetting(); + + DdbWriteInteraction(true, false); + } } + { - ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); + } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("精确控制#7", { 750.0f * settingGlobalScale,60.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + float cursosPosY = 0; + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 22.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("ClassIn X 桌面画笔悬浮窗"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.ChangYanFloating) + if (!Ddb.intercept.ClassInXFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7818,37 +7917,43 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##畅言智慧课堂 桌面悬浮窗", &Ddb.intercept.ChangYanFloating, config); + ImGui::Toggle("##ClassIn X 桌面画笔悬浮窗", &Ddb.intercept.ClassInXFloating, config); - if (ddbInteractionSetList.intercept.changYanFloating != Ddb.intercept.ChangYanFloating) + if (ddbInteractionSetList.intercept.ClassInXFloating != Ddb.intercept.ClassInXFloating) { - ddbInteractionSetList.intercept.changYanFloating = Ddb.intercept.ChangYanFloating; + ddbInteractionSetList.intercept.ClassInXFloating = Ddb.intercept.ClassInXFloating; WriteSetting(); DdbWriteInteraction(true, false); } } - // Separator - cursosPosY = ImGui::GetCursorPosY(); { - ImGui::SetCursorPosY(cursosPosY + 25.0f * settingGlobalScale); - PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Separator, IM_COL32(229, 229, 229, 255)); - ImGui::Separator(); + if (PushStyleColorNum >= 0) ImGui::PopStyleColor(PushStyleColorNum), PushStyleColorNum = 0; + if (PushStyleVarNum >= 0) ImGui::PopStyleVar(PushStyleVarNum), PushStyleVarNum = 0; + while (PushFontNum) PushFontNum--, ImGui::PopFont(); } + ImGui::EndChild(); + } + { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5.0f * settingGlobalScale); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); + ImGui::BeginChild("精确控制#9", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - cursosPosY = ImGui::GetCursorPosY(); + float cursosPosY = 0; { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("畅言智慧课堂 PPT底栏"); + ImGui::TextUnformatted("天喻教育云互动课堂 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("需要管理员权限。"); + ImGui::TextUnformatted("包括PPT控件。"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -7856,7 +7961,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.ChangYanPptFloating) + if (!Ddb.intercept.IntelligentClassFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7866,11 +7971,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##畅言智慧课堂 PPT底栏", &Ddb.intercept.ChangYanPptFloating, config); + ImGui::Toggle("##天喻教育云互动课堂 桌面画笔悬浮窗", &Ddb.intercept.IntelligentClassFloating, config); - if (ddbInteractionSetList.intercept.changYanPptFloating != Ddb.intercept.ChangYanPptFloating) + if (ddbInteractionSetList.intercept.IntelligentClassFloating != Ddb.intercept.IntelligentClassFloating) { - ddbInteractionSetList.intercept.changYanPptFloating = Ddb.intercept.ChangYanPptFloating; + ddbInteractionSetList.intercept.IntelligentClassFloating = Ddb.intercept.IntelligentClassFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7889,20 +7994,20 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#9", { 750.0f * settingGlobalScale,70.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#8", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("天喻教育云互动课堂 桌面悬浮窗"); + ImGui::TextUnformatted("畅言智慧课堂4.0 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("包括PPT控件。"); + ImGui::TextUnformatted("包括PPT控件。支持在白板时自动恢复。需要DDB管理员权限。"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -7910,7 +8015,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.IntelligentClassFloating) + if (!Ddb.intercept.ChangYanFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7920,11 +8025,59 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##天喻教育云互动课堂 桌面悬浮窗", &Ddb.intercept.IntelligentClassFloating, config); + ImGui::Toggle("##畅言智慧课堂4.0 桌面画笔悬浮窗", &Ddb.intercept.ChangYanFloating, config); - if (ddbInteractionSetList.intercept.intelligentClassFloating != Ddb.intercept.IntelligentClassFloating) + if (ddbInteractionSetList.intercept.ChangYanFloating != Ddb.intercept.ChangYanFloating) { - ddbInteractionSetList.intercept.intelligentClassFloating = Ddb.intercept.IntelligentClassFloating; + ddbInteractionSetList.intercept.ChangYanFloating = Ddb.intercept.ChangYanFloating; + WriteSetting(); + + DdbWriteInteraction(true, false); + } + } + + // Separator + cursosPosY = ImGui::GetCursorPosY(); + { + ImGui::SetCursorPosY(cursosPosY + 25.0f * settingGlobalScale); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Separator, IM_COL32(229, 229, 229, 255)); + ImGui::Separator(); + } + + cursosPosY = ImGui::GetCursorPosY(); + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); + ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); + ImGui::TextUnformatted("畅言智慧课堂5.0 桌面画笔悬浮窗"); + } + { + ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); + ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); + ImGui::TextUnformatted("包括PPT控件。支持在白板时自动恢复。需要DDB管理员权限。"); + } + { + ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 6)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); + if (!Ddb.intercept.ChangYan5Floating) + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); + } + else + { + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); + PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); + } + ImGui::Toggle("##畅言智慧课堂5.0 桌面画笔悬浮窗", &Ddb.intercept.ChangYan5Floating, config); + + if (ddbInteractionSetList.intercept.ChangYan5Floating != Ddb.intercept.ChangYan5Floating) + { + ddbInteractionSetList.intercept.ChangYan5Floating = Ddb.intercept.ChangYan5Floating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7943,20 +8096,20 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#10", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#11", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃桌面 画笔悬浮窗"); + ImGui::TextUnformatted("C30智能教学 侧栏悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("1.0/2.0 版本通用。"); + ImGui::TextUnformatted("需要DDB管理员。"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -7964,7 +8117,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.SeewoDesktopAnnotationFloating) + if (!Ddb.intercept.Iclass30SidebarFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -7974,11 +8127,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃桌面 画笔悬浮窗", &Ddb.intercept.SeewoDesktopAnnotationFloating, config); + ImGui::Toggle("##C30智能教学 侧栏悬浮窗", &Ddb.intercept.Iclass30SidebarFloating, config); - if (ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating != Ddb.intercept.SeewoDesktopAnnotationFloating) + if (ddbInteractionSetList.intercept.Iclass30SidebarFloating != Ddb.intercept.Iclass30SidebarFloating) { - ddbInteractionSetList.intercept.seewoDesktopAnnotationFloating = Ddb.intercept.SeewoDesktopAnnotationFloating; + ddbInteractionSetList.intercept.Iclass30SidebarFloating = Ddb.intercept.Iclass30SidebarFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -7998,13 +8151,13 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("希沃桌面 侧栏悬浮窗"); + ImGui::TextUnformatted("C30智能教学 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("1.0/2.0 版本通用,需要管理员权限。"); + ImGui::TextUnformatted("包括PPT控件。支持在白板时自动恢复,支持窗口追踪。需要DDB管理员权限。"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -8012,7 +8165,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.SeewoDesktopSideBarFloating) + if (!Ddb.intercept.Iclass30Floating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -8022,11 +8175,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##希沃桌面 侧栏悬浮窗", &Ddb.intercept.SeewoDesktopSideBarFloating, config); + ImGui::Toggle("##C30智能教学 桌面画笔悬浮窗", &Ddb.intercept.Iclass30Floating, config); - if (ddbInteractionSetList.intercept.seewoDesktopSideBarFloating != Ddb.intercept.SeewoDesktopSideBarFloating) + if (ddbInteractionSetList.intercept.Iclass30Floating != Ddb.intercept.Iclass30Floating) { - ddbInteractionSetList.intercept.seewoDesktopSideBarFloating = Ddb.intercept.SeewoDesktopSideBarFloating; + ddbInteractionSetList.intercept.Iclass30Floating = Ddb.intercept.Iclass30Floating; WriteSetting(); DdbWriteInteraction(true, false); @@ -8045,20 +8198,20 @@ void SettingMain() PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); PushStyleVarNum++, ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(251, 251, 251, 255)); - ImGui::BeginChild("精确控制#11", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + ImGui::BeginChild("精确控制#10", { 750.0f * settingGlobalScale,140.0f * settingGlobalScale }, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); float cursosPosY = 0; { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("C30智能教学 桌面悬浮窗"); + ImGui::TextUnformatted("希沃桌面 侧栏悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("包括PPT控件,支持在白板时自动恢复,需要管理员权限。"); + ImGui::TextUnformatted("需要DDB管理员。1.0/2.0/2.5/3.0 普教版/高教版 通用"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -8066,7 +8219,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.Iclass30Floating) + if (!Ddb.intercept.SeewoDesktopSideBarFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -8076,11 +8229,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##C30智能教学 桌面悬浮窗", &Ddb.intercept.Iclass30Floating, config); + ImGui::Toggle("##希沃桌面 侧栏悬浮窗", &Ddb.intercept.SeewoDesktopSideBarFloating, config); - if (ddbInteractionSetList.intercept.iclass30Floating != Ddb.intercept.Iclass30Floating) + if (ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating != Ddb.intercept.SeewoDesktopSideBarFloating) { - ddbInteractionSetList.intercept.iclass30Floating = Ddb.intercept.Iclass30Floating; + ddbInteractionSetList.intercept.SeewoDesktopSideBarFloating = Ddb.intercept.SeewoDesktopSideBarFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -8100,13 +8253,13 @@ void SettingMain() ImGui::SetCursorPos({ 20.0f * settingGlobalScale, cursosPosY + 20.0f * settingGlobalScale }); ImFontMain->Scale = 0.6f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted("C30智能教学 侧栏悬浮窗"); + ImGui::TextUnformatted("希沃桌面 桌面画笔悬浮窗"); } { ImGui::SetCursorPos({ 20.0f * settingGlobalScale, ImGui::GetCursorPosY() }); ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 120, 120, 255)); - ImGui::TextUnformatted("需要管理员权限。"); + ImGui::TextUnformatted("需要DDB管理员。1.0/2.0/2.5/3.0 普教版/高教版 通用"); } { ImGui::SetCursorPos({ 690.0f * settingGlobalScale, cursosPosY + 25.0f * settingGlobalScale }); @@ -8114,7 +8267,7 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 15)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 95, 184, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_ButtonHovered, IM_COL32(0, 95, 184, 230)); - if (!Ddb.intercept.Iclass30SidebarFloating) + if (!Ddb.intercept.SeewoDesktopDrawingFloating) { PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 155)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 0, 0, 155)); @@ -8124,11 +8277,11 @@ void SettingMain() PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255)); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_BorderShadow, IM_COL32(0, 95, 184, 255)); } - ImGui::Toggle("##C30智能教学 侧栏悬浮窗", &Ddb.intercept.Iclass30SidebarFloating, config); + ImGui::Toggle("##希沃桌面 桌面画笔悬浮窗", &Ddb.intercept.SeewoDesktopDrawingFloating, config); - if (ddbInteractionSetList.intercept.iclass30SidebarFloating != Ddb.intercept.Iclass30SidebarFloating) + if (ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating != Ddb.intercept.SeewoDesktopDrawingFloating) { - ddbInteractionSetList.intercept.iclass30SidebarFloating = Ddb.intercept.Iclass30SidebarFloating; + ddbInteractionSetList.intercept.SeewoDesktopDrawingFloating = Ddb.intercept.SeewoDesktopDrawingFloating; WriteSetting(); DdbWriteInteraction(true, false); @@ -9621,7 +9774,14 @@ void SettingMain() ImFontMain->Scale = 0.5f, PushFontNum++, ImGui::PushFont(ImFontMain); PushStyleColorNum++, ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 0, 0, 255)); - ImGui::TextUnformatted(IA("SettingsUI/Update/Downloading").c_str()); + try + { + ImGui::TextUnformatted(vformat(IA("SettingsUI/Update/Downloading"), make_format_args(downloadLine)).c_str()); + } + catch (...) + { + ImGui::TextUnformatted(IA("SettingsUI/Update/Downloading").c_str()); + } } { double downloadedSize = static_cast(downloadNewProgramState.downloadedSize.load()); diff --git "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" index 7b195678..87ec0a50 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.cpp" @@ -301,6 +301,8 @@ AutomaticUpdateStateEnum DownloadNewProgram(DownloadNewProgramStateClass* state, bool isWindows8OrGreater; wstring windowsEdition; +IdtAtomic downloadLine = 1; + void AutomaticUpdate() { bool state = true; @@ -415,20 +417,37 @@ void AutomaticUpdate() if (tedition == editionInfo.editionDate && _waccess((globalPath + tpath).c_str(), 0) == 0 && hash_md5 == thash_md5 && hash_sha256 == thash_sha256 && updateArch == tarch) { - update = false; - AutomaticUpdateState = UpdateRestart; + if (!setlist.enableAutoUpdate) + { + if (_waccess((globalPath + L"installer").c_str(), 0) == 0) + { + error_code ec; + filesystem::remove_all(globalPath + L"installer", ec); + } + } + else + { + update = false; + AutomaticUpdateState = UpdateRestart; + } } } } + if (update) { + downloadLine = 1; AutomaticUpdateState = UpdateDownloading; against = true; bool hasUpdateNew = false; for (int i = 0; i < editionInfo.path_size; i++) { + downloadLine = i + 1; + AutomaticUpdateState = UpdateDownloading; + AutomaticUpdateState = DownloadNewProgram(&downloadNewProgramState, editionInfo, editionInfo.path[i], updateArch); + if (AutomaticUpdateState == UpdateRestart) { against = false; @@ -442,6 +461,12 @@ void AutomaticUpdate() } else if (AutomaticUpdateState == UpdateNew && !mandatoryUpdate) { + if (_waccess((globalPath + L"installer").c_str(), 0) == 0) + { + error_code ec; + filesystem::remove_all(globalPath + L"installer", ec); + } + hasUpdateNew = true; break; } diff --git "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" index 1a7448e8..b55db183 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" +++ "b/\346\231\272\347\273\230\346\225\231/IdtUpdate.h" @@ -111,4 +111,6 @@ wstring convertToHttp(const wstring& url); extern bool isWindows8OrGreater; extern wstring windowsEdition; +extern IdtAtomic downloadLine; + void AutomaticUpdate(); \ No newline at end of file diff --git "a/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" "b/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" index 0121bce9..0fca49ef 100644 --- "a/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" +++ "b/\346\231\272\347\273\230\346\225\231/IdtWindow.cpp" @@ -148,13 +148,16 @@ void TopWindow() if (IsWindowVisible(freeze_window)) break; this_thread::sleep_for(chrono::milliseconds(10)); } - for (int i = 1; i <= 10 && !IsWindowVisible(magnifierWindow); i++) + if (magnificationCreateReady) { - IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow 被隐藏 Try" + to_string(i)); - ShowWindow(magnifierWindow, SW_SHOWNOACTIVATE); - - if (IsWindowVisible(magnifierWindow)) break; - this_thread::sleep_for(chrono::milliseconds(10)); + for (int i = 1; i <= 10 && !IsWindowVisible(magnifierWindow); i++) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow 被隐藏 Try" + to_string(i)); + ShowWindow(magnifierWindow, SW_SHOWNOACTIVATE); + + if (IsWindowVisible(magnifierWindow)) break; + this_thread::sleep_for(chrono::milliseconds(10)); + } } /*for (int i = 1; i <= 10 && !IsWindowVisible(setting_window); i++) { @@ -236,29 +239,32 @@ void TopWindow() this_thread::sleep_for(chrono::milliseconds(10)); } - for (int i = 1; i <= 10 && !(GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_LAYERED); i++) - { - IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow WS_EX_LAYERED 样式被隐藏 Try" + to_string(i)); - SetWindowLong(magnifierWindow, GWL_EXSTYLE, GetWindowLong(magnifierWindow, GWL_EXSTYLE) | WS_EX_LAYERED); - - if (GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_LAYERED) break; - this_thread::sleep_for(chrono::milliseconds(10)); - } - for (int i = 1; i <= 10 && !(GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_NOACTIVATE); i++) - { - IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow WS_EX_NOACTIVATE 样式被隐藏 Try" + to_string(i)); - SetWindowLong(magnifierWindow, GWL_EXSTYLE, GetWindowLong(magnifierWindow, GWL_EXSTYLE) | WS_EX_NOACTIVATE); - - if (GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_NOACTIVATE) break; - this_thread::sleep_for(chrono::milliseconds(10)); - } - for (int i = 1; i <= 10 && !(GetWindowLong(magnifierChild, GWL_EXSTYLE) & WS_EX_NOACTIVATE); i++) + if (magnificationCreateReady) { - IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierChild WS_EX_NOACTIVATE 样式被隐藏 Try" + to_string(i)); - SetWindowLong(magnifierChild, GWL_EXSTYLE, GetWindowLong(magnifierChild, GWL_EXSTYLE) | WS_EX_NOACTIVATE); - - if (GetWindowLong(magnifierChild, GWL_EXSTYLE) & WS_EX_NOACTIVATE) break; - this_thread::sleep_for(chrono::milliseconds(10)); + for (int i = 1; i <= 10 && !(GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_LAYERED); i++) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow WS_EX_LAYERED 样式被隐藏 Try" + to_string(i)); + SetWindowLong(magnifierWindow, GWL_EXSTYLE, GetWindowLong(magnifierWindow, GWL_EXSTYLE) | WS_EX_LAYERED); + + if (GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_LAYERED) break; + this_thread::sleep_for(chrono::milliseconds(10)); + } + for (int i = 1; i <= 10 && !(GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_NOACTIVATE); i++) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierWindow WS_EX_NOACTIVATE 样式被隐藏 Try" + to_string(i)); + SetWindowLong(magnifierWindow, GWL_EXSTYLE, GetWindowLong(magnifierWindow, GWL_EXSTYLE) | WS_EX_NOACTIVATE); + + if (GetWindowLong(magnifierWindow, GWL_EXSTYLE) & WS_EX_NOACTIVATE) break; + this_thread::sleep_for(chrono::milliseconds(10)); + } + for (int i = 1; i <= 10 && !(GetWindowLong(magnifierChild, GWL_EXSTYLE) & WS_EX_NOACTIVATE); i++) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] magnifierChild WS_EX_NOACTIVATE 样式被隐藏 Try" + to_string(i)); + SetWindowLong(magnifierChild, GWL_EXSTYLE, GetWindowLong(magnifierChild, GWL_EXSTYLE) | WS_EX_NOACTIVATE); + + if (GetWindowLong(magnifierChild, GWL_EXSTYLE) & WS_EX_NOACTIVATE) break; + this_thread::sleep_for(chrono::milliseconds(10)); + } } for (int i = 1; i <= 10 && !(GetWindowLong(setting_window, GWL_EXSTYLE) & WS_EX_NOACTIVATE); i++) @@ -281,8 +287,20 @@ void TopWindow() //SetWindowPos(magnifierWindow, freeze_window, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); // 统一置顶 - if (!SetWindowPos(magnifierWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE)) - IDTLogger->warn("[窗口置顶线程][TopWindow] 置顶窗口时失败 Error" + to_string(GetLastError())); + if (magnificationCreateReady) + { + if (!SetWindowPos(magnifierWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE)) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] 置顶窗口时失败1 Error" + to_string(GetLastError())); + } + } + else + { + if (!SetWindowPos(freeze_window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE)) + { + IDTLogger->warn("[窗口置顶线程][TopWindow] 置顶窗口时失败2 Error" + to_string(GetLastError())); + } + } } topWait: diff --git "a/\346\231\272\347\273\230\346\225\231/PptCOM.dll" "b/\346\231\272\347\273\230\346\225\231/PptCOM.dll" index 55574da115f2045f9478b19e6fc5991df42c8eb1..f7f1c17c6dafa5aa02485e14ab0a73cbbac93c69 100644 GIT binary patch delta 732 zcmY+CUr19?9LImZ>)pNm+vdG&X>B*%bj?Y`%0X(>OC>(Ew?tPdD~m!9A?U4`g9t%` zZU>63=&2y-B|4}N7Ay*a2L77F90Ix0KpZ~RZ`&>#6nT= z6&HX(yoc%~+n**4k)I?LA?Az_yA%NKvF6+*6;bhX9!SA%GAe^aqMI7#sW4wIwU$5h zWshj${ThJAP4&WmNB%4Flbh<>YXP2-|6>!|Q<*B#2=FJ9Qm{&~wJ3IeA$j>J^lRF|AGw!W)}9uu*M*e4mwT(VNu} zEG8|gC)x|g&s(Hu&1x7P^b1-*S@%K{#4cI1tSR&dfBiNV%lP85mDF{SyumWP*WFJB zgqMa{w*swjiii5XqxVhHyHQ$td+{`jWfJ-~a#s delta 745 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 Jr3* zz-opAw=z5$_Ane+&v4*T`p3W?`U8Va@CQZ!1LDv>fvthj^al>5|4{T#`UBJG56qOog4qG}uW@M?VDyFF-#9b_X5>a^}Y?8+T(~ zoJ;ajd!=%xlKyi3%GD(=KDXh;d74ByKb3cEt^v9GAlHJ#0zJMw1%Y=9bbXu?@ZuVo z7v~kcILDJHdG0>&D$?WgX(ul=C*|`_UUC&goU8Nm1acM4i*prToQv|}dYKpZR(KVA z^CRuzDu9<=Pd87ulj|Q|1z3Oa;_8YQe$7%~p*{!W9E#T*U5-yhdCA=wdB%EQ&Wp2HiPC=j;;i5cz2zap3w3|)M)6`f&jIrJBuj!bZ03~h zAy(h-W1EP@ejo3}Y!|Wm-XYrp_tAN8WqXLl-JADiwh6k&!p(LO3(3iFvn|AG@?Opv zCEfIAn~237K25kw;7%3$?ra%&8PAn`ymx7zJ8A6tAi0M+6 z?TB`w`ynOjoGRuxY*(~r2K?E!sFQOVKI>9Q{<8`vL|n|;FPmTio7vTrE=LQxmWe%7OObLFqx>@&2VvN^(L?hLc< zu^#0?}W@&Lcv>Drw>;tmj8rxZt z$fF!bWPQtiBF`)⁢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 z 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 A8~^|S diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" index bb80766b..93cbbfd9 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/en-US.jsonc" @@ -203,7 +203,7 @@ "Skip1": "Recommended Skin", "Skip2": "Default Skin", "Skip3": "Minimal Clock", - "Skip4": "Year of Snake" + "Skip4": "Year of Horse" }, "SettingUIScale": { @@ -353,6 +353,7 @@ "Tip": "To use the plugin, please ensure that the permissions for the Inkeys application and the PPT application are the same for proper detection. If issues persist, please refer to the solution.", "Warn": "Detected that your PowerPoint/WPS is set to 'Always run as administrator'. This means our application must also run with administrator privileges to detect the slideshow process and enable PPT integration. If you prefer not to run your presentation software as an administrator, please refer to the solution.", + "Error": "An error occurred during plugin loading. This may be because .NET Framework 4.x is not installed or an exception occurred in the global COM environment.\nCode ", "BasicLogic": { @@ -422,8 +423,8 @@ }, "DesktopDrawpadBlocker": { - "N": "DesktopDrawpadBlocker", - "E": "Blocks floating widgets from competing software (e.g., Seewo Whiteboard) on your screen. Supports blocking various common floating widgets and other PPT control bars or toolkits." + "N": "DesktopDrawpadBlocker 3 Lite", + "E": "On all-in-one educational machines, some whiteboard software's floating window toolbar cannot be turned off directly, leading to multiple drawing tools appearing on the desktop. This often causes teachers to mix up different tools, making it impossible to continue annotations and severely impacting teaching efficiency. Thus, DesktopDrawpadBlocker was created." }, "LnkHelper": { @@ -491,7 +492,7 @@ "InformationFail": "Failed to fetch update info", "InformationDamage": "Downloaded info corrupted", "InformationUnStandardized": "Invalid update info", - "Downloading": "Downloading update", + "Downloading": "Downloading update (Line {})", "DownloadFail": "Download failed", "DownloadDamage": "File damaged", "Restart": "Restart to update to latest version", diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" index 8b9a6ae8..cc18f7cf 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-CN.jsonc" @@ -239,7 +239,7 @@ "Skip1": "推荐皮肤", "Skip2": "默认皮肤", "Skip3": "极简时钟", - "Skip4": "蛇年迎新" + "Skip4": "马年迎新" }, "SettingUIScale": { @@ -413,6 +413,7 @@ "Tip": "使用插件时需要保证智绘教Inkeys的程序权限和PPT程序是一致的才能正确识别。如果依然存在问题,可以参考解决方案。", "Warn": "检测到您的PowerPoint/WPS已经设置为始终以管理员身份打开,这意味着程序需要以管理员身份运行才能识别到放映进程并开启PPT联动。如果您不希望放映软件始终以管理员身份运行,可以参考解决方案。", + "Error": "插件加载发生错误,可能是因为未安装 .NET Framework 4.x 或全局 COM 环境发生异常。\n错误代码", "BasicLogic": { @@ -482,8 +483,8 @@ }, "DesktopDrawpadBlocker": { - "N": "同类软件悬浮窗拦截助手", - "E": "拦截屏幕上 希沃白板桌面悬浮窗 等同类软件悬浮窗。支持拦截常见同类软件悬浮窗,以及 PPT 小工具等 PPT 操控栏。" + "N": "同类软件悬浮窗拦截助手 3 Lite", + "E": "在教学一体机上,一些白板软件的桌面悬浮窗画笔功能不能直接关闭,桌面上会出现多家画笔工具的悬浮窗。这容易导致老师混用不同的工具导致无法继续批注,这严重影响了教学效率,于是 DesktopDrawpadBlocker 应运而生。" }, "LnkHelper": { @@ -562,7 +563,7 @@ "InformationFail": "下载版本信息失败", "InformationDamage": "下载版本信息损坏", "InformationUnStandardized": "下载版本信息不符合规范", - "Downloading": "新版本正极速下载中", + "Downloading": "新版本正极速下载中(线路{})", "DownloadFail": "下载最新版本软件失败", "DownloadDamage": "下载最新版本软件损坏", "Restart": "重启软件更新到最新版本", diff --git "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" index 5d67e28c..b9cfa5a9 100644 --- "a/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" +++ "b/\346\231\272\347\273\230\346\225\231/src/i18n/zh-TW.jsonc" @@ -236,7 +236,7 @@ "Skip1": "推薦皮膚", "Skip2": "預設皮膚", "Skip3": "極簡時鐘", - "Skip4": "蛇年迎新" + "Skip4": "馬年迎新" }, "SettingUIScale": { @@ -410,6 +410,7 @@ "Tip": "使用插件時需要確保智繪教Inkeys的程式權限和PPT程式是一致的才能正確識別。如果依然存在問題,可以參考解決方案。", "Warn": "偵測到您的PowerPoint/WPS已經設定為一律以系統管理員身分開啟,這意味著程式需要以系統管理員身分執行才能識別到放映處理序並開啟PPT連動。如果您不希望放映軟體一律以系統管理員身分執行,可以參考解決方案。", + "Error": "外掛程式載入發生錯誤,可能是因為未安裝 .NET Framework 4.x 或全域 COM 環境發生異常。\n錯誤代碼", "BasicLogic": { @@ -479,8 +480,8 @@ }, "DesktopDrawpadBlocker": { - "N": "同類軟體懸浮視窗攔截小幫手", - "E": "攔截螢幕上 希沃白板桌面懸浮視窗 等同類軟體懸浮視窗。支援攔截常見同類軟體懸浮視窗,以及 PPT 小工具等 PPT 操控欄。" + "N": "同類軟體懸浮視窗攔截小幫手 3 Lite", + "E": "在教學一體機上,一些白板軟體的桌面懸浮窗畫筆功能不能直接關閉,桌面會出現多家畫筆工具的懸浮窗。這容易導致老師混用不同的工具而無法繼續批注,嚴重影響了教學效率,於是 DesktopDrawpadBlocker 應運而生。" }, "LnkHelper": { @@ -559,7 +560,7 @@ "InformationFail": "下載版本資訊失敗", "InformationDamage": "下載版本資訊損壞", "InformationUnStandardized": "下載版本資訊不符合規範", - "Downloading": "新版本正高速下載中", + "Downloading": "新版本正極速下載中(線路{})", "DownloadFail": "下載最新版軟體失敗", "DownloadDamage": "下載最新版軟體損壞", "Restart": "重啟軟體升級至最新版", diff --git "a/\346\231\272\347\273\230\346\225\231/src/skin/dragon.png" "b/\346\231\272\347\273\230\346\225\231/src/skin/dragon.png" index 5da37df740c3af5ca2423a3476f3fb3911c84e7a..d8654b39fe6ffdf6250eb005fd951f2a5eed9ea2 100644 GIT binary patch literal 18446 zcmV)6K*+y|P)sJ7#nw6E|Mk7>Y9->edf*U?e_csoO9oM^JXNxW)qUJuSUA_=DmB*@BB{rp6@wV zcV(m=g#LXyNcBv1Jbz3ON1AqNE{)5;y2`@-;45lU176akxe zSyPRgePU*&{@iRpdQMfP=j*yzk^~2lfb^er`~R_m_dLM1T{x}_L4eZT6}Yv#C-~k( zU+|JqA=+VHdewnhcVQbYT+aplM>;P+gd~WNH34!+ zL?9v|lu!^(%Sa9c(7Qf7m|Cq3ZXAwYzhzenaD;riOM0=iC_PZF*k7wP?4qOy5FkPL z50u5vT<}6u;FjgFmPdoq-b7E}p6-FbJCu+*R5_>P@cmg#zFfiV@hS_u)-Yi=1xw

LBB{|m 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 BR{sD1 literal 14184 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 z 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