├── src ├── cimgui_all.h ├── LICENSE.txt ├── imconfig.h ├── imstb_rectpack.h └── imstb_textedit.h ├── .editorconfig ├── fips.yml ├── src-docking ├── cimgui_all.h ├── LICENSE.txt ├── imconfig.h ├── imstb_rectpack.h └── imstb_textedit.h ├── .gitignore ├── .github └── workflows │ ├── build.yml │ └── release.yml ├── release.sh ├── LICENSE ├── README.md └── CMakeLists.txt /src/cimgui_all.h: -------------------------------------------------------------------------------- 1 | #include "cimgui.h" 2 | #include "cimgui_internal.h" 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*.yml] 4 | indent_style=space 5 | indent_size=2 -------------------------------------------------------------------------------- /fips.yml: -------------------------------------------------------------------------------- 1 | # dcimgui 2 | --- 3 | exports: 4 | modules: 5 | dcimgui: . 6 | -------------------------------------------------------------------------------- /src-docking/cimgui_all.h: -------------------------------------------------------------------------------- 1 | #include "cimgui.h" 2 | #include "cimgui_internal.h" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | imgui/ 3 | imgui-docking/ 4 | dear_bindings/ 5 | build/ 6 | src*/*.json 7 | .zig-* 8 | zig-* 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macos-latest, windows-latest] 10 | runs-on: ${{matrix.os}} 11 | steps: 12 | - uses: actions/checkout@main 13 | - run: | 14 | mkdir build 15 | cd build 16 | cmake .. 17 | cmake --build . 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Dear ImGui version tag' 8 | required: true 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | release: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@main 18 | - name: Generate 19 | run: | 20 | ./release.sh ${{ github.event.inputs.version }} 21 | - name: Commit & Push 22 | run: | 23 | git config --global user.name "GH Action" 24 | git config --global user.email "none" 25 | git add . 26 | git commit -m "updated (${{ github.event.inputs.version }})" 27 | git tag ${{ github.event.inputs.version }} 28 | git push && git push --tags 29 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | echo "Version: $1" 3 | 4 | # main branch 5 | git clone --depth 1 --branch $1 https://github.com/ocornut/imgui 6 | git clone --depth 1 --branch $1-docking https://github.com/ocornut/imgui imgui-docking 7 | git clone --depth 1 https://github.com/dearimgui/dear_bindings 8 | 9 | python3 -m venv venv 10 | source venv/bin/activate 11 | python3 -m pip install -r dear_bindings/requirements.txt 12 | 13 | mkdir -p src 14 | python3 dear_bindings/dear_bindings.py -o src/cimgui --custom-namespace-prefix ig imgui/imgui.h 15 | python3 dear_bindings/dear_bindings.py -o src/cimgui_internal --custom-namespace-prefix ig --include imgui/imgui.h imgui/imgui_internal.h 16 | cp imgui/*.h imgui/*.cpp imgui/LICENSE.txt src/ 17 | 18 | # docking branch 19 | mkdir -p src-docking 20 | python3 dear_bindings/dear_bindings.py -o src-docking/cimgui --custom-namespace-prefix ig imgui-docking/imgui.h 21 | python3 dear_bindings/dear_bindings.py -o src-docking/cimgui_internal --custom-namespace-prefix ig --include imgui-docking/imgui.h imgui-docking/imgui_internal.h 22 | cp imgui-docking/*.h imgui-docking/*.cpp imgui-docking/LICENSE.txt src-docking/ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andre Weissflog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2025 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src-docking/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2025 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/floooh/dcimgui/actions/workflows/build.yml/badge.svg)](https://github.com/floooh/dcimgui/actions/workflows/build.yml) 2 | 3 | A version-tagged all-in-one [Dear ImGui](https://github.com/ocornut/imgui) 4 | source distribution repository for C, C++ and Zig coding with: 5 | 6 | - regular and docking flavours of Dear ImGui 7 | - C bindings for both flavours (generated with the 8 | new [dear_bindings](https://github.com/dearimgui/dear_bindings) approach. 9 | 10 | The C bindings use the `ig` prefix and cimgui.h/cimgui.cpp filenames to be as 11 | compatible as possible with the 'legacy' cimgui bindings (but please be aware 12 | that there are still significant differences to the legacy cimgui bindings). 13 | 14 | The CMakeLists.txt file can be used both from regular cmake projects and 15 | from fips projects (https://floooh.github.io/fips/) and defines two 16 | static link libraries (`imgui` and `imgui-docking`). 17 | 18 | NOTE: do not use the `imgui` and `imgui-docking` libraries together in the 19 | same project since this will confuse header search paths. 20 | 21 | To use the C API: 22 | 23 | - for the regular version: link with `imgui` and include `cimgui.h` 24 | - for the docking version: link with `imgui-docking` and include `cimgui.h` 25 | - NOTE: the cimgui.h header contains duplicate symbol definitions and 26 | must be compiled in C11 mode. 27 | 28 | To use the C++ API: 29 | 30 | - for regular version: link with `imgui` and include `imgui.h` 31 | - for the docking version: link with `imgui-docking` and include `imgui.h` 32 | 33 | To use the Zig module: 34 | 35 | - add a dependency to your build.zig.zon: 36 | ``` 37 | zig fetch --save=cimgui git+https://github.com/floooh/dcimgui.git 38 | ``` 39 | - ...and see https://github.com/floooh/sokol-zig-imgui-sample/blob/main/build.zig 40 | for how to integrate the `cimgui` or `cimgui_docking` module with your Zig project 41 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # builds a combined C and C++ library called 'imgui' 2 | if (FIPS_IMPORT) 3 | fips_begin_lib(imgui) 4 | fips_files( 5 | src/cimgui.cpp 6 | src/cimgui_internal.cpp 7 | src/imgui_demo.cpp 8 | src/imgui_draw.cpp 9 | src/imgui_tables.cpp 10 | src/imgui_widgets.cpp 11 | src/imgui.cpp) 12 | fips_end_lib() 13 | fips_begin_lib(imgui-docking) 14 | fips_files( 15 | src-docking/cimgui.cpp 16 | src-docking/cimgui_internal.cpp 17 | src-docking/imgui_demo.cpp 18 | src-docking/imgui_draw.cpp 19 | src-docking/imgui_tables.cpp 20 | src-docking/imgui_widgets.cpp 21 | src-docking/imgui.cpp) 22 | fips_end_lib() 23 | else() 24 | cmake_minimum_required(VERSION 3.20) 25 | set(CMAKE_CXX_STANDARD 11) 26 | project(dcimgui) 27 | add_library(imgui 28 | src/cimgui.cpp 29 | src/cimgui_internal.cpp 30 | src/imgui_demo.cpp 31 | src/imgui_draw.cpp 32 | src/imgui_tables.cpp 33 | src/imgui_widgets.cpp 34 | src/imgui.cpp) 35 | add_library(imgui-docking 36 | src-docking/cimgui.cpp 37 | src-docking/cimgui_internal.cpp 38 | src-docking/imgui_demo.cpp 39 | src-docking/imgui_draw.cpp 40 | src-docking/imgui_tables.cpp 41 | src-docking/imgui_widgets.cpp 42 | src-docking/imgui.cpp) 43 | endif() 44 | target_include_directories(imgui PUBLIC src) 45 | target_include_directories(imgui-docking PUBLIC src-docking) 46 | if ((${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") OR (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")) 47 | target_compile_options(imgui PUBLIC -Wno-return-type-c-linkage -Wno-unused-function) 48 | target_compile_options(imgui-docking PUBLIC -Wno-return-type-c-linkage -Wno-unused-function) 49 | elseif (MSVC) 50 | target_compile_options(imgui PUBLIC /wd4190) 51 | target_compile_options(imgui-docking PUBLIC /wd4190) 52 | endif() -------------------------------------------------------------------------------- /src/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // DEAR IMGUI COMPILE-TIME OPTIONS 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // - 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. 19 | // - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes. 20 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 21 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 22 | 23 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 24 | // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 25 | // - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() 26 | // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. 27 | //#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export 28 | //#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import 29 | //#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden 30 | 31 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. 32 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 33 | 34 | //---- Disable all of Dear ImGui or don't implement standard windows/tools. 35 | // 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. 36 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 37 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. 38 | //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. 39 | 40 | //---- Don't implement some functions to reduce linkage requirements. 41 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) 42 | //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) 43 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) 44 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). 45 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 46 | //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). 47 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 48 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 49 | //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) 50 | //#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. 51 | //#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(). 52 | //#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. 53 | //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available 54 | 55 | //---- Enable Test Engine / Automation features. 56 | //#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. 57 | 58 | //---- Include imgui_user.h at the end of imgui.h as a convenience 59 | // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. 60 | //#define IMGUI_INCLUDE_IMGUI_USER_H 61 | //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" 62 | 63 | //---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. 64 | //#define IMGUI_USE_BGRA_PACKED_COLOR 65 | 66 | //---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. 67 | //#define IMGUI_USE_LEGACY_CRC32_ADLER 68 | 69 | //---- 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...) 70 | //#define IMGUI_USE_WCHAR32 71 | 72 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 73 | // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. 74 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 75 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 76 | //#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. 77 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 78 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 79 | //#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. 80 | 81 | //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) 82 | // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. 83 | //#define IMGUI_USE_STB_SPRINTF 84 | 85 | //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) 86 | // 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). 87 | // Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience. 88 | // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. 89 | //#define IMGUI_ENABLE_FREETYPE 90 | 91 | //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) 92 | // Only works in combination with IMGUI_ENABLE_FREETYPE. 93 | // - 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. 94 | // - Both require headers to be available in the include path + program to be linked with the library code (not provided). 95 | // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) 96 | //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG 97 | //#define IMGUI_ENABLE_FREETYPE_LUNASVG 98 | 99 | //---- Use stb_truetype to build and rasterize the font atlas (default) 100 | // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. 101 | //#define IMGUI_ENABLE_STB_TRUETYPE 102 | 103 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 104 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 105 | /* 106 | #define IM_VEC2_CLASS_EXTRA \ 107 | constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ 108 | operator MyVec2() const { return MyVec2(x,y); } 109 | 110 | #define IM_VEC4_CLASS_EXTRA \ 111 | constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 112 | operator MyVec4() const { return MyVec4(x,y,z,w); } 113 | */ 114 | //---- ...Or use Dear ImGui's own very basic math operators. 115 | //#define IMGUI_DEFINE_MATH_OPERATORS 116 | 117 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 118 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 119 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 120 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 121 | //#define ImDrawIdx unsigned int 122 | 123 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 124 | //struct ImDrawList; 125 | //struct ImDrawCmd; 126 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 127 | //#define ImDrawCallback MyImDrawCallback 128 | 129 | //---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) 130 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 131 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 132 | //#define IM_DEBUG_BREAK __debugbreak() 133 | 134 | //---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. 135 | // (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) 136 | //#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS 137 | 138 | //---- Debug Tools: Enable slower asserts 139 | //#define IMGUI_DEBUG_PARANOID 140 | 141 | //---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) 142 | /* 143 | namespace ImGui 144 | { 145 | void MyFunction(const char* name, MyMatrix44* mtx); 146 | } 147 | */ 148 | -------------------------------------------------------------------------------- /src-docking/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // DEAR IMGUI COMPILE-TIME OPTIONS 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) 7 | // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. 8 | //----------------------------------------------------------------------------- 9 | // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp 10 | // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 11 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 12 | // Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using. 13 | //----------------------------------------------------------------------------- 14 | 15 | #pragma once 16 | 17 | //---- Define assertion handler. Defaults to calling assert(). 18 | // - 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. 19 | // - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes. 20 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 21 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 22 | 23 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 24 | // Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. 25 | // - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions() 26 | // for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details. 27 | //#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export 28 | //#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import 29 | //#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden 30 | 31 | //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. 32 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 33 | 34 | //---- Disable all of Dear ImGui or don't implement standard windows/tools. 35 | // 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. 36 | //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. 37 | //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. 38 | //#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty. 39 | 40 | //---- Don't implement some functions to reduce linkage requirements. 41 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a) 42 | //#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW) 43 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a) 44 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME). 45 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). 46 | //#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")). 47 | //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) 48 | //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. 49 | //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) 50 | //#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. 51 | //#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(). 52 | //#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert. 53 | //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available 54 | 55 | //---- Enable Test Engine / Automation features. 56 | //#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details. 57 | 58 | //---- Include imgui_user.h at the end of imgui.h as a convenience 59 | // May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included. 60 | //#define IMGUI_INCLUDE_IMGUI_USER_H 61 | //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" 62 | 63 | //---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support. 64 | //#define IMGUI_USE_BGRA_PACKED_COLOR 65 | 66 | //---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate. 67 | //#define IMGUI_USE_LEGACY_CRC32_ADLER 68 | 69 | //---- 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...) 70 | //#define IMGUI_USE_WCHAR32 71 | 72 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 73 | // By default the embedded implementations are declared static and not available outside of Dear ImGui sources files. 74 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 75 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 76 | //#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined. 77 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 78 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 79 | //#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined. 80 | 81 | //---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined) 82 | // Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h. 83 | //#define IMGUI_USE_STB_SPRINTF 84 | 85 | //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) 86 | // 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). 87 | // Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience. 88 | // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. 89 | //#define IMGUI_ENABLE_FREETYPE 90 | 91 | //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) 92 | // Only works in combination with IMGUI_ENABLE_FREETYPE. 93 | // - 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. 94 | // - Both require headers to be available in the include path + program to be linked with the library code (not provided). 95 | // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) 96 | //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG 97 | //#define IMGUI_ENABLE_FREETYPE_LUNASVG 98 | 99 | //---- Use stb_truetype to build and rasterize the font atlas (default) 100 | // The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend. 101 | //#define IMGUI_ENABLE_STB_TRUETYPE 102 | 103 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 104 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 105 | /* 106 | #define IM_VEC2_CLASS_EXTRA \ 107 | constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \ 108 | operator MyVec2() const { return MyVec2(x,y); } 109 | 110 | #define IM_VEC4_CLASS_EXTRA \ 111 | constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 112 | operator MyVec4() const { return MyVec4(x,y,z,w); } 113 | */ 114 | //---- ...Or use Dear ImGui's own very basic math operators. 115 | //#define IMGUI_DEFINE_MATH_OPERATORS 116 | 117 | //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. 118 | // Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices). 119 | // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. 120 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 121 | //#define ImDrawIdx unsigned int 122 | 123 | //---- Override ImDrawCallback signature (will need to modify renderer backends accordingly) 124 | //struct ImDrawList; 125 | //struct ImDrawCmd; 126 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 127 | //#define ImDrawCallback MyImDrawCallback 128 | 129 | //---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase) 130 | // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) 131 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 132 | //#define IM_DEBUG_BREAK __debugbreak() 133 | 134 | //---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set. 135 | // (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use) 136 | //#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS 137 | 138 | //---- Debug Tools: Enable slower asserts 139 | //#define IMGUI_DEBUG_PARANOID 140 | 141 | //---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files) 142 | /* 143 | namespace ImGui 144 | { 145 | void MyFunction(const char* name, MyMatrix44* mtx); 146 | } 147 | */ 148 | -------------------------------------------------------------------------------- /src/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.01. 3 | // Grep for [DEAR IMGUI] to find the changes. 4 | // 5 | // stb_rect_pack.h - v1.01 - public domain - rectangle packing 6 | // Sean Barrett 2014 7 | // 8 | // Useful for e.g. packing rectangular textures into an atlas. 9 | // Does not do rotation. 10 | // 11 | // Before #including, 12 | // 13 | // #define STB_RECT_PACK_IMPLEMENTATION 14 | // 15 | // in the file that you want to have the implementation. 16 | // 17 | // Not necessarily the awesomest packing method, but better than 18 | // the totally naive one in stb_truetype (which is primarily what 19 | // this is meant to replace). 20 | // 21 | // Has only had a few tests run, may have issues. 22 | // 23 | // More docs to come. 24 | // 25 | // No memory allocations; uses qsort() and assert() from stdlib. 26 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 27 | // 28 | // This library currently uses the Skyline Bottom-Left algorithm. 29 | // 30 | // Please note: better rectangle packers are welcome! Please 31 | // implement them to the same API, but with a different init 32 | // function. 33 | // 34 | // Credits 35 | // 36 | // Library 37 | // Sean Barrett 38 | // Minor features 39 | // Martins Mozeiko 40 | // github:IntellectualKitty 41 | // 42 | // Bugfixes / warning fixes 43 | // Jeremy Jaussaud 44 | // Fabian Giesen 45 | // 46 | // Version history: 47 | // 48 | // 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section 49 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 50 | // 0.99 (2019-02-07) warning fixes 51 | // 0.11 (2017-03-03) return packing success/fail result 52 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 53 | // 0.09 (2016-08-27) fix compiler warnings 54 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 55 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 56 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 57 | // 0.05: added STBRP_ASSERT to allow replacing assert 58 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 59 | // 0.01: initial release 60 | // 61 | // LICENSE 62 | // 63 | // See end of file for license information. 64 | 65 | ////////////////////////////////////////////////////////////////////////////// 66 | // 67 | // INCLUDE SECTION 68 | // 69 | 70 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 71 | #define STB_INCLUDE_STB_RECT_PACK_H 72 | 73 | #define STB_RECT_PACK_VERSION 1 74 | 75 | #ifdef STBRP_STATIC 76 | #define STBRP_DEF static 77 | #else 78 | #define STBRP_DEF extern 79 | #endif 80 | 81 | #ifdef __cplusplus 82 | extern "C" { 83 | #endif 84 | 85 | typedef struct stbrp_context stbrp_context; 86 | typedef struct stbrp_node stbrp_node; 87 | typedef struct stbrp_rect stbrp_rect; 88 | 89 | typedef int stbrp_coord; 90 | 91 | #define STBRP__MAXVAL 0x7fffffff 92 | // Mostly for internal use, but this is the maximum supported coordinate value. 93 | 94 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 95 | // Assign packed locations to rectangles. The rectangles are of type 96 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 97 | // are 'num_rects' many of them. 98 | // 99 | // Rectangles which are successfully packed have the 'was_packed' flag 100 | // set to a non-zero value and 'x' and 'y' store the minimum location 101 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 102 | // if you imagine y increasing downwards). Rectangles which do not fit 103 | // have the 'was_packed' flag set to 0. 104 | // 105 | // You should not try to access the 'rects' array from another thread 106 | // while this function is running, as the function temporarily reorders 107 | // the array while it executes. 108 | // 109 | // To pack into another rectangle, you need to call stbrp_init_target 110 | // again. To continue packing into the same rectangle, you can call 111 | // this function again. Calling this multiple times with multiple rect 112 | // arrays will probably produce worse packing results than calling it 113 | // a single time with the full rectangle array, but the option is 114 | // available. 115 | // 116 | // The function returns 1 if all of the rectangles were successfully 117 | // packed and 0 otherwise. 118 | 119 | struct stbrp_rect 120 | { 121 | // reserved for your use: 122 | int id; 123 | 124 | // input: 125 | stbrp_coord w, h; 126 | 127 | // output: 128 | stbrp_coord x, y; 129 | int was_packed; // non-zero if valid packing 130 | 131 | }; // 16 bytes, nominally 132 | 133 | 134 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 135 | // Initialize a rectangle packer to: 136 | // pack a rectangle that is 'width' by 'height' in dimensions 137 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 138 | // 139 | // You must call this function every time you start packing into a new target. 140 | // 141 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 142 | // the following stbrp_pack_rects() call (or calls), but can be freed after 143 | // the call (or calls) finish. 144 | // 145 | // Note: to guarantee best results, either: 146 | // 1. make sure 'num_nodes' >= 'width' 147 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 148 | // 149 | // If you don't do either of the above things, widths will be quantized to multiples 150 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 151 | // 152 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 153 | // may run out of temporary storage and be unable to pack some rectangles. 154 | 155 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 156 | // Optionally call this function after init but before doing any packing to 157 | // change the handling of the out-of-temp-memory scenario, described above. 158 | // If you call init again, this will be reset to the default (false). 159 | 160 | 161 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 162 | // Optionally select which packing heuristic the library should use. Different 163 | // heuristics will produce better/worse results for different data sets. 164 | // If you call init again, this will be reset to the default. 165 | 166 | enum 167 | { 168 | STBRP_HEURISTIC_Skyline_default=0, 169 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 170 | STBRP_HEURISTIC_Skyline_BF_sortHeight 171 | }; 172 | 173 | 174 | ////////////////////////////////////////////////////////////////////////////// 175 | // 176 | // the details of the following structures don't matter to you, but they must 177 | // be visible so you can handle the memory allocations for them 178 | 179 | struct stbrp_node 180 | { 181 | stbrp_coord x,y; 182 | stbrp_node *next; 183 | }; 184 | 185 | struct stbrp_context 186 | { 187 | int width; 188 | int height; 189 | int align; 190 | int init_mode; 191 | int heuristic; 192 | int num_nodes; 193 | stbrp_node *active_head; 194 | stbrp_node *free_head; 195 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 196 | }; 197 | 198 | #ifdef __cplusplus 199 | } 200 | #endif 201 | 202 | #endif 203 | 204 | ////////////////////////////////////////////////////////////////////////////// 205 | // 206 | // IMPLEMENTATION SECTION 207 | // 208 | 209 | #ifdef STB_RECT_PACK_IMPLEMENTATION 210 | #ifndef STBRP_SORT 211 | #include 212 | #define STBRP_SORT qsort 213 | #endif 214 | 215 | #ifndef STBRP_ASSERT 216 | #include 217 | #define STBRP_ASSERT assert 218 | #endif 219 | 220 | #ifdef _MSC_VER 221 | #define STBRP__NOTUSED(v) (void)(v) 222 | #define STBRP__CDECL __cdecl 223 | #else 224 | #define STBRP__NOTUSED(v) (void)sizeof(v) 225 | #define STBRP__CDECL 226 | #endif 227 | 228 | enum 229 | { 230 | STBRP__INIT_skyline = 1 231 | }; 232 | 233 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 234 | { 235 | switch (context->init_mode) { 236 | case STBRP__INIT_skyline: 237 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 238 | context->heuristic = heuristic; 239 | break; 240 | default: 241 | STBRP_ASSERT(0); 242 | } 243 | } 244 | 245 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 246 | { 247 | if (allow_out_of_mem) 248 | // if it's ok to run out of memory, then don't bother aligning them; 249 | // this gives better packing, but may fail due to OOM (even though 250 | // the rectangles easily fit). @TODO a smarter approach would be to only 251 | // quantize once we've hit OOM, then we could get rid of this parameter. 252 | context->align = 1; 253 | else { 254 | // if it's not ok to run out of memory, then quantize the widths 255 | // so that num_nodes is always enough nodes. 256 | // 257 | // I.e. num_nodes * align >= width 258 | // align >= width / num_nodes 259 | // align = ceil(width/num_nodes) 260 | 261 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 262 | } 263 | } 264 | 265 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 266 | { 267 | int i; 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | context->extra[1].y = (1<<30); 287 | context->extra[1].next = NULL; 288 | } 289 | 290 | // find minimum y position if it starts at x1 291 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 292 | { 293 | stbrp_node *node = first; 294 | int x1 = x0 + width; 295 | int min_y, visited_width, waste_area; 296 | 297 | STBRP__NOTUSED(c); 298 | 299 | STBRP_ASSERT(first->x <= x0); 300 | 301 | #if 0 302 | // skip in case we're past the node 303 | while (node->next->x <= x0) 304 | ++node; 305 | #else 306 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 307 | #endif 308 | 309 | STBRP_ASSERT(node->x <= x0); 310 | 311 | min_y = 0; 312 | waste_area = 0; 313 | visited_width = 0; 314 | while (node->x < x1) { 315 | if (node->y > min_y) { 316 | // raise min_y higher. 317 | // we've accounted for all waste up to min_y, 318 | // but we'll now add more waste for everything we've visted 319 | waste_area += visited_width * (node->y - min_y); 320 | min_y = node->y; 321 | // the first time through, visited_width might be reduced 322 | if (node->x < x0) 323 | visited_width += node->next->x - x0; 324 | else 325 | visited_width += node->next->x - node->x; 326 | } else { 327 | // add waste area 328 | int under_width = node->next->x - node->x; 329 | if (under_width + visited_width > width) 330 | under_width = width - visited_width; 331 | waste_area += under_width * (min_y - node->y); 332 | visited_width += under_width; 333 | } 334 | node = node->next; 335 | } 336 | 337 | *pwaste = waste_area; 338 | return min_y; 339 | } 340 | 341 | typedef struct 342 | { 343 | int x,y; 344 | stbrp_node **prev_link; 345 | } stbrp__findresult; 346 | 347 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 348 | { 349 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 350 | stbrp__findresult fr; 351 | stbrp_node **prev, *node, *tail, **best = NULL; 352 | 353 | // align to multiple of c->align 354 | width = (width + c->align - 1); 355 | width -= width % c->align; 356 | STBRP_ASSERT(width % c->align == 0); 357 | 358 | // if it can't possibly fit, bail immediately 359 | if (width > c->width || height > c->height) { 360 | fr.prev_link = NULL; 361 | fr.x = fr.y = 0; 362 | return fr; 363 | } 364 | 365 | node = c->active_head; 366 | prev = &c->active_head; 367 | while (node->x + width <= c->width) { 368 | int y,waste; 369 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 370 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 371 | // bottom left 372 | if (y < best_y) { 373 | best_y = y; 374 | best = prev; 375 | } 376 | } else { 377 | // best-fit 378 | if (y + height <= c->height) { 379 | // can only use it if it first vertically 380 | if (y < best_y || (y == best_y && waste < best_waste)) { 381 | best_y = y; 382 | best_waste = waste; 383 | best = prev; 384 | } 385 | } 386 | } 387 | prev = &node->next; 388 | node = node->next; 389 | } 390 | 391 | best_x = (best == NULL) ? 0 : (*best)->x; 392 | 393 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 394 | // 395 | // e.g, if fitting 396 | // 397 | // ____________________ 398 | // |____________________| 399 | // 400 | // into 401 | // 402 | // | | 403 | // | ____________| 404 | // |____________| 405 | // 406 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 407 | // 408 | // This makes BF take about 2x the time 409 | 410 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 411 | tail = c->active_head; 412 | node = c->active_head; 413 | prev = &c->active_head; 414 | // find first node that's admissible 415 | while (tail->x < width) 416 | tail = tail->next; 417 | while (tail) { 418 | int xpos = tail->x - width; 419 | int y,waste; 420 | STBRP_ASSERT(xpos >= 0); 421 | // find the left position that matches this 422 | while (node->next->x <= xpos) { 423 | prev = &node->next; 424 | node = node->next; 425 | } 426 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 427 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 428 | if (y + height <= c->height) { 429 | if (y <= best_y) { 430 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 431 | best_x = xpos; 432 | //STBRP_ASSERT(y <= best_y); [DEAR IMGUI] 433 | best_y = y; 434 | best_waste = waste; 435 | best = prev; 436 | } 437 | } 438 | } 439 | tail = tail->next; 440 | } 441 | } 442 | 443 | fr.prev_link = best; 444 | fr.x = best_x; 445 | fr.y = best_y; 446 | return fr; 447 | } 448 | 449 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 450 | { 451 | // find best position according to heuristic 452 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 453 | stbrp_node *node, *cur; 454 | 455 | // bail if: 456 | // 1. it failed 457 | // 2. the best node doesn't fit (we don't always check this) 458 | // 3. we're out of memory 459 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 460 | res.prev_link = NULL; 461 | return res; 462 | } 463 | 464 | // on success, create new node 465 | node = context->free_head; 466 | node->x = (stbrp_coord) res.x; 467 | node->y = (stbrp_coord) (res.y + height); 468 | 469 | context->free_head = node->next; 470 | 471 | // insert the new node into the right starting point, and 472 | // let 'cur' point to the remaining nodes needing to be 473 | // stiched back in 474 | 475 | cur = *res.prev_link; 476 | if (cur->x < res.x) { 477 | // preserve the existing one, so start testing with the next one 478 | stbrp_node *next = cur->next; 479 | cur->next = node; 480 | cur = next; 481 | } else { 482 | *res.prev_link = node; 483 | } 484 | 485 | // from here, traverse cur and free the nodes, until we get to one 486 | // that shouldn't be freed 487 | while (cur->next && cur->next->x <= res.x + width) { 488 | stbrp_node *next = cur->next; 489 | // move the current node to the free list 490 | cur->next = context->free_head; 491 | context->free_head = cur; 492 | cur = next; 493 | } 494 | 495 | // stitch the list back in 496 | node->next = cur; 497 | 498 | if (cur->x < res.x + width) 499 | cur->x = (stbrp_coord) (res.x + width); 500 | 501 | #ifdef _DEBUG 502 | cur = context->active_head; 503 | while (cur->x < context->width) { 504 | STBRP_ASSERT(cur->x < cur->next->x); 505 | cur = cur->next; 506 | } 507 | STBRP_ASSERT(cur->next == NULL); 508 | 509 | { 510 | int count=0; 511 | cur = context->active_head; 512 | while (cur) { 513 | cur = cur->next; 514 | ++count; 515 | } 516 | cur = context->free_head; 517 | while (cur) { 518 | cur = cur->next; 519 | ++count; 520 | } 521 | STBRP_ASSERT(count == context->num_nodes+2); 522 | } 523 | #endif 524 | 525 | return res; 526 | } 527 | 528 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 529 | { 530 | const stbrp_rect *p = (const stbrp_rect *) a; 531 | const stbrp_rect *q = (const stbrp_rect *) b; 532 | if (p->h > q->h) 533 | return -1; 534 | if (p->h < q->h) 535 | return 1; 536 | return (p->w > q->w) ? -1 : (p->w < q->w); 537 | } 538 | 539 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 540 | { 541 | const stbrp_rect *p = (const stbrp_rect *) a; 542 | const stbrp_rect *q = (const stbrp_rect *) b; 543 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 544 | } 545 | 546 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 547 | { 548 | int i, all_rects_packed = 1; 549 | 550 | // we use the 'was_packed' field internally to allow sorting/unsorting 551 | for (i=0; i < num_rects; ++i) { 552 | rects[i].was_packed = i; 553 | } 554 | 555 | // sort according to heuristic 556 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 557 | 558 | for (i=0; i < num_rects; ++i) { 559 | if (rects[i].w == 0 || rects[i].h == 0) { 560 | rects[i].x = rects[i].y = 0; // empty rect needs no space 561 | } else { 562 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 563 | if (fr.prev_link) { 564 | rects[i].x = (stbrp_coord) fr.x; 565 | rects[i].y = (stbrp_coord) fr.y; 566 | } else { 567 | rects[i].x = rects[i].y = STBRP__MAXVAL; 568 | } 569 | } 570 | } 571 | 572 | // unsort 573 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 574 | 575 | // set was_packed flags and all_rects_packed status 576 | for (i=0; i < num_rects; ++i) { 577 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 578 | if (!rects[i].was_packed) 579 | all_rects_packed = 0; 580 | } 581 | 582 | // return the all_rects_packed status 583 | return all_rects_packed; 584 | } 585 | #endif 586 | 587 | /* 588 | ------------------------------------------------------------------------------ 589 | This software is available under 2 licenses -- choose whichever you prefer. 590 | ------------------------------------------------------------------------------ 591 | ALTERNATIVE A - MIT License 592 | Copyright (c) 2017 Sean Barrett 593 | Permission is hereby granted, free of charge, to any person obtaining a copy of 594 | this software and associated documentation files (the "Software"), to deal in 595 | the Software without restriction, including without limitation the rights to 596 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 597 | of the Software, and to permit persons to whom the Software is furnished to do 598 | so, subject to the following conditions: 599 | The above copyright notice and this permission notice shall be included in all 600 | copies or substantial portions of the Software. 601 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 602 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 603 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 604 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 605 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 606 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 607 | SOFTWARE. 608 | ------------------------------------------------------------------------------ 609 | ALTERNATIVE B - Public Domain (www.unlicense.org) 610 | This is free and unencumbered software released into the public domain. 611 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 612 | software, either in source code form or as a compiled binary, for any purpose, 613 | commercial or non-commercial, and by any means. 614 | In jurisdictions that recognize copyright laws, the author or authors of this 615 | software dedicate any and all copyright interest in the software to the public 616 | domain. We make this dedication for the benefit of the public at large and to 617 | the detriment of our heirs and successors. We intend this dedication to be an 618 | overt act of relinquishment in perpetuity of all present and future rights to 619 | this software under copyright law. 620 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 621 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 622 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 623 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 624 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 625 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 626 | ------------------------------------------------------------------------------ 627 | */ 628 | -------------------------------------------------------------------------------- /src-docking/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.01. 3 | // Grep for [DEAR IMGUI] to find the changes. 4 | // 5 | // stb_rect_pack.h - v1.01 - public domain - rectangle packing 6 | // Sean Barrett 2014 7 | // 8 | // Useful for e.g. packing rectangular textures into an atlas. 9 | // Does not do rotation. 10 | // 11 | // Before #including, 12 | // 13 | // #define STB_RECT_PACK_IMPLEMENTATION 14 | // 15 | // in the file that you want to have the implementation. 16 | // 17 | // Not necessarily the awesomest packing method, but better than 18 | // the totally naive one in stb_truetype (which is primarily what 19 | // this is meant to replace). 20 | // 21 | // Has only had a few tests run, may have issues. 22 | // 23 | // More docs to come. 24 | // 25 | // No memory allocations; uses qsort() and assert() from stdlib. 26 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 27 | // 28 | // This library currently uses the Skyline Bottom-Left algorithm. 29 | // 30 | // Please note: better rectangle packers are welcome! Please 31 | // implement them to the same API, but with a different init 32 | // function. 33 | // 34 | // Credits 35 | // 36 | // Library 37 | // Sean Barrett 38 | // Minor features 39 | // Martins Mozeiko 40 | // github:IntellectualKitty 41 | // 42 | // Bugfixes / warning fixes 43 | // Jeremy Jaussaud 44 | // Fabian Giesen 45 | // 46 | // Version history: 47 | // 48 | // 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section 49 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 50 | // 0.99 (2019-02-07) warning fixes 51 | // 0.11 (2017-03-03) return packing success/fail result 52 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 53 | // 0.09 (2016-08-27) fix compiler warnings 54 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 55 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 56 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 57 | // 0.05: added STBRP_ASSERT to allow replacing assert 58 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 59 | // 0.01: initial release 60 | // 61 | // LICENSE 62 | // 63 | // See end of file for license information. 64 | 65 | ////////////////////////////////////////////////////////////////////////////// 66 | // 67 | // INCLUDE SECTION 68 | // 69 | 70 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 71 | #define STB_INCLUDE_STB_RECT_PACK_H 72 | 73 | #define STB_RECT_PACK_VERSION 1 74 | 75 | #ifdef STBRP_STATIC 76 | #define STBRP_DEF static 77 | #else 78 | #define STBRP_DEF extern 79 | #endif 80 | 81 | #ifdef __cplusplus 82 | extern "C" { 83 | #endif 84 | 85 | typedef struct stbrp_context stbrp_context; 86 | typedef struct stbrp_node stbrp_node; 87 | typedef struct stbrp_rect stbrp_rect; 88 | 89 | typedef int stbrp_coord; 90 | 91 | #define STBRP__MAXVAL 0x7fffffff 92 | // Mostly for internal use, but this is the maximum supported coordinate value. 93 | 94 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 95 | // Assign packed locations to rectangles. The rectangles are of type 96 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 97 | // are 'num_rects' many of them. 98 | // 99 | // Rectangles which are successfully packed have the 'was_packed' flag 100 | // set to a non-zero value and 'x' and 'y' store the minimum location 101 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 102 | // if you imagine y increasing downwards). Rectangles which do not fit 103 | // have the 'was_packed' flag set to 0. 104 | // 105 | // You should not try to access the 'rects' array from another thread 106 | // while this function is running, as the function temporarily reorders 107 | // the array while it executes. 108 | // 109 | // To pack into another rectangle, you need to call stbrp_init_target 110 | // again. To continue packing into the same rectangle, you can call 111 | // this function again. Calling this multiple times with multiple rect 112 | // arrays will probably produce worse packing results than calling it 113 | // a single time with the full rectangle array, but the option is 114 | // available. 115 | // 116 | // The function returns 1 if all of the rectangles were successfully 117 | // packed and 0 otherwise. 118 | 119 | struct stbrp_rect 120 | { 121 | // reserved for your use: 122 | int id; 123 | 124 | // input: 125 | stbrp_coord w, h; 126 | 127 | // output: 128 | stbrp_coord x, y; 129 | int was_packed; // non-zero if valid packing 130 | 131 | }; // 16 bytes, nominally 132 | 133 | 134 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 135 | // Initialize a rectangle packer to: 136 | // pack a rectangle that is 'width' by 'height' in dimensions 137 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 138 | // 139 | // You must call this function every time you start packing into a new target. 140 | // 141 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 142 | // the following stbrp_pack_rects() call (or calls), but can be freed after 143 | // the call (or calls) finish. 144 | // 145 | // Note: to guarantee best results, either: 146 | // 1. make sure 'num_nodes' >= 'width' 147 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 148 | // 149 | // If you don't do either of the above things, widths will be quantized to multiples 150 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 151 | // 152 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 153 | // may run out of temporary storage and be unable to pack some rectangles. 154 | 155 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 156 | // Optionally call this function after init but before doing any packing to 157 | // change the handling of the out-of-temp-memory scenario, described above. 158 | // If you call init again, this will be reset to the default (false). 159 | 160 | 161 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 162 | // Optionally select which packing heuristic the library should use. Different 163 | // heuristics will produce better/worse results for different data sets. 164 | // If you call init again, this will be reset to the default. 165 | 166 | enum 167 | { 168 | STBRP_HEURISTIC_Skyline_default=0, 169 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 170 | STBRP_HEURISTIC_Skyline_BF_sortHeight 171 | }; 172 | 173 | 174 | ////////////////////////////////////////////////////////////////////////////// 175 | // 176 | // the details of the following structures don't matter to you, but they must 177 | // be visible so you can handle the memory allocations for them 178 | 179 | struct stbrp_node 180 | { 181 | stbrp_coord x,y; 182 | stbrp_node *next; 183 | }; 184 | 185 | struct stbrp_context 186 | { 187 | int width; 188 | int height; 189 | int align; 190 | int init_mode; 191 | int heuristic; 192 | int num_nodes; 193 | stbrp_node *active_head; 194 | stbrp_node *free_head; 195 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 196 | }; 197 | 198 | #ifdef __cplusplus 199 | } 200 | #endif 201 | 202 | #endif 203 | 204 | ////////////////////////////////////////////////////////////////////////////// 205 | // 206 | // IMPLEMENTATION SECTION 207 | // 208 | 209 | #ifdef STB_RECT_PACK_IMPLEMENTATION 210 | #ifndef STBRP_SORT 211 | #include 212 | #define STBRP_SORT qsort 213 | #endif 214 | 215 | #ifndef STBRP_ASSERT 216 | #include 217 | #define STBRP_ASSERT assert 218 | #endif 219 | 220 | #ifdef _MSC_VER 221 | #define STBRP__NOTUSED(v) (void)(v) 222 | #define STBRP__CDECL __cdecl 223 | #else 224 | #define STBRP__NOTUSED(v) (void)sizeof(v) 225 | #define STBRP__CDECL 226 | #endif 227 | 228 | enum 229 | { 230 | STBRP__INIT_skyline = 1 231 | }; 232 | 233 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 234 | { 235 | switch (context->init_mode) { 236 | case STBRP__INIT_skyline: 237 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 238 | context->heuristic = heuristic; 239 | break; 240 | default: 241 | STBRP_ASSERT(0); 242 | } 243 | } 244 | 245 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 246 | { 247 | if (allow_out_of_mem) 248 | // if it's ok to run out of memory, then don't bother aligning them; 249 | // this gives better packing, but may fail due to OOM (even though 250 | // the rectangles easily fit). @TODO a smarter approach would be to only 251 | // quantize once we've hit OOM, then we could get rid of this parameter. 252 | context->align = 1; 253 | else { 254 | // if it's not ok to run out of memory, then quantize the widths 255 | // so that num_nodes is always enough nodes. 256 | // 257 | // I.e. num_nodes * align >= width 258 | // align >= width / num_nodes 259 | // align = ceil(width/num_nodes) 260 | 261 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 262 | } 263 | } 264 | 265 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 266 | { 267 | int i; 268 | 269 | for (i=0; i < num_nodes-1; ++i) 270 | nodes[i].next = &nodes[i+1]; 271 | nodes[i].next = NULL; 272 | context->init_mode = STBRP__INIT_skyline; 273 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 274 | context->free_head = &nodes[0]; 275 | context->active_head = &context->extra[0]; 276 | context->width = width; 277 | context->height = height; 278 | context->num_nodes = num_nodes; 279 | stbrp_setup_allow_out_of_mem(context, 0); 280 | 281 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 282 | context->extra[0].x = 0; 283 | context->extra[0].y = 0; 284 | context->extra[0].next = &context->extra[1]; 285 | context->extra[1].x = (stbrp_coord) width; 286 | context->extra[1].y = (1<<30); 287 | context->extra[1].next = NULL; 288 | } 289 | 290 | // find minimum y position if it starts at x1 291 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 292 | { 293 | stbrp_node *node = first; 294 | int x1 = x0 + width; 295 | int min_y, visited_width, waste_area; 296 | 297 | STBRP__NOTUSED(c); 298 | 299 | STBRP_ASSERT(first->x <= x0); 300 | 301 | #if 0 302 | // skip in case we're past the node 303 | while (node->next->x <= x0) 304 | ++node; 305 | #else 306 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 307 | #endif 308 | 309 | STBRP_ASSERT(node->x <= x0); 310 | 311 | min_y = 0; 312 | waste_area = 0; 313 | visited_width = 0; 314 | while (node->x < x1) { 315 | if (node->y > min_y) { 316 | // raise min_y higher. 317 | // we've accounted for all waste up to min_y, 318 | // but we'll now add more waste for everything we've visted 319 | waste_area += visited_width * (node->y - min_y); 320 | min_y = node->y; 321 | // the first time through, visited_width might be reduced 322 | if (node->x < x0) 323 | visited_width += node->next->x - x0; 324 | else 325 | visited_width += node->next->x - node->x; 326 | } else { 327 | // add waste area 328 | int under_width = node->next->x - node->x; 329 | if (under_width + visited_width > width) 330 | under_width = width - visited_width; 331 | waste_area += under_width * (min_y - node->y); 332 | visited_width += under_width; 333 | } 334 | node = node->next; 335 | } 336 | 337 | *pwaste = waste_area; 338 | return min_y; 339 | } 340 | 341 | typedef struct 342 | { 343 | int x,y; 344 | stbrp_node **prev_link; 345 | } stbrp__findresult; 346 | 347 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 348 | { 349 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 350 | stbrp__findresult fr; 351 | stbrp_node **prev, *node, *tail, **best = NULL; 352 | 353 | // align to multiple of c->align 354 | width = (width + c->align - 1); 355 | width -= width % c->align; 356 | STBRP_ASSERT(width % c->align == 0); 357 | 358 | // if it can't possibly fit, bail immediately 359 | if (width > c->width || height > c->height) { 360 | fr.prev_link = NULL; 361 | fr.x = fr.y = 0; 362 | return fr; 363 | } 364 | 365 | node = c->active_head; 366 | prev = &c->active_head; 367 | while (node->x + width <= c->width) { 368 | int y,waste; 369 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 370 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 371 | // bottom left 372 | if (y < best_y) { 373 | best_y = y; 374 | best = prev; 375 | } 376 | } else { 377 | // best-fit 378 | if (y + height <= c->height) { 379 | // can only use it if it first vertically 380 | if (y < best_y || (y == best_y && waste < best_waste)) { 381 | best_y = y; 382 | best_waste = waste; 383 | best = prev; 384 | } 385 | } 386 | } 387 | prev = &node->next; 388 | node = node->next; 389 | } 390 | 391 | best_x = (best == NULL) ? 0 : (*best)->x; 392 | 393 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 394 | // 395 | // e.g, if fitting 396 | // 397 | // ____________________ 398 | // |____________________| 399 | // 400 | // into 401 | // 402 | // | | 403 | // | ____________| 404 | // |____________| 405 | // 406 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 407 | // 408 | // This makes BF take about 2x the time 409 | 410 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 411 | tail = c->active_head; 412 | node = c->active_head; 413 | prev = &c->active_head; 414 | // find first node that's admissible 415 | while (tail->x < width) 416 | tail = tail->next; 417 | while (tail) { 418 | int xpos = tail->x - width; 419 | int y,waste; 420 | STBRP_ASSERT(xpos >= 0); 421 | // find the left position that matches this 422 | while (node->next->x <= xpos) { 423 | prev = &node->next; 424 | node = node->next; 425 | } 426 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 427 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 428 | if (y + height <= c->height) { 429 | if (y <= best_y) { 430 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 431 | best_x = xpos; 432 | //STBRP_ASSERT(y <= best_y); [DEAR IMGUI] 433 | best_y = y; 434 | best_waste = waste; 435 | best = prev; 436 | } 437 | } 438 | } 439 | tail = tail->next; 440 | } 441 | } 442 | 443 | fr.prev_link = best; 444 | fr.x = best_x; 445 | fr.y = best_y; 446 | return fr; 447 | } 448 | 449 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 450 | { 451 | // find best position according to heuristic 452 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 453 | stbrp_node *node, *cur; 454 | 455 | // bail if: 456 | // 1. it failed 457 | // 2. the best node doesn't fit (we don't always check this) 458 | // 3. we're out of memory 459 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 460 | res.prev_link = NULL; 461 | return res; 462 | } 463 | 464 | // on success, create new node 465 | node = context->free_head; 466 | node->x = (stbrp_coord) res.x; 467 | node->y = (stbrp_coord) (res.y + height); 468 | 469 | context->free_head = node->next; 470 | 471 | // insert the new node into the right starting point, and 472 | // let 'cur' point to the remaining nodes needing to be 473 | // stiched back in 474 | 475 | cur = *res.prev_link; 476 | if (cur->x < res.x) { 477 | // preserve the existing one, so start testing with the next one 478 | stbrp_node *next = cur->next; 479 | cur->next = node; 480 | cur = next; 481 | } else { 482 | *res.prev_link = node; 483 | } 484 | 485 | // from here, traverse cur and free the nodes, until we get to one 486 | // that shouldn't be freed 487 | while (cur->next && cur->next->x <= res.x + width) { 488 | stbrp_node *next = cur->next; 489 | // move the current node to the free list 490 | cur->next = context->free_head; 491 | context->free_head = cur; 492 | cur = next; 493 | } 494 | 495 | // stitch the list back in 496 | node->next = cur; 497 | 498 | if (cur->x < res.x + width) 499 | cur->x = (stbrp_coord) (res.x + width); 500 | 501 | #ifdef _DEBUG 502 | cur = context->active_head; 503 | while (cur->x < context->width) { 504 | STBRP_ASSERT(cur->x < cur->next->x); 505 | cur = cur->next; 506 | } 507 | STBRP_ASSERT(cur->next == NULL); 508 | 509 | { 510 | int count=0; 511 | cur = context->active_head; 512 | while (cur) { 513 | cur = cur->next; 514 | ++count; 515 | } 516 | cur = context->free_head; 517 | while (cur) { 518 | cur = cur->next; 519 | ++count; 520 | } 521 | STBRP_ASSERT(count == context->num_nodes+2); 522 | } 523 | #endif 524 | 525 | return res; 526 | } 527 | 528 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 529 | { 530 | const stbrp_rect *p = (const stbrp_rect *) a; 531 | const stbrp_rect *q = (const stbrp_rect *) b; 532 | if (p->h > q->h) 533 | return -1; 534 | if (p->h < q->h) 535 | return 1; 536 | return (p->w > q->w) ? -1 : (p->w < q->w); 537 | } 538 | 539 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 540 | { 541 | const stbrp_rect *p = (const stbrp_rect *) a; 542 | const stbrp_rect *q = (const stbrp_rect *) b; 543 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 544 | } 545 | 546 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 547 | { 548 | int i, all_rects_packed = 1; 549 | 550 | // we use the 'was_packed' field internally to allow sorting/unsorting 551 | for (i=0; i < num_rects; ++i) { 552 | rects[i].was_packed = i; 553 | } 554 | 555 | // sort according to heuristic 556 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 557 | 558 | for (i=0; i < num_rects; ++i) { 559 | if (rects[i].w == 0 || rects[i].h == 0) { 560 | rects[i].x = rects[i].y = 0; // empty rect needs no space 561 | } else { 562 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 563 | if (fr.prev_link) { 564 | rects[i].x = (stbrp_coord) fr.x; 565 | rects[i].y = (stbrp_coord) fr.y; 566 | } else { 567 | rects[i].x = rects[i].y = STBRP__MAXVAL; 568 | } 569 | } 570 | } 571 | 572 | // unsort 573 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 574 | 575 | // set was_packed flags and all_rects_packed status 576 | for (i=0; i < num_rects; ++i) { 577 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 578 | if (!rects[i].was_packed) 579 | all_rects_packed = 0; 580 | } 581 | 582 | // return the all_rects_packed status 583 | return all_rects_packed; 584 | } 585 | #endif 586 | 587 | /* 588 | ------------------------------------------------------------------------------ 589 | This software is available under 2 licenses -- choose whichever you prefer. 590 | ------------------------------------------------------------------------------ 591 | ALTERNATIVE A - MIT License 592 | Copyright (c) 2017 Sean Barrett 593 | Permission is hereby granted, free of charge, to any person obtaining a copy of 594 | this software and associated documentation files (the "Software"), to deal in 595 | the Software without restriction, including without limitation the rights to 596 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 597 | of the Software, and to permit persons to whom the Software is furnished to do 598 | so, subject to the following conditions: 599 | The above copyright notice and this permission notice shall be included in all 600 | copies or substantial portions of the Software. 601 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 602 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 603 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 604 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 605 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 606 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 607 | SOFTWARE. 608 | ------------------------------------------------------------------------------ 609 | ALTERNATIVE B - Public Domain (www.unlicense.org) 610 | This is free and unencumbered software released into the public domain. 611 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 612 | software, either in source code form or as a compiled binary, for any purpose, 613 | commercial or non-commercial, and by any means. 614 | In jurisdictions that recognize copyright laws, the author or authors of this 615 | software dedicate any and all copyright interest in the software to the public 616 | domain. We make this dedication for the benefit of the public at large and to 617 | the detriment of our heirs and successors. We intend this dedication to be an 618 | overt act of relinquishment in perpetuity of all present and future rights to 619 | this software under copyright law. 620 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 621 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 622 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 623 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 624 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 625 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 626 | ------------------------------------------------------------------------------ 627 | */ 628 | -------------------------------------------------------------------------------- /src/imstb_textedit.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_textedit.h 1.14. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) 5 | // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) 6 | // - Added name to struct or it may be forward declared in our code. 7 | // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) 8 | // - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. 9 | // Grep for [DEAR IMGUI] to find some changes. 10 | // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* 11 | 12 | // stb_textedit.h - v1.14 - public domain - Sean Barrett 13 | // Development of this library was sponsored by RAD Game Tools 14 | // 15 | // This C header file implements the guts of a multi-line text-editing 16 | // widget; you implement display, word-wrapping, and low-level string 17 | // insertion/deletion, and stb_textedit will map user inputs into 18 | // insertions & deletions, plus updates to the cursor position, 19 | // selection state, and undo state. 20 | // 21 | // It is intended for use in games and other systems that need to build 22 | // their own custom widgets and which do not have heavy text-editing 23 | // requirements (this library is not recommended for use for editing large 24 | // texts, as its performance does not scale and it has limited undo). 25 | // 26 | // Non-trivial behaviors are modelled after Windows text controls. 27 | // 28 | // 29 | // LICENSE 30 | // 31 | // See end of file for license information. 32 | // 33 | // 34 | // DEPENDENCIES 35 | // 36 | // Uses the C runtime function 'memmove', which you can override 37 | // by defining IMSTB_TEXTEDIT_memmove before the implementation. 38 | // Uses no other functions. Performs no runtime allocations. 39 | // 40 | // 41 | // VERSION HISTORY 42 | // 43 | // !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. 44 | // 1.14 (2021-07-11) page up/down, various fixes 45 | // 1.13 (2019-02-07) fix bug in undo size management 46 | // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 47 | // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 48 | // 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual 49 | // 1.9 (2016-08-27) customizable move-by-word 50 | // 1.8 (2016-04-02) better keyboard handling when mouse button is down 51 | // 1.7 (2015-09-13) change y range handling in case baseline is non-0 52 | // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 53 | // 1.5 (2014-09-10) add support for secondary keys for OS X 54 | // 1.4 (2014-08-17) fix signed/unsigned warnings 55 | // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 56 | // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 57 | // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 58 | // 1.0 (2012-07-26) improve documentation, initial public release 59 | // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 60 | // 0.2 (2011-11-28) fixes to undo/redo 61 | // 0.1 (2010-07-08) initial version 62 | // 63 | // ADDITIONAL CONTRIBUTORS 64 | // 65 | // Ulf Winklemann: move-by-word in 1.1 66 | // Fabian Giesen: secondary key inputs in 1.5 67 | // Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 68 | // Louis Schnellbach: page up/down in 1.14 69 | // 70 | // Bugfixes: 71 | // Scott Graham 72 | // Daniel Keller 73 | // Omar Cornut 74 | // Dan Thompson 75 | // 76 | // USAGE 77 | // 78 | // This file behaves differently depending on what symbols you define 79 | // before including it. 80 | // 81 | // 82 | // Header-file mode: 83 | // 84 | // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 85 | // it will operate in "header file" mode. In this mode, it declares a 86 | // single public symbol, STB_TexteditState, which encapsulates the current 87 | // state of a text widget (except for the string, which you will store 88 | // separately). 89 | // 90 | // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 91 | // primitive type that defines a single character (e.g. char, wchar_t, etc). 92 | // 93 | // To save space or increase undo-ability, you can optionally define the 94 | // following things that are used by the undo system: 95 | // 96 | // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 97 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 98 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 99 | // 100 | // If you don't define these, they are set to permissive types and 101 | // moderate sizes. The undo system does no memory allocations, so 102 | // it grows STB_TexteditState by the worst-case storage which is (in bytes): 103 | // 104 | // [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT 105 | // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHARCOUNT 106 | // 107 | // 108 | // Implementation mode: 109 | // 110 | // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 111 | // will compile the implementation of the text edit widget, depending 112 | // on a large number of symbols which must be defined before the include. 113 | // 114 | // The implementation is defined only as static functions. You will then 115 | // need to provide your own APIs in the same file which will access the 116 | // static functions. 117 | // 118 | // The basic concept is that you provide a "string" object which 119 | // behaves like an array of characters. stb_textedit uses indices to 120 | // refer to positions in the string, implicitly representing positions 121 | // in the displayed textedit. This is true for both plain text and 122 | // rich text; even with rich text stb_truetype interacts with your 123 | // code as if there was an array of all the displayed characters. 124 | // 125 | // Symbols that must be the same in header-file and implementation mode: 126 | // 127 | // STB_TEXTEDIT_CHARTYPE the character type 128 | // STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 129 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 130 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 131 | // 132 | // Symbols you must define for implementation mode: 133 | // 134 | // STB_TEXTEDIT_STRING the type of object representing a string being edited, 135 | // typically this is a wrapper object with other data you need 136 | // 137 | // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 138 | // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 139 | // starting from character #n (see discussion below) 140 | // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 141 | // to the xpos of the i+1'th char for a line of characters 142 | // starting at character #n (i.e. accounts for kerning 143 | // with previous char) 144 | // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 145 | // (return type is int, -1 means not valid to insert) 146 | // (not supported if you want to use UTF-8, see below) 147 | // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 148 | // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 149 | // as manually wordwrapping for end-of-line positioning 150 | // 151 | // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 152 | // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 153 | // returns number of characters actually inserted. [DEAR IMGUI] 154 | // 155 | // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 156 | // 157 | // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 158 | // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 159 | // STB_TEXTEDIT_K_UP keyboard input to move cursor up 160 | // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 161 | // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 162 | // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 163 | // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 164 | // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 165 | // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 166 | // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 167 | // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 168 | // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 169 | // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 170 | // STB_TEXTEDIT_K_REDO keyboard input to perform redo 171 | // 172 | // Optional: 173 | // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 174 | // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 175 | // required for default WORDLEFT/WORDRIGHT handlers 176 | // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 177 | // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 178 | // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 179 | // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 180 | // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 181 | // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 182 | // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 183 | // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 184 | // 185 | // To support UTF-8: 186 | // 187 | // STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character 188 | // STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character 189 | // Do NOT define STB_TEXTEDIT_KEYTOTEXT. 190 | // Instead, call stb_textedit_text() directly for text contents. 191 | // 192 | // Keyboard input must be encoded as a single integer value; e.g. a character code 193 | // and some bitflags that represent shift states. to simplify the interface, SHIFT must 194 | // be a bitflag, so we can test the shifted state of cursor movements to allow selection, 195 | // i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 196 | // 197 | // You can encode other things, such as CONTROL or ALT, in additional bits, and 198 | // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 199 | // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 200 | // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 201 | // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 202 | // API below. The control keys will only match WM_KEYDOWN events because of the 203 | // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 204 | // bit so it only decodes WM_CHAR events. 205 | // 206 | // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 207 | // row of characters assuming they start on the i'th character--the width and 208 | // the height and the number of characters consumed. This allows this library 209 | // to traverse the entire layout incrementally. You need to compute word-wrapping 210 | // here. 211 | // 212 | // Each textfield keeps its own insert mode state, which is not how normal 213 | // applications work. To keep an app-wide insert mode, update/copy the 214 | // "insert_mode" field of STB_TexteditState before/after calling API functions. 215 | // 216 | // API 217 | // 218 | // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 219 | // 220 | // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 221 | // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 222 | // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 223 | // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 224 | // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) 225 | // void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len) 226 | // 227 | // Each of these functions potentially updates the string and updates the 228 | // state. 229 | // 230 | // initialize_state: 231 | // set the textedit state to a known good default state when initially 232 | // constructing the textedit. 233 | // 234 | // click: 235 | // call this with the mouse x,y on a mouse down; it will update the cursor 236 | // and reset the selection start/end to the cursor point. the x,y must 237 | // be relative to the text widget, with (0,0) being the top left. 238 | // 239 | // drag: 240 | // call this with the mouse x,y on a mouse drag/up; it will update the 241 | // cursor and the selection end point 242 | // 243 | // cut: 244 | // call this to delete the current selection; returns true if there was 245 | // one. you should FIRST copy the current selection to the system paste buffer. 246 | // (To copy, just copy the current selection out of the string yourself.) 247 | // 248 | // paste: 249 | // call this to paste text at the current cursor point or over the current 250 | // selection if there is one. 251 | // 252 | // key: 253 | // call this for keyboard inputs sent to the textfield. you can use it 254 | // for "key down" events or for "translated" key events. if you need to 255 | // do both (as in Win32), or distinguish Unicode characters from control 256 | // inputs, set a high bit to distinguish the two; then you can define the 257 | // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 258 | // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 259 | // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to 260 | // anything other type you want before including. 261 | // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are 262 | // transformed into text and stb_textedit_text() is automatically called. 263 | // 264 | // text: (added 2025) 265 | // call this to directly send text input the textfield, which is required 266 | // for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT() 267 | // cannot infer text length. 268 | // 269 | // 270 | // When rendering, you can read the cursor position and selection state from 271 | // the STB_TexteditState. 272 | // 273 | // 274 | // Notes: 275 | // 276 | // This is designed to be usable in IMGUI, so it allows for the possibility of 277 | // running in an IMGUI that has NOT cached the multi-line layout. For this 278 | // reason, it provides an interface that is compatible with computing the 279 | // layout incrementally--we try to make sure we make as few passes through 280 | // as possible. (For example, to locate the mouse pointer in the text, we 281 | // could define functions that return the X and Y positions of characters 282 | // and binary search Y and then X, but if we're doing dynamic layout this 283 | // will run the layout algorithm many times, so instead we manually search 284 | // forward in one pass. Similar logic applies to e.g. up-arrow and 285 | // down-arrow movement.) 286 | // 287 | // If it's run in a widget that *has* cached the layout, then this is less 288 | // efficient, but it's not horrible on modern computers. But you wouldn't 289 | // want to edit million-line files with it. 290 | 291 | 292 | //////////////////////////////////////////////////////////////////////////// 293 | //////////////////////////////////////////////////////////////////////////// 294 | //// 295 | //// Header-file mode 296 | //// 297 | //// 298 | 299 | #ifndef INCLUDE_IMSTB_TEXTEDIT_H 300 | #define INCLUDE_IMSTB_TEXTEDIT_H 301 | 302 | //////////////////////////////////////////////////////////////////////// 303 | // 304 | // STB_TexteditState 305 | // 306 | // Definition of STB_TexteditState which you should store 307 | // per-textfield; it includes cursor position, selection state, 308 | // and undo state. 309 | // 310 | 311 | #ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT 312 | #define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 313 | #endif 314 | #ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT 315 | #define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 316 | #endif 317 | #ifndef IMSTB_TEXTEDIT_CHARTYPE 318 | #define IMSTB_TEXTEDIT_CHARTYPE int 319 | #endif 320 | #ifndef IMSTB_TEXTEDIT_POSITIONTYPE 321 | #define IMSTB_TEXTEDIT_POSITIONTYPE int 322 | #endif 323 | 324 | typedef struct 325 | { 326 | // private data 327 | IMSTB_TEXTEDIT_POSITIONTYPE where; 328 | IMSTB_TEXTEDIT_POSITIONTYPE insert_length; 329 | IMSTB_TEXTEDIT_POSITIONTYPE delete_length; 330 | int char_storage; 331 | } StbUndoRecord; 332 | 333 | typedef struct 334 | { 335 | // private data 336 | StbUndoRecord undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT]; 337 | IMSTB_TEXTEDIT_CHARTYPE undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT]; 338 | short undo_point, redo_point; 339 | int undo_char_point, redo_char_point; 340 | } StbUndoState; 341 | 342 | typedef struct STB_TexteditState 343 | { 344 | ///////////////////// 345 | // 346 | // public data 347 | // 348 | 349 | int cursor; 350 | // position of the text cursor within the string 351 | 352 | int select_start; // selection start point 353 | int select_end; 354 | // selection start and end point in characters; if equal, no selection. 355 | // note that start may be less than or greater than end (e.g. when 356 | // dragging the mouse, start is where the initial click was, and you 357 | // can drag in either direction) 358 | 359 | unsigned char insert_mode; 360 | // each textfield keeps its own insert mode state. to keep an app-wide 361 | // insert mode, copy this value in/out of the app state 362 | 363 | int row_count_per_page; 364 | // page size in number of row. 365 | // this value MUST be set to >0 for pageup or pagedown in multilines documents. 366 | 367 | ///////////////////// 368 | // 369 | // private data 370 | // 371 | unsigned char cursor_at_end_of_line; // not implemented yet 372 | unsigned char initialized; 373 | unsigned char has_preferred_x; 374 | unsigned char single_line; 375 | unsigned char padding1, padding2, padding3; 376 | float preferred_x; // this determines where the cursor up/down tries to seek to along x 377 | StbUndoState undostate; 378 | } STB_TexteditState; 379 | 380 | 381 | //////////////////////////////////////////////////////////////////////// 382 | // 383 | // StbTexteditRow 384 | // 385 | // Result of layout query, used by stb_textedit to determine where 386 | // the text in each row is. 387 | 388 | // result of layout query 389 | typedef struct 390 | { 391 | float x0,x1; // starting x location, end x location (allows for align=right, etc) 392 | float baseline_y_delta; // position of baseline relative to previous row's baseline 393 | float ymin,ymax; // height of row above and below baseline 394 | int num_chars; 395 | } StbTexteditRow; 396 | #endif //INCLUDE_IMSTB_TEXTEDIT_H 397 | 398 | 399 | //////////////////////////////////////////////////////////////////////////// 400 | //////////////////////////////////////////////////////////////////////////// 401 | //// 402 | //// Implementation mode 403 | //// 404 | //// 405 | 406 | 407 | // implementation isn't include-guarded, since it might have indirectly 408 | // included just the "header" portion 409 | #ifdef IMSTB_TEXTEDIT_IMPLEMENTATION 410 | 411 | #ifndef IMSTB_TEXTEDIT_memmove 412 | #include 413 | #define IMSTB_TEXTEDIT_memmove memmove 414 | #endif 415 | 416 | // [DEAR IMGUI] 417 | // Functions must be implemented for UTF8 support 418 | // Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. 419 | // There is not necessarily a '[DEAR IMGUI]' at the usage sites. 420 | #ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX 421 | #define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1) 422 | #endif 423 | #ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX 424 | #define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1) 425 | #endif 426 | 427 | ///////////////////////////////////////////////////////////////////////////// 428 | // 429 | // Mouse input handling 430 | // 431 | 432 | // traverse the layout to locate the nearest character to a display position 433 | static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line) 434 | { 435 | StbTexteditRow r; 436 | int n = STB_TEXTEDIT_STRINGLEN(str); 437 | float base_y = 0, prev_x; 438 | int i=0, k; 439 | 440 | r.x0 = r.x1 = 0; 441 | r.ymin = r.ymax = 0; 442 | r.num_chars = 0; 443 | *out_side_on_line = 0; 444 | 445 | // search rows to find one that straddles 'y' 446 | while (i < n) { 447 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 448 | if (r.num_chars <= 0) 449 | return n; 450 | 451 | if (i==0 && y < base_y + r.ymin) 452 | return 0; 453 | 454 | if (y < base_y + r.ymax) 455 | break; 456 | 457 | i += r.num_chars; 458 | base_y += r.baseline_y_delta; 459 | } 460 | 461 | // below all text, return 'after' last character 462 | if (i >= n) 463 | { 464 | *out_side_on_line = 1; 465 | return n; 466 | } 467 | 468 | // check if it's before the beginning of the line 469 | if (x < r.x0) 470 | return i; 471 | 472 | // check if it's before the end of the line 473 | if (x < r.x1) { 474 | // search characters in row for one that straddles 'x' 475 | prev_x = r.x0; 476 | for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) { 477 | float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 478 | if (x < prev_x+w) { 479 | *out_side_on_line = (k == 0) ? 0 : 1; 480 | if (x < prev_x+w/2) 481 | return k+i; 482 | else 483 | return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k); 484 | } 485 | prev_x += w; 486 | } 487 | // shouldn't happen, but if it does, fall through to end-of-line case 488 | } 489 | 490 | // if the last character is a newline, return that. otherwise return 'after' the last character 491 | *out_side_on_line = 1; 492 | if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 493 | return i+r.num_chars-1; 494 | else 495 | return i+r.num_chars; 496 | } 497 | 498 | // API click: on mouse down, move the cursor to the clicked location, and reset the selection 499 | static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 500 | { 501 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 502 | // goes off the top or bottom of the text 503 | int side_on_line; 504 | if( state->single_line ) 505 | { 506 | StbTexteditRow r; 507 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 508 | y = r.ymin; 509 | } 510 | 511 | state->cursor = stb_text_locate_coord(str, x, y, &side_on_line); 512 | state->select_start = state->cursor; 513 | state->select_end = state->cursor; 514 | state->has_preferred_x = 0; 515 | str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); 516 | } 517 | 518 | // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 519 | static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 520 | { 521 | int p = 0; 522 | int side_on_line; 523 | 524 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 525 | // goes off the top or bottom of the text 526 | if( state->single_line ) 527 | { 528 | StbTexteditRow r; 529 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 530 | y = r.ymin; 531 | } 532 | 533 | if (state->select_start == state->select_end) 534 | state->select_start = state->cursor; 535 | 536 | p = stb_text_locate_coord(str, x, y, &side_on_line); 537 | state->cursor = state->select_end = p; 538 | str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); 539 | } 540 | 541 | ///////////////////////////////////////////////////////////////////////////// 542 | // 543 | // Keyboard input handling 544 | // 545 | 546 | // forward declarations 547 | static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); 548 | static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); 549 | static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 550 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 551 | static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 552 | 553 | typedef struct 554 | { 555 | float x,y; // position of n'th character 556 | float height; // height of line 557 | int first_char, length; // first char of row, and length 558 | int prev_first; // first char of previous row 559 | } StbFindState; 560 | 561 | // find the x/y location of a character, and remember info about the previous row in 562 | // case we get a move-up event (for page up, we'll have to rescan) 563 | static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line) 564 | { 565 | StbTexteditRow r; 566 | int prev_start = 0; 567 | int z = STB_TEXTEDIT_STRINGLEN(str); 568 | int i=0, first; 569 | 570 | if (n == z && single_line) { 571 | // special case if it's at the end (may not be needed?) 572 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 573 | find->y = 0; 574 | find->first_char = 0; 575 | find->length = z; 576 | find->height = r.ymax - r.ymin; 577 | find->x = r.x1; 578 | return; 579 | } 580 | 581 | // search rows to find the one that straddles character n 582 | find->y = 0; 583 | 584 | for(;;) { 585 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 586 | if (n < i + r.num_chars) 587 | break; 588 | 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 589 | break; 590 | if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line 591 | break; // [DEAR IMGUI] 592 | prev_start = i; 593 | i += r.num_chars; 594 | find->y += r.baseline_y_delta; 595 | if (i == z) // [DEAR IMGUI] 596 | { 597 | r.num_chars = 0; // [DEAR IMGUI] 598 | break; // [DEAR IMGUI] 599 | } 600 | } 601 | 602 | find->first_char = first = i; 603 | find->length = r.num_chars; 604 | find->height = r.ymax - r.ymin; 605 | find->prev_first = prev_start; 606 | 607 | // now scan to find xpos 608 | find->x = r.x0; 609 | for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first) 610 | find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 611 | } 612 | 613 | #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 614 | 615 | // make the selection/cursor state valid if client altered the string 616 | static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 617 | { 618 | int n = STB_TEXTEDIT_STRINGLEN(str); 619 | if (STB_TEXT_HAS_SELECTION(state)) { 620 | if (state->select_start > n) state->select_start = n; 621 | if (state->select_end > n) state->select_end = n; 622 | // if clamping forced them to be equal, move the cursor to match 623 | if (state->select_start == state->select_end) 624 | state->cursor = state->select_start; 625 | } 626 | if (state->cursor > n) state->cursor = n; 627 | } 628 | 629 | // delete characters while updating undo 630 | static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 631 | { 632 | stb_text_makeundo_delete(str, state, where, len); 633 | STB_TEXTEDIT_DELETECHARS(str, where, len); 634 | state->has_preferred_x = 0; 635 | } 636 | 637 | // delete the section 638 | static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 639 | { 640 | stb_textedit_clamp(str, state); 641 | if (STB_TEXT_HAS_SELECTION(state)) { 642 | if (state->select_start < state->select_end) { 643 | stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 644 | state->select_end = state->cursor = state->select_start; 645 | } else { 646 | stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 647 | state->select_start = state->cursor = state->select_end; 648 | } 649 | state->has_preferred_x = 0; 650 | } 651 | } 652 | 653 | // canoncialize the selection so start <= end 654 | static void stb_textedit_sortselection(STB_TexteditState *state) 655 | { 656 | if (state->select_end < state->select_start) { 657 | int temp = state->select_end; 658 | state->select_end = state->select_start; 659 | state->select_start = temp; 660 | } 661 | } 662 | 663 | // move cursor to first character of selection 664 | static void stb_textedit_move_to_first(STB_TexteditState *state) 665 | { 666 | if (STB_TEXT_HAS_SELECTION(state)) { 667 | stb_textedit_sortselection(state); 668 | state->cursor = state->select_start; 669 | state->select_end = state->select_start; 670 | state->has_preferred_x = 0; 671 | } 672 | } 673 | 674 | // move cursor to last character of selection 675 | static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 676 | { 677 | if (STB_TEXT_HAS_SELECTION(state)) { 678 | stb_textedit_sortselection(state); 679 | stb_textedit_clamp(str, state); 680 | state->cursor = state->select_end; 681 | state->select_start = state->select_end; 682 | state->has_preferred_x = 0; 683 | } 684 | } 685 | 686 | // [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping. 687 | #ifndef STB_TEXTEDIT_MOVELINESTART 688 | static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) 689 | { 690 | if (state->single_line) 691 | return 0; 692 | while (cursor > 0) { 693 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor); 694 | if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) 695 | break; 696 | cursor = prev; 697 | } 698 | return cursor; 699 | } 700 | #define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start 701 | #endif 702 | #ifndef STB_TEXTEDIT_MOVELINEEND 703 | static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) 704 | { 705 | int n = STB_TEXTEDIT_STRINGLEN(str); 706 | if (state->single_line) 707 | return n; 708 | while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE) 709 | cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor); 710 | return cursor; 711 | } 712 | #define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end 713 | #endif 714 | 715 | #ifdef STB_TEXTEDIT_IS_SPACE 716 | static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) 717 | { 718 | return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 719 | } 720 | 721 | #ifndef STB_TEXTEDIT_MOVEWORDLEFT 722 | static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) 723 | { 724 | c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character 725 | while (c >= 0 && !is_word_boundary(str, c)) 726 | c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c); 727 | 728 | if( c < 0 ) 729 | c = 0; 730 | 731 | return c; 732 | } 733 | #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 734 | #endif 735 | 736 | #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 737 | static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) 738 | { 739 | const int len = STB_TEXTEDIT_STRINGLEN(str); 740 | c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character 741 | while( c < len && !is_word_boundary( str, c ) ) 742 | c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); 743 | 744 | if( c > len ) 745 | c = len; 746 | 747 | return c; 748 | } 749 | #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 750 | #endif 751 | 752 | #endif 753 | 754 | // update selection and cursor to match each other 755 | static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 756 | { 757 | if (!STB_TEXT_HAS_SELECTION(state)) 758 | state->select_start = state->select_end = state->cursor; 759 | else 760 | state->cursor = state->select_end; 761 | } 762 | 763 | // API cut: delete selection 764 | static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 765 | { 766 | if (STB_TEXT_HAS_SELECTION(state)) { 767 | stb_textedit_delete_selection(str,state); // implicitly clamps 768 | state->has_preferred_x = 0; 769 | return 1; 770 | } 771 | return 0; 772 | } 773 | 774 | // API paste: replace existing selection with passed-in text 775 | static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len) 776 | { 777 | // if there's a selection, the paste should delete it 778 | stb_textedit_clamp(str, state); 779 | stb_textedit_delete_selection(str,state); 780 | // try to insert the characters 781 | len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len); 782 | if (len) { 783 | stb_text_makeundo_insert(state, state->cursor, len); 784 | state->cursor += len; 785 | state->has_preferred_x = 0; 786 | return 1; 787 | } 788 | // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) 789 | return 0; 790 | } 791 | 792 | #ifndef STB_TEXTEDIT_KEYTYPE 793 | #define STB_TEXTEDIT_KEYTYPE int 794 | #endif 795 | 796 | // API key: process text input 797 | // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. 798 | static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) 799 | { 800 | // can't add newline in single-line mode 801 | if (text[0] == '\n' && state->single_line) 802 | return; 803 | 804 | if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 805 | stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 806 | STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 807 | text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); 808 | if (text_len) { 809 | state->cursor += text_len; 810 | state->has_preferred_x = 0; 811 | } 812 | } else { 813 | stb_textedit_delete_selection(str, state); // implicitly clamps 814 | text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); 815 | if (text_len) { 816 | stb_text_makeundo_insert(state, state->cursor, text_len); 817 | state->cursor += text_len; 818 | state->has_preferred_x = 0; 819 | } 820 | } 821 | } 822 | 823 | // API key: process a keyboard input 824 | static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 825 | { 826 | retry: 827 | switch (key) { 828 | default: { 829 | #ifdef STB_TEXTEDIT_KEYTOTEXT 830 | // This is not suitable for UTF-8 support. 831 | int c = STB_TEXTEDIT_KEYTOTEXT(key); 832 | if (c > 0) { 833 | IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; 834 | stb_textedit_text(str, state, &ch, 1); 835 | } 836 | #endif 837 | break; 838 | } 839 | 840 | #ifdef STB_TEXTEDIT_K_INSERT 841 | case STB_TEXTEDIT_K_INSERT: 842 | state->insert_mode = !state->insert_mode; 843 | break; 844 | #endif 845 | 846 | case STB_TEXTEDIT_K_UNDO: 847 | stb_text_undo(str, state); 848 | state->has_preferred_x = 0; 849 | break; 850 | 851 | case STB_TEXTEDIT_K_REDO: 852 | stb_text_redo(str, state); 853 | state->has_preferred_x = 0; 854 | break; 855 | 856 | case STB_TEXTEDIT_K_LEFT: 857 | // if currently there's a selection, move cursor to start of selection 858 | if (STB_TEXT_HAS_SELECTION(state)) 859 | stb_textedit_move_to_first(state); 860 | else 861 | if (state->cursor > 0) 862 | state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); 863 | state->has_preferred_x = 0; 864 | break; 865 | 866 | case STB_TEXTEDIT_K_RIGHT: 867 | // if currently there's a selection, move cursor to end of selection 868 | if (STB_TEXT_HAS_SELECTION(state)) 869 | stb_textedit_move_to_last(str, state); 870 | else 871 | state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 872 | stb_textedit_clamp(str, state); 873 | state->has_preferred_x = 0; 874 | break; 875 | 876 | case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 877 | stb_textedit_clamp(str, state); 878 | stb_textedit_prep_selection_at_cursor(state); 879 | // move selection left 880 | if (state->select_end > 0) 881 | state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end); 882 | state->cursor = state->select_end; 883 | state->has_preferred_x = 0; 884 | break; 885 | 886 | #ifdef STB_TEXTEDIT_MOVEWORDLEFT 887 | case STB_TEXTEDIT_K_WORDLEFT: 888 | if (STB_TEXT_HAS_SELECTION(state)) 889 | stb_textedit_move_to_first(state); 890 | else { 891 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 892 | stb_textedit_clamp( str, state ); 893 | } 894 | break; 895 | 896 | case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 897 | if( !STB_TEXT_HAS_SELECTION( state ) ) 898 | stb_textedit_prep_selection_at_cursor(state); 899 | 900 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 901 | state->select_end = state->cursor; 902 | 903 | stb_textedit_clamp( str, state ); 904 | break; 905 | #endif 906 | 907 | #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 908 | case STB_TEXTEDIT_K_WORDRIGHT: 909 | if (STB_TEXT_HAS_SELECTION(state)) 910 | stb_textedit_move_to_last(str, state); 911 | else { 912 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 913 | stb_textedit_clamp( str, state ); 914 | } 915 | break; 916 | 917 | case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 918 | if( !STB_TEXT_HAS_SELECTION( state ) ) 919 | stb_textedit_prep_selection_at_cursor(state); 920 | 921 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 922 | state->select_end = state->cursor; 923 | 924 | stb_textedit_clamp( str, state ); 925 | break; 926 | #endif 927 | 928 | case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 929 | stb_textedit_prep_selection_at_cursor(state); 930 | // move selection right 931 | state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end); 932 | stb_textedit_clamp(str, state); 933 | state->cursor = state->select_end; 934 | state->has_preferred_x = 0; 935 | break; 936 | 937 | case STB_TEXTEDIT_K_DOWN: 938 | case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: 939 | case STB_TEXTEDIT_K_PGDOWN: 940 | case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { 941 | StbFindState find; 942 | StbTexteditRow row; 943 | int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 944 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; 945 | int row_count = is_page ? state->row_count_per_page : 1; 946 | 947 | if (!is_page && state->single_line) { 948 | // on windows, up&down in single-line behave like left&right 949 | key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 950 | goto retry; 951 | } 952 | 953 | if (sel) 954 | stb_textedit_prep_selection_at_cursor(state); 955 | else if (STB_TEXT_HAS_SELECTION(state)) 956 | stb_textedit_move_to_last(str, state); 957 | 958 | // compute current position of cursor point 959 | stb_textedit_clamp(str, state); 960 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 961 | 962 | for (j = 0; j < row_count; ++j) { 963 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 964 | int start = find.first_char + find.length; 965 | 966 | if (find.length == 0) 967 | break; 968 | 969 | // [DEAR IMGUI] 970 | // going down while being on the last line shouldn't bring us to that line end 971 | //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) 972 | // break; 973 | 974 | // now find character position down a row 975 | state->cursor = start; 976 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 977 | x = row.x0; 978 | for (i=0; i < row.num_chars; ) { 979 | float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 980 | int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 981 | #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE 982 | if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) 983 | break; 984 | #endif 985 | x += dx; 986 | if (x > goal_x) 987 | break; 988 | i += next - state->cursor; 989 | state->cursor = next; 990 | } 991 | stb_textedit_clamp(str, state); 992 | 993 | if (state->cursor == find.first_char + find.length) 994 | str->LastMoveDirectionLR = ImGuiDir_Left; 995 | state->has_preferred_x = 1; 996 | state->preferred_x = goal_x; 997 | 998 | if (sel) 999 | state->select_end = state->cursor; 1000 | 1001 | // go to next line 1002 | find.first_char = find.first_char + find.length; 1003 | find.length = row.num_chars; 1004 | } 1005 | break; 1006 | } 1007 | 1008 | case STB_TEXTEDIT_K_UP: 1009 | case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: 1010 | case STB_TEXTEDIT_K_PGUP: 1011 | case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { 1012 | StbFindState find; 1013 | StbTexteditRow row; 1014 | int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 1015 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; 1016 | int row_count = is_page ? state->row_count_per_page : 1; 1017 | 1018 | if (!is_page && state->single_line) { 1019 | // on windows, up&down become left&right 1020 | key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 1021 | goto retry; 1022 | } 1023 | 1024 | if (sel) 1025 | stb_textedit_prep_selection_at_cursor(state); 1026 | else if (STB_TEXT_HAS_SELECTION(state)) 1027 | stb_textedit_move_to_first(state); 1028 | 1029 | // compute current position of cursor point 1030 | stb_textedit_clamp(str, state); 1031 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 1032 | 1033 | for (j = 0; j < row_count; ++j) { 1034 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 1035 | 1036 | // can only go up if there's a previous row 1037 | if (find.prev_first == find.first_char) 1038 | break; 1039 | 1040 | // now find character position up a row 1041 | state->cursor = find.prev_first; 1042 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 1043 | x = row.x0; 1044 | for (i=0; i < row.num_chars; ) { 1045 | float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 1046 | int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 1047 | #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE 1048 | if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) 1049 | break; 1050 | #endif 1051 | x += dx; 1052 | if (x > goal_x) 1053 | break; 1054 | i += next - state->cursor; 1055 | state->cursor = next; 1056 | } 1057 | stb_textedit_clamp(str, state); 1058 | 1059 | if (state->cursor == find.first_char) 1060 | str->LastMoveDirectionLR = ImGuiDir_Right; 1061 | else if (state->cursor == find.prev_first) 1062 | str->LastMoveDirectionLR = ImGuiDir_Left; 1063 | state->has_preferred_x = 1; 1064 | state->preferred_x = goal_x; 1065 | 1066 | if (sel) 1067 | state->select_end = state->cursor; 1068 | 1069 | // go to previous line 1070 | // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) 1071 | prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; 1072 | while (prev_scan > 0) 1073 | { 1074 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan); 1075 | if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) 1076 | break; 1077 | prev_scan = prev; 1078 | } 1079 | find.first_char = find.prev_first; 1080 | find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan); 1081 | } 1082 | break; 1083 | } 1084 | 1085 | case STB_TEXTEDIT_K_DELETE: 1086 | case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 1087 | if (STB_TEXT_HAS_SELECTION(state)) 1088 | stb_textedit_delete_selection(str, state); 1089 | else { 1090 | int n = STB_TEXTEDIT_STRINGLEN(str); 1091 | if (state->cursor < n) 1092 | stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor); 1093 | } 1094 | state->has_preferred_x = 0; 1095 | break; 1096 | 1097 | case STB_TEXTEDIT_K_BACKSPACE: 1098 | case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 1099 | if (STB_TEXT_HAS_SELECTION(state)) 1100 | stb_textedit_delete_selection(str, state); 1101 | else { 1102 | stb_textedit_clamp(str, state); 1103 | if (state->cursor > 0) { 1104 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); 1105 | stb_textedit_delete(str, state, prev, state->cursor - prev); 1106 | state->cursor = prev; 1107 | } 1108 | } 1109 | state->has_preferred_x = 0; 1110 | break; 1111 | 1112 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1113 | case STB_TEXTEDIT_K_TEXTSTART2: 1114 | #endif 1115 | case STB_TEXTEDIT_K_TEXTSTART: 1116 | state->cursor = state->select_start = state->select_end = 0; 1117 | state->has_preferred_x = 0; 1118 | break; 1119 | 1120 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1121 | case STB_TEXTEDIT_K_TEXTEND2: 1122 | #endif 1123 | case STB_TEXTEDIT_K_TEXTEND: 1124 | state->cursor = STB_TEXTEDIT_STRINGLEN(str); 1125 | state->select_start = state->select_end = 0; 1126 | state->has_preferred_x = 0; 1127 | break; 1128 | 1129 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1130 | case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 1131 | #endif 1132 | case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 1133 | stb_textedit_prep_selection_at_cursor(state); 1134 | state->cursor = state->select_end = 0; 1135 | state->has_preferred_x = 0; 1136 | break; 1137 | 1138 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1139 | case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 1140 | #endif 1141 | case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 1142 | stb_textedit_prep_selection_at_cursor(state); 1143 | state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 1144 | state->has_preferred_x = 0; 1145 | break; 1146 | 1147 | 1148 | #ifdef STB_TEXTEDIT_K_LINESTART2 1149 | case STB_TEXTEDIT_K_LINESTART2: 1150 | #endif 1151 | case STB_TEXTEDIT_K_LINESTART: 1152 | stb_textedit_clamp(str, state); 1153 | stb_textedit_move_to_first(state); 1154 | state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); 1155 | state->has_preferred_x = 0; 1156 | break; 1157 | 1158 | #ifdef STB_TEXTEDIT_K_LINEEND2 1159 | case STB_TEXTEDIT_K_LINEEND2: 1160 | #endif 1161 | case STB_TEXTEDIT_K_LINEEND: { 1162 | stb_textedit_clamp(str, state); 1163 | stb_textedit_move_to_last(str, state); 1164 | state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); 1165 | state->has_preferred_x = 0; 1166 | break; 1167 | } 1168 | 1169 | #ifdef STB_TEXTEDIT_K_LINESTART2 1170 | case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1171 | #endif 1172 | case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1173 | stb_textedit_clamp(str, state); 1174 | stb_textedit_prep_selection_at_cursor(state); 1175 | state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); 1176 | state->select_end = state->cursor; 1177 | state->has_preferred_x = 0; 1178 | break; 1179 | 1180 | #ifdef STB_TEXTEDIT_K_LINEEND2 1181 | case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1182 | #endif 1183 | case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1184 | stb_textedit_clamp(str, state); 1185 | stb_textedit_prep_selection_at_cursor(state); 1186 | state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); 1187 | state->select_end = state->cursor; 1188 | state->has_preferred_x = 0; 1189 | break; 1190 | } 1191 | } 1192 | } 1193 | 1194 | ///////////////////////////////////////////////////////////////////////////// 1195 | // 1196 | // Undo processing 1197 | // 1198 | // @OPTIMIZE: the undo/redo buffer should be circular 1199 | 1200 | static void stb_textedit_flush_redo(StbUndoState *state) 1201 | { 1202 | state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT; 1203 | state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT; 1204 | } 1205 | 1206 | // discard the oldest entry in the undo list 1207 | static void stb_textedit_discard_undo(StbUndoState *state) 1208 | { 1209 | if (state->undo_point > 0) { 1210 | // if the 0th undo state has characters, clean those up 1211 | if (state->undo_rec[0].char_storage >= 0) { 1212 | int n = state->undo_rec[0].insert_length, i; 1213 | // delete n characters from all other records 1214 | state->undo_char_point -= n; 1215 | IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE))); 1216 | for (i=0; i < state->undo_point; ++i) 1217 | if (state->undo_rec[i].char_storage >= 0) 1218 | state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 1219 | } 1220 | --state->undo_point; 1221 | IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 1222 | } 1223 | } 1224 | 1225 | // discard the oldest entry in the redo list--it's bad if this 1226 | // ever happens, but because undo & redo have to store the actual 1227 | // characters in different cases, the redo character buffer can 1228 | // fill up even though the undo buffer didn't 1229 | static void stb_textedit_discard_redo(StbUndoState *state) 1230 | { 1231 | int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1; 1232 | 1233 | if (state->redo_point <= k) { 1234 | // if the k'th undo state has characters, clean those up 1235 | if (state->undo_rec[k].char_storage >= 0) { 1236 | int n = state->undo_rec[k].insert_length, i; 1237 | // move the remaining redo character data to the end of the buffer 1238 | state->redo_char_point += n; 1239 | IMSTB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((IMSTB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(IMSTB_TEXTEDIT_CHARTYPE))); 1240 | // adjust the position of all the other records to account for above memmove 1241 | for (i=state->redo_point; i < k; ++i) 1242 | if (state->undo_rec[i].char_storage >= 0) 1243 | state->undo_rec[i].char_storage += n; 1244 | } 1245 | // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 1246 | // [DEAR IMGUI] 1247 | size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 1248 | const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 1249 | const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 1250 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 1251 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 1252 | IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 1253 | 1254 | // now move redo_point to point to the new one 1255 | ++state->redo_point; 1256 | } 1257 | } 1258 | 1259 | static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1260 | { 1261 | // any time we create a new undo record, we discard redo 1262 | stb_textedit_flush_redo(state); 1263 | 1264 | // if we have no free records, we have to make room, by sliding the 1265 | // existing records down 1266 | if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1267 | stb_textedit_discard_undo(state); 1268 | 1269 | // if the characters to store won't possibly fit in the buffer, we can't undo 1270 | if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) { 1271 | state->undo_point = 0; 1272 | state->undo_char_point = 0; 1273 | return NULL; 1274 | } 1275 | 1276 | // if we don't have enough free characters in the buffer, we have to make room 1277 | while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) 1278 | stb_textedit_discard_undo(state); 1279 | 1280 | return &state->undo_rec[state->undo_point++]; 1281 | } 1282 | 1283 | static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1284 | { 1285 | StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1286 | if (r == NULL) 1287 | return NULL; 1288 | 1289 | r->where = pos; 1290 | r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len; 1291 | r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len; 1292 | 1293 | if (insert_len == 0) { 1294 | r->char_storage = -1; 1295 | return NULL; 1296 | } else { 1297 | r->char_storage = state->undo_char_point; 1298 | state->undo_char_point += insert_len; 1299 | return &state->undo_char[r->char_storage]; 1300 | } 1301 | } 1302 | 1303 | static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1304 | { 1305 | StbUndoState *s = &state->undostate; 1306 | StbUndoRecord u, *r; 1307 | if (s->undo_point == 0) 1308 | return; 1309 | 1310 | // we need to do two things: apply the undo record, and create a redo record 1311 | u = s->undo_rec[s->undo_point-1]; 1312 | r = &s->undo_rec[s->redo_point-1]; 1313 | r->char_storage = -1; 1314 | 1315 | r->insert_length = u.delete_length; 1316 | r->delete_length = u.insert_length; 1317 | r->where = u.where; 1318 | 1319 | if (u.delete_length) { 1320 | // if the undo record says to delete characters, then the redo record will 1321 | // need to re-insert the characters that get deleted, so we need to store 1322 | // them. 1323 | 1324 | // there are three cases: 1325 | // there's enough room to store the characters 1326 | // characters stored for *redoing* don't leave room for redo 1327 | // characters stored for *undoing* don't leave room for redo 1328 | // if the last is true, we have to bail 1329 | 1330 | if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) { 1331 | // the undo records take up too much character space; there's no space to store the redo characters 1332 | r->insert_length = 0; 1333 | } else { 1334 | int i; 1335 | 1336 | // there's definitely room to store the characters eventually 1337 | while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1338 | // should never happen: 1339 | if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1340 | return; 1341 | // there's currently not enough room, so discard a redo record 1342 | stb_textedit_discard_redo(s); 1343 | } 1344 | r = &s->undo_rec[s->redo_point-1]; 1345 | 1346 | r->char_storage = s->redo_char_point - u.delete_length; 1347 | s->redo_char_point = s->redo_char_point - u.delete_length; 1348 | 1349 | // now save the characters 1350 | for (i=0; i < u.delete_length; ++i) 1351 | s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1352 | } 1353 | 1354 | // now we can carry out the deletion 1355 | STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1356 | } 1357 | 1358 | // check type of recorded action: 1359 | if (u.insert_length) { 1360 | // easy case: was a deletion, so we need to insert n characters 1361 | u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1362 | s->undo_char_point -= u.insert_length; 1363 | } 1364 | 1365 | state->cursor = u.where + u.insert_length; 1366 | 1367 | s->undo_point--; 1368 | s->redo_point--; 1369 | } 1370 | 1371 | static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1372 | { 1373 | StbUndoState *s = &state->undostate; 1374 | StbUndoRecord *u, r; 1375 | if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1376 | return; 1377 | 1378 | // we need to do two things: apply the redo record, and create an undo record 1379 | u = &s->undo_rec[s->undo_point]; 1380 | r = s->undo_rec[s->redo_point]; 1381 | 1382 | // we KNOW there must be room for the undo record, because the redo record 1383 | // was derived from an undo record 1384 | 1385 | u->delete_length = r.insert_length; 1386 | u->insert_length = r.delete_length; 1387 | u->where = r.where; 1388 | u->char_storage = -1; 1389 | 1390 | if (r.delete_length) { 1391 | // the redo record requires us to delete characters, so the undo record 1392 | // needs to store the characters 1393 | 1394 | if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1395 | u->insert_length = 0; 1396 | u->delete_length = 0; 1397 | } else { 1398 | int i; 1399 | u->char_storage = s->undo_char_point; 1400 | s->undo_char_point = s->undo_char_point + u->insert_length; 1401 | 1402 | // now save the characters 1403 | for (i=0; i < u->insert_length; ++i) 1404 | s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1405 | } 1406 | 1407 | STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1408 | } 1409 | 1410 | if (r.insert_length) { 1411 | // easy case: need to insert n characters 1412 | r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1413 | s->redo_char_point += r.insert_length; 1414 | } 1415 | 1416 | state->cursor = r.where + r.insert_length; 1417 | 1418 | s->undo_point++; 1419 | s->redo_point++; 1420 | } 1421 | 1422 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1423 | { 1424 | stb_text_createundo(&state->undostate, where, 0, length); 1425 | } 1426 | 1427 | static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1428 | { 1429 | int i; 1430 | IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1431 | if (p) { 1432 | for (i=0; i < length; ++i) 1433 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1434 | } 1435 | } 1436 | 1437 | static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1438 | { 1439 | int i; 1440 | IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1441 | if (p) { 1442 | for (i=0; i < old_length; ++i) 1443 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1444 | } 1445 | } 1446 | 1447 | // reset the state to default 1448 | static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1449 | { 1450 | state->undostate.undo_point = 0; 1451 | state->undostate.undo_char_point = 0; 1452 | state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT; 1453 | state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT; 1454 | state->select_end = state->select_start = 0; 1455 | state->cursor = 0; 1456 | state->has_preferred_x = 0; 1457 | state->preferred_x = 0; 1458 | state->cursor_at_end_of_line = 0; 1459 | state->initialized = 1; 1460 | state->single_line = (unsigned char) is_single_line; 1461 | state->insert_mode = 0; 1462 | state->row_count_per_page = 0; 1463 | } 1464 | 1465 | // API initialize 1466 | static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1467 | { 1468 | stb_textedit_clear_state(state, is_single_line); 1469 | } 1470 | 1471 | #if defined(__GNUC__) || defined(__clang__) 1472 | #pragma GCC diagnostic push 1473 | #pragma GCC diagnostic ignored "-Wcast-qual" 1474 | #endif 1475 | 1476 | static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len) 1477 | { 1478 | return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len); 1479 | } 1480 | 1481 | #if defined(__GNUC__) || defined(__clang__) 1482 | #pragma GCC diagnostic pop 1483 | #endif 1484 | 1485 | #endif//IMSTB_TEXTEDIT_IMPLEMENTATION 1486 | 1487 | /* 1488 | ------------------------------------------------------------------------------ 1489 | This software is available under 2 licenses -- choose whichever you prefer. 1490 | ------------------------------------------------------------------------------ 1491 | ALTERNATIVE A - MIT License 1492 | Copyright (c) 2017 Sean Barrett 1493 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1494 | this software and associated documentation files (the "Software"), to deal in 1495 | the Software without restriction, including without limitation the rights to 1496 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1497 | of the Software, and to permit persons to whom the Software is furnished to do 1498 | so, subject to the following conditions: 1499 | The above copyright notice and this permission notice shall be included in all 1500 | copies or substantial portions of the Software. 1501 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1502 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1503 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1504 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1505 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1506 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1507 | SOFTWARE. 1508 | ------------------------------------------------------------------------------ 1509 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1510 | This is free and unencumbered software released into the public domain. 1511 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1512 | software, either in source code form or as a compiled binary, for any purpose, 1513 | commercial or non-commercial, and by any means. 1514 | In jurisdictions that recognize copyright laws, the author or authors of this 1515 | software dedicate any and all copyright interest in the software to the public 1516 | domain. We make this dedication for the benefit of the public at large and to 1517 | the detriment of our heirs and successors. We intend this dedication to be an 1518 | overt act of relinquishment in perpetuity of all present and future rights to 1519 | this software under copyright law. 1520 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1521 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1522 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1523 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1524 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1525 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1526 | ------------------------------------------------------------------------------ 1527 | */ 1528 | -------------------------------------------------------------------------------- /src-docking/imstb_textedit.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_textedit.h 1.14. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) 5 | // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) 6 | // - Added name to struct or it may be forward declared in our code. 7 | // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925) 8 | // - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. 9 | // Grep for [DEAR IMGUI] to find some changes. 10 | // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* 11 | 12 | // stb_textedit.h - v1.14 - public domain - Sean Barrett 13 | // Development of this library was sponsored by RAD Game Tools 14 | // 15 | // This C header file implements the guts of a multi-line text-editing 16 | // widget; you implement display, word-wrapping, and low-level string 17 | // insertion/deletion, and stb_textedit will map user inputs into 18 | // insertions & deletions, plus updates to the cursor position, 19 | // selection state, and undo state. 20 | // 21 | // It is intended for use in games and other systems that need to build 22 | // their own custom widgets and which do not have heavy text-editing 23 | // requirements (this library is not recommended for use for editing large 24 | // texts, as its performance does not scale and it has limited undo). 25 | // 26 | // Non-trivial behaviors are modelled after Windows text controls. 27 | // 28 | // 29 | // LICENSE 30 | // 31 | // See end of file for license information. 32 | // 33 | // 34 | // DEPENDENCIES 35 | // 36 | // Uses the C runtime function 'memmove', which you can override 37 | // by defining IMSTB_TEXTEDIT_memmove before the implementation. 38 | // Uses no other functions. Performs no runtime allocations. 39 | // 40 | // 41 | // VERSION HISTORY 42 | // 43 | // !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion. 44 | // 1.14 (2021-07-11) page up/down, various fixes 45 | // 1.13 (2019-02-07) fix bug in undo size management 46 | // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 47 | // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 48 | // 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual 49 | // 1.9 (2016-08-27) customizable move-by-word 50 | // 1.8 (2016-04-02) better keyboard handling when mouse button is down 51 | // 1.7 (2015-09-13) change y range handling in case baseline is non-0 52 | // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 53 | // 1.5 (2014-09-10) add support for secondary keys for OS X 54 | // 1.4 (2014-08-17) fix signed/unsigned warnings 55 | // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 56 | // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 57 | // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 58 | // 1.0 (2012-07-26) improve documentation, initial public release 59 | // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 60 | // 0.2 (2011-11-28) fixes to undo/redo 61 | // 0.1 (2010-07-08) initial version 62 | // 63 | // ADDITIONAL CONTRIBUTORS 64 | // 65 | // Ulf Winklemann: move-by-word in 1.1 66 | // Fabian Giesen: secondary key inputs in 1.5 67 | // Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 68 | // Louis Schnellbach: page up/down in 1.14 69 | // 70 | // Bugfixes: 71 | // Scott Graham 72 | // Daniel Keller 73 | // Omar Cornut 74 | // Dan Thompson 75 | // 76 | // USAGE 77 | // 78 | // This file behaves differently depending on what symbols you define 79 | // before including it. 80 | // 81 | // 82 | // Header-file mode: 83 | // 84 | // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 85 | // it will operate in "header file" mode. In this mode, it declares a 86 | // single public symbol, STB_TexteditState, which encapsulates the current 87 | // state of a text widget (except for the string, which you will store 88 | // separately). 89 | // 90 | // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 91 | // primitive type that defines a single character (e.g. char, wchar_t, etc). 92 | // 93 | // To save space or increase undo-ability, you can optionally define the 94 | // following things that are used by the undo system: 95 | // 96 | // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 97 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 98 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 99 | // 100 | // If you don't define these, they are set to permissive types and 101 | // moderate sizes. The undo system does no memory allocations, so 102 | // it grows STB_TexteditState by the worst-case storage which is (in bytes): 103 | // 104 | // [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT 105 | // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHARCOUNT 106 | // 107 | // 108 | // Implementation mode: 109 | // 110 | // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 111 | // will compile the implementation of the text edit widget, depending 112 | // on a large number of symbols which must be defined before the include. 113 | // 114 | // The implementation is defined only as static functions. You will then 115 | // need to provide your own APIs in the same file which will access the 116 | // static functions. 117 | // 118 | // The basic concept is that you provide a "string" object which 119 | // behaves like an array of characters. stb_textedit uses indices to 120 | // refer to positions in the string, implicitly representing positions 121 | // in the displayed textedit. This is true for both plain text and 122 | // rich text; even with rich text stb_truetype interacts with your 123 | // code as if there was an array of all the displayed characters. 124 | // 125 | // Symbols that must be the same in header-file and implementation mode: 126 | // 127 | // STB_TEXTEDIT_CHARTYPE the character type 128 | // STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 129 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 130 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 131 | // 132 | // Symbols you must define for implementation mode: 133 | // 134 | // STB_TEXTEDIT_STRING the type of object representing a string being edited, 135 | // typically this is a wrapper object with other data you need 136 | // 137 | // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 138 | // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 139 | // starting from character #n (see discussion below) 140 | // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 141 | // to the xpos of the i+1'th char for a line of characters 142 | // starting at character #n (i.e. accounts for kerning 143 | // with previous char) 144 | // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 145 | // (return type is int, -1 means not valid to insert) 146 | // (not supported if you want to use UTF-8, see below) 147 | // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 148 | // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 149 | // as manually wordwrapping for end-of-line positioning 150 | // 151 | // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 152 | // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 153 | // returns number of characters actually inserted. [DEAR IMGUI] 154 | // 155 | // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 156 | // 157 | // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 158 | // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 159 | // STB_TEXTEDIT_K_UP keyboard input to move cursor up 160 | // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 161 | // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 162 | // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 163 | // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 164 | // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 165 | // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 166 | // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 167 | // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 168 | // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 169 | // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 170 | // STB_TEXTEDIT_K_REDO keyboard input to perform redo 171 | // 172 | // Optional: 173 | // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 174 | // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 175 | // required for default WORDLEFT/WORDRIGHT handlers 176 | // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 177 | // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 178 | // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 179 | // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 180 | // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 181 | // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 182 | // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 183 | // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 184 | // 185 | // To support UTF-8: 186 | // 187 | // STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character 188 | // STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character 189 | // Do NOT define STB_TEXTEDIT_KEYTOTEXT. 190 | // Instead, call stb_textedit_text() directly for text contents. 191 | // 192 | // Keyboard input must be encoded as a single integer value; e.g. a character code 193 | // and some bitflags that represent shift states. to simplify the interface, SHIFT must 194 | // be a bitflag, so we can test the shifted state of cursor movements to allow selection, 195 | // i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 196 | // 197 | // You can encode other things, such as CONTROL or ALT, in additional bits, and 198 | // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 199 | // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 200 | // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 201 | // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 202 | // API below. The control keys will only match WM_KEYDOWN events because of the 203 | // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 204 | // bit so it only decodes WM_CHAR events. 205 | // 206 | // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 207 | // row of characters assuming they start on the i'th character--the width and 208 | // the height and the number of characters consumed. This allows this library 209 | // to traverse the entire layout incrementally. You need to compute word-wrapping 210 | // here. 211 | // 212 | // Each textfield keeps its own insert mode state, which is not how normal 213 | // applications work. To keep an app-wide insert mode, update/copy the 214 | // "insert_mode" field of STB_TexteditState before/after calling API functions. 215 | // 216 | // API 217 | // 218 | // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 219 | // 220 | // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 221 | // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 222 | // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 223 | // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 224 | // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) 225 | // void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len) 226 | // 227 | // Each of these functions potentially updates the string and updates the 228 | // state. 229 | // 230 | // initialize_state: 231 | // set the textedit state to a known good default state when initially 232 | // constructing the textedit. 233 | // 234 | // click: 235 | // call this with the mouse x,y on a mouse down; it will update the cursor 236 | // and reset the selection start/end to the cursor point. the x,y must 237 | // be relative to the text widget, with (0,0) being the top left. 238 | // 239 | // drag: 240 | // call this with the mouse x,y on a mouse drag/up; it will update the 241 | // cursor and the selection end point 242 | // 243 | // cut: 244 | // call this to delete the current selection; returns true if there was 245 | // one. you should FIRST copy the current selection to the system paste buffer. 246 | // (To copy, just copy the current selection out of the string yourself.) 247 | // 248 | // paste: 249 | // call this to paste text at the current cursor point or over the current 250 | // selection if there is one. 251 | // 252 | // key: 253 | // call this for keyboard inputs sent to the textfield. you can use it 254 | // for "key down" events or for "translated" key events. if you need to 255 | // do both (as in Win32), or distinguish Unicode characters from control 256 | // inputs, set a high bit to distinguish the two; then you can define the 257 | // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 258 | // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 259 | // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to 260 | // anything other type you want before including. 261 | // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are 262 | // transformed into text and stb_textedit_text() is automatically called. 263 | // 264 | // text: (added 2025) 265 | // call this to directly send text input the textfield, which is required 266 | // for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT() 267 | // cannot infer text length. 268 | // 269 | // 270 | // When rendering, you can read the cursor position and selection state from 271 | // the STB_TexteditState. 272 | // 273 | // 274 | // Notes: 275 | // 276 | // This is designed to be usable in IMGUI, so it allows for the possibility of 277 | // running in an IMGUI that has NOT cached the multi-line layout. For this 278 | // reason, it provides an interface that is compatible with computing the 279 | // layout incrementally--we try to make sure we make as few passes through 280 | // as possible. (For example, to locate the mouse pointer in the text, we 281 | // could define functions that return the X and Y positions of characters 282 | // and binary search Y and then X, but if we're doing dynamic layout this 283 | // will run the layout algorithm many times, so instead we manually search 284 | // forward in one pass. Similar logic applies to e.g. up-arrow and 285 | // down-arrow movement.) 286 | // 287 | // If it's run in a widget that *has* cached the layout, then this is less 288 | // efficient, but it's not horrible on modern computers. But you wouldn't 289 | // want to edit million-line files with it. 290 | 291 | 292 | //////////////////////////////////////////////////////////////////////////// 293 | //////////////////////////////////////////////////////////////////////////// 294 | //// 295 | //// Header-file mode 296 | //// 297 | //// 298 | 299 | #ifndef INCLUDE_IMSTB_TEXTEDIT_H 300 | #define INCLUDE_IMSTB_TEXTEDIT_H 301 | 302 | //////////////////////////////////////////////////////////////////////// 303 | // 304 | // STB_TexteditState 305 | // 306 | // Definition of STB_TexteditState which you should store 307 | // per-textfield; it includes cursor position, selection state, 308 | // and undo state. 309 | // 310 | 311 | #ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT 312 | #define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 313 | #endif 314 | #ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT 315 | #define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 316 | #endif 317 | #ifndef IMSTB_TEXTEDIT_CHARTYPE 318 | #define IMSTB_TEXTEDIT_CHARTYPE int 319 | #endif 320 | #ifndef IMSTB_TEXTEDIT_POSITIONTYPE 321 | #define IMSTB_TEXTEDIT_POSITIONTYPE int 322 | #endif 323 | 324 | typedef struct 325 | { 326 | // private data 327 | IMSTB_TEXTEDIT_POSITIONTYPE where; 328 | IMSTB_TEXTEDIT_POSITIONTYPE insert_length; 329 | IMSTB_TEXTEDIT_POSITIONTYPE delete_length; 330 | int char_storage; 331 | } StbUndoRecord; 332 | 333 | typedef struct 334 | { 335 | // private data 336 | StbUndoRecord undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT]; 337 | IMSTB_TEXTEDIT_CHARTYPE undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT]; 338 | short undo_point, redo_point; 339 | int undo_char_point, redo_char_point; 340 | } StbUndoState; 341 | 342 | typedef struct STB_TexteditState 343 | { 344 | ///////////////////// 345 | // 346 | // public data 347 | // 348 | 349 | int cursor; 350 | // position of the text cursor within the string 351 | 352 | int select_start; // selection start point 353 | int select_end; 354 | // selection start and end point in characters; if equal, no selection. 355 | // note that start may be less than or greater than end (e.g. when 356 | // dragging the mouse, start is where the initial click was, and you 357 | // can drag in either direction) 358 | 359 | unsigned char insert_mode; 360 | // each textfield keeps its own insert mode state. to keep an app-wide 361 | // insert mode, copy this value in/out of the app state 362 | 363 | int row_count_per_page; 364 | // page size in number of row. 365 | // this value MUST be set to >0 for pageup or pagedown in multilines documents. 366 | 367 | ///////////////////// 368 | // 369 | // private data 370 | // 371 | unsigned char cursor_at_end_of_line; // not implemented yet 372 | unsigned char initialized; 373 | unsigned char has_preferred_x; 374 | unsigned char single_line; 375 | unsigned char padding1, padding2, padding3; 376 | float preferred_x; // this determines where the cursor up/down tries to seek to along x 377 | StbUndoState undostate; 378 | } STB_TexteditState; 379 | 380 | 381 | //////////////////////////////////////////////////////////////////////// 382 | // 383 | // StbTexteditRow 384 | // 385 | // Result of layout query, used by stb_textedit to determine where 386 | // the text in each row is. 387 | 388 | // result of layout query 389 | typedef struct 390 | { 391 | float x0,x1; // starting x location, end x location (allows for align=right, etc) 392 | float baseline_y_delta; // position of baseline relative to previous row's baseline 393 | float ymin,ymax; // height of row above and below baseline 394 | int num_chars; 395 | } StbTexteditRow; 396 | #endif //INCLUDE_IMSTB_TEXTEDIT_H 397 | 398 | 399 | //////////////////////////////////////////////////////////////////////////// 400 | //////////////////////////////////////////////////////////////////////////// 401 | //// 402 | //// Implementation mode 403 | //// 404 | //// 405 | 406 | 407 | // implementation isn't include-guarded, since it might have indirectly 408 | // included just the "header" portion 409 | #ifdef IMSTB_TEXTEDIT_IMPLEMENTATION 410 | 411 | #ifndef IMSTB_TEXTEDIT_memmove 412 | #include 413 | #define IMSTB_TEXTEDIT_memmove memmove 414 | #endif 415 | 416 | // [DEAR IMGUI] 417 | // Functions must be implemented for UTF8 support 418 | // Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit. 419 | // There is not necessarily a '[DEAR IMGUI]' at the usage sites. 420 | #ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX 421 | #define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1) 422 | #endif 423 | #ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX 424 | #define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1) 425 | #endif 426 | 427 | ///////////////////////////////////////////////////////////////////////////// 428 | // 429 | // Mouse input handling 430 | // 431 | 432 | // traverse the layout to locate the nearest character to a display position 433 | static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line) 434 | { 435 | StbTexteditRow r; 436 | int n = STB_TEXTEDIT_STRINGLEN(str); 437 | float base_y = 0, prev_x; 438 | int i=0, k; 439 | 440 | r.x0 = r.x1 = 0; 441 | r.ymin = r.ymax = 0; 442 | r.num_chars = 0; 443 | *out_side_on_line = 0; 444 | 445 | // search rows to find one that straddles 'y' 446 | while (i < n) { 447 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 448 | if (r.num_chars <= 0) 449 | return n; 450 | 451 | if (i==0 && y < base_y + r.ymin) 452 | return 0; 453 | 454 | if (y < base_y + r.ymax) 455 | break; 456 | 457 | i += r.num_chars; 458 | base_y += r.baseline_y_delta; 459 | } 460 | 461 | // below all text, return 'after' last character 462 | if (i >= n) 463 | { 464 | *out_side_on_line = 1; 465 | return n; 466 | } 467 | 468 | // check if it's before the beginning of the line 469 | if (x < r.x0) 470 | return i; 471 | 472 | // check if it's before the end of the line 473 | if (x < r.x1) { 474 | // search characters in row for one that straddles 'x' 475 | prev_x = r.x0; 476 | for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) { 477 | float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 478 | if (x < prev_x+w) { 479 | *out_side_on_line = (k == 0) ? 0 : 1; 480 | if (x < prev_x+w/2) 481 | return k+i; 482 | else 483 | return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k); 484 | } 485 | prev_x += w; 486 | } 487 | // shouldn't happen, but if it does, fall through to end-of-line case 488 | } 489 | 490 | // if the last character is a newline, return that. otherwise return 'after' the last character 491 | *out_side_on_line = 1; 492 | if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 493 | return i+r.num_chars-1; 494 | else 495 | return i+r.num_chars; 496 | } 497 | 498 | // API click: on mouse down, move the cursor to the clicked location, and reset the selection 499 | static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 500 | { 501 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 502 | // goes off the top or bottom of the text 503 | int side_on_line; 504 | if( state->single_line ) 505 | { 506 | StbTexteditRow r; 507 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 508 | y = r.ymin; 509 | } 510 | 511 | state->cursor = stb_text_locate_coord(str, x, y, &side_on_line); 512 | state->select_start = state->cursor; 513 | state->select_end = state->cursor; 514 | state->has_preferred_x = 0; 515 | str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); 516 | } 517 | 518 | // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 519 | static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 520 | { 521 | int p = 0; 522 | int side_on_line; 523 | 524 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 525 | // goes off the top or bottom of the text 526 | if( state->single_line ) 527 | { 528 | StbTexteditRow r; 529 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 530 | y = r.ymin; 531 | } 532 | 533 | if (state->select_start == state->select_end) 534 | state->select_start = state->cursor; 535 | 536 | p = stb_text_locate_coord(str, x, y, &side_on_line); 537 | state->cursor = state->select_end = p; 538 | str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left); 539 | } 540 | 541 | ///////////////////////////////////////////////////////////////////////////// 542 | // 543 | // Keyboard input handling 544 | // 545 | 546 | // forward declarations 547 | static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); 548 | static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state); 549 | static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 550 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 551 | static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 552 | 553 | typedef struct 554 | { 555 | float x,y; // position of n'th character 556 | float height; // height of line 557 | int first_char, length; // first char of row, and length 558 | int prev_first; // first char of previous row 559 | } StbFindState; 560 | 561 | // find the x/y location of a character, and remember info about the previous row in 562 | // case we get a move-up event (for page up, we'll have to rescan) 563 | static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line) 564 | { 565 | StbTexteditRow r; 566 | int prev_start = 0; 567 | int z = STB_TEXTEDIT_STRINGLEN(str); 568 | int i=0, first; 569 | 570 | if (n == z && single_line) { 571 | // special case if it's at the end (may not be needed?) 572 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 573 | find->y = 0; 574 | find->first_char = 0; 575 | find->length = z; 576 | find->height = r.ymax - r.ymin; 577 | find->x = r.x1; 578 | return; 579 | } 580 | 581 | // search rows to find the one that straddles character n 582 | find->y = 0; 583 | 584 | for(;;) { 585 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 586 | if (n < i + r.num_chars) 587 | break; 588 | 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 589 | break; 590 | if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line 591 | break; // [DEAR IMGUI] 592 | prev_start = i; 593 | i += r.num_chars; 594 | find->y += r.baseline_y_delta; 595 | if (i == z) // [DEAR IMGUI] 596 | { 597 | r.num_chars = 0; // [DEAR IMGUI] 598 | break; // [DEAR IMGUI] 599 | } 600 | } 601 | 602 | find->first_char = first = i; 603 | find->length = r.num_chars; 604 | find->height = r.ymax - r.ymin; 605 | find->prev_first = prev_start; 606 | 607 | // now scan to find xpos 608 | find->x = r.x0; 609 | for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first) 610 | find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 611 | } 612 | 613 | #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 614 | 615 | // make the selection/cursor state valid if client altered the string 616 | static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 617 | { 618 | int n = STB_TEXTEDIT_STRINGLEN(str); 619 | if (STB_TEXT_HAS_SELECTION(state)) { 620 | if (state->select_start > n) state->select_start = n; 621 | if (state->select_end > n) state->select_end = n; 622 | // if clamping forced them to be equal, move the cursor to match 623 | if (state->select_start == state->select_end) 624 | state->cursor = state->select_start; 625 | } 626 | if (state->cursor > n) state->cursor = n; 627 | } 628 | 629 | // delete characters while updating undo 630 | static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 631 | { 632 | stb_text_makeundo_delete(str, state, where, len); 633 | STB_TEXTEDIT_DELETECHARS(str, where, len); 634 | state->has_preferred_x = 0; 635 | } 636 | 637 | // delete the section 638 | static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 639 | { 640 | stb_textedit_clamp(str, state); 641 | if (STB_TEXT_HAS_SELECTION(state)) { 642 | if (state->select_start < state->select_end) { 643 | stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 644 | state->select_end = state->cursor = state->select_start; 645 | } else { 646 | stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 647 | state->select_start = state->cursor = state->select_end; 648 | } 649 | state->has_preferred_x = 0; 650 | } 651 | } 652 | 653 | // canoncialize the selection so start <= end 654 | static void stb_textedit_sortselection(STB_TexteditState *state) 655 | { 656 | if (state->select_end < state->select_start) { 657 | int temp = state->select_end; 658 | state->select_end = state->select_start; 659 | state->select_start = temp; 660 | } 661 | } 662 | 663 | // move cursor to first character of selection 664 | static void stb_textedit_move_to_first(STB_TexteditState *state) 665 | { 666 | if (STB_TEXT_HAS_SELECTION(state)) { 667 | stb_textedit_sortselection(state); 668 | state->cursor = state->select_start; 669 | state->select_end = state->select_start; 670 | state->has_preferred_x = 0; 671 | } 672 | } 673 | 674 | // move cursor to last character of selection 675 | static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 676 | { 677 | if (STB_TEXT_HAS_SELECTION(state)) { 678 | stb_textedit_sortselection(state); 679 | stb_textedit_clamp(str, state); 680 | state->cursor = state->select_end; 681 | state->select_start = state->select_end; 682 | state->has_preferred_x = 0; 683 | } 684 | } 685 | 686 | // [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping. 687 | #ifndef STB_TEXTEDIT_MOVELINESTART 688 | static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) 689 | { 690 | if (state->single_line) 691 | return 0; 692 | while (cursor > 0) { 693 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor); 694 | if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) 695 | break; 696 | cursor = prev; 697 | } 698 | return cursor; 699 | } 700 | #define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start 701 | #endif 702 | #ifndef STB_TEXTEDIT_MOVELINEEND 703 | static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor) 704 | { 705 | int n = STB_TEXTEDIT_STRINGLEN(str); 706 | if (state->single_line) 707 | return n; 708 | while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE) 709 | cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor); 710 | return cursor; 711 | } 712 | #define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end 713 | #endif 714 | 715 | #ifdef STB_TEXTEDIT_IS_SPACE 716 | static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) 717 | { 718 | return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 719 | } 720 | 721 | #ifndef STB_TEXTEDIT_MOVEWORDLEFT 722 | static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) 723 | { 724 | c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character 725 | while (c >= 0 && !is_word_boundary(str, c)) 726 | c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c); 727 | 728 | if( c < 0 ) 729 | c = 0; 730 | 731 | return c; 732 | } 733 | #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 734 | #endif 735 | 736 | #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 737 | static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) 738 | { 739 | const int len = STB_TEXTEDIT_STRINGLEN(str); 740 | c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character 741 | while( c < len && !is_word_boundary( str, c ) ) 742 | c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); 743 | 744 | if( c > len ) 745 | c = len; 746 | 747 | return c; 748 | } 749 | #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 750 | #endif 751 | 752 | #endif 753 | 754 | // update selection and cursor to match each other 755 | static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 756 | { 757 | if (!STB_TEXT_HAS_SELECTION(state)) 758 | state->select_start = state->select_end = state->cursor; 759 | else 760 | state->cursor = state->select_end; 761 | } 762 | 763 | // API cut: delete selection 764 | static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 765 | { 766 | if (STB_TEXT_HAS_SELECTION(state)) { 767 | stb_textedit_delete_selection(str,state); // implicitly clamps 768 | state->has_preferred_x = 0; 769 | return 1; 770 | } 771 | return 0; 772 | } 773 | 774 | // API paste: replace existing selection with passed-in text 775 | static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len) 776 | { 777 | // if there's a selection, the paste should delete it 778 | stb_textedit_clamp(str, state); 779 | stb_textedit_delete_selection(str,state); 780 | // try to insert the characters 781 | len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len); 782 | if (len) { 783 | stb_text_makeundo_insert(state, state->cursor, len); 784 | state->cursor += len; 785 | state->has_preferred_x = 0; 786 | return 1; 787 | } 788 | // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details) 789 | return 0; 790 | } 791 | 792 | #ifndef STB_TEXTEDIT_KEYTYPE 793 | #define STB_TEXTEDIT_KEYTYPE int 794 | #endif 795 | 796 | // API key: process text input 797 | // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. 798 | static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) 799 | { 800 | // can't add newline in single-line mode 801 | if (text[0] == '\n' && state->single_line) 802 | return; 803 | 804 | if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 805 | stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 806 | STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 807 | text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); 808 | if (text_len) { 809 | state->cursor += text_len; 810 | state->has_preferred_x = 0; 811 | } 812 | } else { 813 | stb_textedit_delete_selection(str, state); // implicitly clamps 814 | text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len); 815 | if (text_len) { 816 | stb_text_makeundo_insert(state, state->cursor, text_len); 817 | state->cursor += text_len; 818 | state->has_preferred_x = 0; 819 | } 820 | } 821 | } 822 | 823 | // API key: process a keyboard input 824 | static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 825 | { 826 | retry: 827 | switch (key) { 828 | default: { 829 | #ifdef STB_TEXTEDIT_KEYTOTEXT 830 | // This is not suitable for UTF-8 support. 831 | int c = STB_TEXTEDIT_KEYTOTEXT(key); 832 | if (c > 0) { 833 | IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c; 834 | stb_textedit_text(str, state, &ch, 1); 835 | } 836 | #endif 837 | break; 838 | } 839 | 840 | #ifdef STB_TEXTEDIT_K_INSERT 841 | case STB_TEXTEDIT_K_INSERT: 842 | state->insert_mode = !state->insert_mode; 843 | break; 844 | #endif 845 | 846 | case STB_TEXTEDIT_K_UNDO: 847 | stb_text_undo(str, state); 848 | state->has_preferred_x = 0; 849 | break; 850 | 851 | case STB_TEXTEDIT_K_REDO: 852 | stb_text_redo(str, state); 853 | state->has_preferred_x = 0; 854 | break; 855 | 856 | case STB_TEXTEDIT_K_LEFT: 857 | // if currently there's a selection, move cursor to start of selection 858 | if (STB_TEXT_HAS_SELECTION(state)) 859 | stb_textedit_move_to_first(state); 860 | else 861 | if (state->cursor > 0) 862 | state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); 863 | state->has_preferred_x = 0; 864 | break; 865 | 866 | case STB_TEXTEDIT_K_RIGHT: 867 | // if currently there's a selection, move cursor to end of selection 868 | if (STB_TEXT_HAS_SELECTION(state)) 869 | stb_textedit_move_to_last(str, state); 870 | else 871 | state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 872 | stb_textedit_clamp(str, state); 873 | state->has_preferred_x = 0; 874 | break; 875 | 876 | case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 877 | stb_textedit_clamp(str, state); 878 | stb_textedit_prep_selection_at_cursor(state); 879 | // move selection left 880 | if (state->select_end > 0) 881 | state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end); 882 | state->cursor = state->select_end; 883 | state->has_preferred_x = 0; 884 | break; 885 | 886 | #ifdef STB_TEXTEDIT_MOVEWORDLEFT 887 | case STB_TEXTEDIT_K_WORDLEFT: 888 | if (STB_TEXT_HAS_SELECTION(state)) 889 | stb_textedit_move_to_first(state); 890 | else { 891 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 892 | stb_textedit_clamp( str, state ); 893 | } 894 | break; 895 | 896 | case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 897 | if( !STB_TEXT_HAS_SELECTION( state ) ) 898 | stb_textedit_prep_selection_at_cursor(state); 899 | 900 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 901 | state->select_end = state->cursor; 902 | 903 | stb_textedit_clamp( str, state ); 904 | break; 905 | #endif 906 | 907 | #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 908 | case STB_TEXTEDIT_K_WORDRIGHT: 909 | if (STB_TEXT_HAS_SELECTION(state)) 910 | stb_textedit_move_to_last(str, state); 911 | else { 912 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 913 | stb_textedit_clamp( str, state ); 914 | } 915 | break; 916 | 917 | case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 918 | if( !STB_TEXT_HAS_SELECTION( state ) ) 919 | stb_textedit_prep_selection_at_cursor(state); 920 | 921 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 922 | state->select_end = state->cursor; 923 | 924 | stb_textedit_clamp( str, state ); 925 | break; 926 | #endif 927 | 928 | case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 929 | stb_textedit_prep_selection_at_cursor(state); 930 | // move selection right 931 | state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end); 932 | stb_textedit_clamp(str, state); 933 | state->cursor = state->select_end; 934 | state->has_preferred_x = 0; 935 | break; 936 | 937 | case STB_TEXTEDIT_K_DOWN: 938 | case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: 939 | case STB_TEXTEDIT_K_PGDOWN: 940 | case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: { 941 | StbFindState find; 942 | StbTexteditRow row; 943 | int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 944 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN; 945 | int row_count = is_page ? state->row_count_per_page : 1; 946 | 947 | if (!is_page && state->single_line) { 948 | // on windows, up&down in single-line behave like left&right 949 | key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 950 | goto retry; 951 | } 952 | 953 | if (sel) 954 | stb_textedit_prep_selection_at_cursor(state); 955 | else if (STB_TEXT_HAS_SELECTION(state)) 956 | stb_textedit_move_to_last(str, state); 957 | 958 | // compute current position of cursor point 959 | stb_textedit_clamp(str, state); 960 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 961 | 962 | for (j = 0; j < row_count; ++j) { 963 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 964 | int start = find.first_char + find.length; 965 | 966 | if (find.length == 0) 967 | break; 968 | 969 | // [DEAR IMGUI] 970 | // going down while being on the last line shouldn't bring us to that line end 971 | //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE) 972 | // break; 973 | 974 | // now find character position down a row 975 | state->cursor = start; 976 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 977 | x = row.x0; 978 | for (i=0; i < row.num_chars; ) { 979 | float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 980 | int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 981 | #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE 982 | if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) 983 | break; 984 | #endif 985 | x += dx; 986 | if (x > goal_x) 987 | break; 988 | i += next - state->cursor; 989 | state->cursor = next; 990 | } 991 | stb_textedit_clamp(str, state); 992 | 993 | if (state->cursor == find.first_char + find.length) 994 | str->LastMoveDirectionLR = ImGuiDir_Left; 995 | state->has_preferred_x = 1; 996 | state->preferred_x = goal_x; 997 | 998 | if (sel) 999 | state->select_end = state->cursor; 1000 | 1001 | // go to next line 1002 | find.first_char = find.first_char + find.length; 1003 | find.length = row.num_chars; 1004 | } 1005 | break; 1006 | } 1007 | 1008 | case STB_TEXTEDIT_K_UP: 1009 | case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: 1010 | case STB_TEXTEDIT_K_PGUP: 1011 | case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: { 1012 | StbFindState find; 1013 | StbTexteditRow row; 1014 | int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 1015 | int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP; 1016 | int row_count = is_page ? state->row_count_per_page : 1; 1017 | 1018 | if (!is_page && state->single_line) { 1019 | // on windows, up&down become left&right 1020 | key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 1021 | goto retry; 1022 | } 1023 | 1024 | if (sel) 1025 | stb_textedit_prep_selection_at_cursor(state); 1026 | else if (STB_TEXT_HAS_SELECTION(state)) 1027 | stb_textedit_move_to_first(state); 1028 | 1029 | // compute current position of cursor point 1030 | stb_textedit_clamp(str, state); 1031 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 1032 | 1033 | for (j = 0; j < row_count; ++j) { 1034 | float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x; 1035 | 1036 | // can only go up if there's a previous row 1037 | if (find.prev_first == find.first_char) 1038 | break; 1039 | 1040 | // now find character position up a row 1041 | state->cursor = find.prev_first; 1042 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 1043 | x = row.x0; 1044 | for (i=0; i < row.num_chars; ) { 1045 | float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 1046 | int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); 1047 | #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE 1048 | if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) 1049 | break; 1050 | #endif 1051 | x += dx; 1052 | if (x > goal_x) 1053 | break; 1054 | i += next - state->cursor; 1055 | state->cursor = next; 1056 | } 1057 | stb_textedit_clamp(str, state); 1058 | 1059 | if (state->cursor == find.first_char) 1060 | str->LastMoveDirectionLR = ImGuiDir_Right; 1061 | else if (state->cursor == find.prev_first) 1062 | str->LastMoveDirectionLR = ImGuiDir_Left; 1063 | state->has_preferred_x = 1; 1064 | state->preferred_x = goal_x; 1065 | 1066 | if (sel) 1067 | state->select_end = state->cursor; 1068 | 1069 | // go to previous line 1070 | // (we need to scan previous line the hard way. maybe we could expose this as a new API function?) 1071 | prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; 1072 | while (prev_scan > 0) 1073 | { 1074 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan); 1075 | if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE) 1076 | break; 1077 | prev_scan = prev; 1078 | } 1079 | find.first_char = find.prev_first; 1080 | find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan); 1081 | } 1082 | break; 1083 | } 1084 | 1085 | case STB_TEXTEDIT_K_DELETE: 1086 | case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 1087 | if (STB_TEXT_HAS_SELECTION(state)) 1088 | stb_textedit_delete_selection(str, state); 1089 | else { 1090 | int n = STB_TEXTEDIT_STRINGLEN(str); 1091 | if (state->cursor < n) 1092 | stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor); 1093 | } 1094 | state->has_preferred_x = 0; 1095 | break; 1096 | 1097 | case STB_TEXTEDIT_K_BACKSPACE: 1098 | case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 1099 | if (STB_TEXT_HAS_SELECTION(state)) 1100 | stb_textedit_delete_selection(str, state); 1101 | else { 1102 | stb_textedit_clamp(str, state); 1103 | if (state->cursor > 0) { 1104 | int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor); 1105 | stb_textedit_delete(str, state, prev, state->cursor - prev); 1106 | state->cursor = prev; 1107 | } 1108 | } 1109 | state->has_preferred_x = 0; 1110 | break; 1111 | 1112 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1113 | case STB_TEXTEDIT_K_TEXTSTART2: 1114 | #endif 1115 | case STB_TEXTEDIT_K_TEXTSTART: 1116 | state->cursor = state->select_start = state->select_end = 0; 1117 | state->has_preferred_x = 0; 1118 | break; 1119 | 1120 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1121 | case STB_TEXTEDIT_K_TEXTEND2: 1122 | #endif 1123 | case STB_TEXTEDIT_K_TEXTEND: 1124 | state->cursor = STB_TEXTEDIT_STRINGLEN(str); 1125 | state->select_start = state->select_end = 0; 1126 | state->has_preferred_x = 0; 1127 | break; 1128 | 1129 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1130 | case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 1131 | #endif 1132 | case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 1133 | stb_textedit_prep_selection_at_cursor(state); 1134 | state->cursor = state->select_end = 0; 1135 | state->has_preferred_x = 0; 1136 | break; 1137 | 1138 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1139 | case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 1140 | #endif 1141 | case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 1142 | stb_textedit_prep_selection_at_cursor(state); 1143 | state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 1144 | state->has_preferred_x = 0; 1145 | break; 1146 | 1147 | 1148 | #ifdef STB_TEXTEDIT_K_LINESTART2 1149 | case STB_TEXTEDIT_K_LINESTART2: 1150 | #endif 1151 | case STB_TEXTEDIT_K_LINESTART: 1152 | stb_textedit_clamp(str, state); 1153 | stb_textedit_move_to_first(state); 1154 | state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); 1155 | state->has_preferred_x = 0; 1156 | break; 1157 | 1158 | #ifdef STB_TEXTEDIT_K_LINEEND2 1159 | case STB_TEXTEDIT_K_LINEEND2: 1160 | #endif 1161 | case STB_TEXTEDIT_K_LINEEND: { 1162 | stb_textedit_clamp(str, state); 1163 | stb_textedit_move_to_last(str, state); 1164 | state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); 1165 | state->has_preferred_x = 0; 1166 | break; 1167 | } 1168 | 1169 | #ifdef STB_TEXTEDIT_K_LINESTART2 1170 | case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1171 | #endif 1172 | case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1173 | stb_textedit_clamp(str, state); 1174 | stb_textedit_prep_selection_at_cursor(state); 1175 | state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor); 1176 | state->select_end = state->cursor; 1177 | state->has_preferred_x = 0; 1178 | break; 1179 | 1180 | #ifdef STB_TEXTEDIT_K_LINEEND2 1181 | case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1182 | #endif 1183 | case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1184 | stb_textedit_clamp(str, state); 1185 | stb_textedit_prep_selection_at_cursor(state); 1186 | state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor); 1187 | state->select_end = state->cursor; 1188 | state->has_preferred_x = 0; 1189 | break; 1190 | } 1191 | } 1192 | } 1193 | 1194 | ///////////////////////////////////////////////////////////////////////////// 1195 | // 1196 | // Undo processing 1197 | // 1198 | // @OPTIMIZE: the undo/redo buffer should be circular 1199 | 1200 | static void stb_textedit_flush_redo(StbUndoState *state) 1201 | { 1202 | state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT; 1203 | state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT; 1204 | } 1205 | 1206 | // discard the oldest entry in the undo list 1207 | static void stb_textedit_discard_undo(StbUndoState *state) 1208 | { 1209 | if (state->undo_point > 0) { 1210 | // if the 0th undo state has characters, clean those up 1211 | if (state->undo_rec[0].char_storage >= 0) { 1212 | int n = state->undo_rec[0].insert_length, i; 1213 | // delete n characters from all other records 1214 | state->undo_char_point -= n; 1215 | IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE))); 1216 | for (i=0; i < state->undo_point; ++i) 1217 | if (state->undo_rec[i].char_storage >= 0) 1218 | state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 1219 | } 1220 | --state->undo_point; 1221 | IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 1222 | } 1223 | } 1224 | 1225 | // discard the oldest entry in the redo list--it's bad if this 1226 | // ever happens, but because undo & redo have to store the actual 1227 | // characters in different cases, the redo character buffer can 1228 | // fill up even though the undo buffer didn't 1229 | static void stb_textedit_discard_redo(StbUndoState *state) 1230 | { 1231 | int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1; 1232 | 1233 | if (state->redo_point <= k) { 1234 | // if the k'th undo state has characters, clean those up 1235 | if (state->undo_rec[k].char_storage >= 0) { 1236 | int n = state->undo_rec[k].insert_length, i; 1237 | // move the remaining redo character data to the end of the buffer 1238 | state->redo_char_point += n; 1239 | IMSTB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((IMSTB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(IMSTB_TEXTEDIT_CHARTYPE))); 1240 | // adjust the position of all the other records to account for above memmove 1241 | for (i=state->redo_point; i < k; ++i) 1242 | if (state->undo_rec[i].char_storage >= 0) 1243 | state->undo_rec[i].char_storage += n; 1244 | } 1245 | // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 1246 | // [DEAR IMGUI] 1247 | size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 1248 | const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 1249 | const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 1250 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 1251 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 1252 | IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 1253 | 1254 | // now move redo_point to point to the new one 1255 | ++state->redo_point; 1256 | } 1257 | } 1258 | 1259 | static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1260 | { 1261 | // any time we create a new undo record, we discard redo 1262 | stb_textedit_flush_redo(state); 1263 | 1264 | // if we have no free records, we have to make room, by sliding the 1265 | // existing records down 1266 | if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1267 | stb_textedit_discard_undo(state); 1268 | 1269 | // if the characters to store won't possibly fit in the buffer, we can't undo 1270 | if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) { 1271 | state->undo_point = 0; 1272 | state->undo_char_point = 0; 1273 | return NULL; 1274 | } 1275 | 1276 | // if we don't have enough free characters in the buffer, we have to make room 1277 | while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) 1278 | stb_textedit_discard_undo(state); 1279 | 1280 | return &state->undo_rec[state->undo_point++]; 1281 | } 1282 | 1283 | static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1284 | { 1285 | StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1286 | if (r == NULL) 1287 | return NULL; 1288 | 1289 | r->where = pos; 1290 | r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len; 1291 | r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len; 1292 | 1293 | if (insert_len == 0) { 1294 | r->char_storage = -1; 1295 | return NULL; 1296 | } else { 1297 | r->char_storage = state->undo_char_point; 1298 | state->undo_char_point += insert_len; 1299 | return &state->undo_char[r->char_storage]; 1300 | } 1301 | } 1302 | 1303 | static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1304 | { 1305 | StbUndoState *s = &state->undostate; 1306 | StbUndoRecord u, *r; 1307 | if (s->undo_point == 0) 1308 | return; 1309 | 1310 | // we need to do two things: apply the undo record, and create a redo record 1311 | u = s->undo_rec[s->undo_point-1]; 1312 | r = &s->undo_rec[s->redo_point-1]; 1313 | r->char_storage = -1; 1314 | 1315 | r->insert_length = u.delete_length; 1316 | r->delete_length = u.insert_length; 1317 | r->where = u.where; 1318 | 1319 | if (u.delete_length) { 1320 | // if the undo record says to delete characters, then the redo record will 1321 | // need to re-insert the characters that get deleted, so we need to store 1322 | // them. 1323 | 1324 | // there are three cases: 1325 | // there's enough room to store the characters 1326 | // characters stored for *redoing* don't leave room for redo 1327 | // characters stored for *undoing* don't leave room for redo 1328 | // if the last is true, we have to bail 1329 | 1330 | if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) { 1331 | // the undo records take up too much character space; there's no space to store the redo characters 1332 | r->insert_length = 0; 1333 | } else { 1334 | int i; 1335 | 1336 | // there's definitely room to store the characters eventually 1337 | while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1338 | // should never happen: 1339 | if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1340 | return; 1341 | // there's currently not enough room, so discard a redo record 1342 | stb_textedit_discard_redo(s); 1343 | } 1344 | r = &s->undo_rec[s->redo_point-1]; 1345 | 1346 | r->char_storage = s->redo_char_point - u.delete_length; 1347 | s->redo_char_point = s->redo_char_point - u.delete_length; 1348 | 1349 | // now save the characters 1350 | for (i=0; i < u.delete_length; ++i) 1351 | s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1352 | } 1353 | 1354 | // now we can carry out the deletion 1355 | STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1356 | } 1357 | 1358 | // check type of recorded action: 1359 | if (u.insert_length) { 1360 | // easy case: was a deletion, so we need to insert n characters 1361 | u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1362 | s->undo_char_point -= u.insert_length; 1363 | } 1364 | 1365 | state->cursor = u.where + u.insert_length; 1366 | 1367 | s->undo_point--; 1368 | s->redo_point--; 1369 | } 1370 | 1371 | static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1372 | { 1373 | StbUndoState *s = &state->undostate; 1374 | StbUndoRecord *u, r; 1375 | if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT) 1376 | return; 1377 | 1378 | // we need to do two things: apply the redo record, and create an undo record 1379 | u = &s->undo_rec[s->undo_point]; 1380 | r = s->undo_rec[s->redo_point]; 1381 | 1382 | // we KNOW there must be room for the undo record, because the redo record 1383 | // was derived from an undo record 1384 | 1385 | u->delete_length = r.insert_length; 1386 | u->insert_length = r.delete_length; 1387 | u->where = r.where; 1388 | u->char_storage = -1; 1389 | 1390 | if (r.delete_length) { 1391 | // the redo record requires us to delete characters, so the undo record 1392 | // needs to store the characters 1393 | 1394 | if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1395 | u->insert_length = 0; 1396 | u->delete_length = 0; 1397 | } else { 1398 | int i; 1399 | u->char_storage = s->undo_char_point; 1400 | s->undo_char_point = s->undo_char_point + u->insert_length; 1401 | 1402 | // now save the characters 1403 | for (i=0; i < u->insert_length; ++i) 1404 | s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1405 | } 1406 | 1407 | STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1408 | } 1409 | 1410 | if (r.insert_length) { 1411 | // easy case: need to insert n characters 1412 | r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1413 | s->redo_char_point += r.insert_length; 1414 | } 1415 | 1416 | state->cursor = r.where + r.insert_length; 1417 | 1418 | s->undo_point++; 1419 | s->redo_point++; 1420 | } 1421 | 1422 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1423 | { 1424 | stb_text_createundo(&state->undostate, where, 0, length); 1425 | } 1426 | 1427 | static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1428 | { 1429 | int i; 1430 | IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1431 | if (p) { 1432 | for (i=0; i < length; ++i) 1433 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1434 | } 1435 | } 1436 | 1437 | static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1438 | { 1439 | int i; 1440 | IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1441 | if (p) { 1442 | for (i=0; i < old_length; ++i) 1443 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1444 | } 1445 | } 1446 | 1447 | // reset the state to default 1448 | static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1449 | { 1450 | state->undostate.undo_point = 0; 1451 | state->undostate.undo_char_point = 0; 1452 | state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT; 1453 | state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT; 1454 | state->select_end = state->select_start = 0; 1455 | state->cursor = 0; 1456 | state->has_preferred_x = 0; 1457 | state->preferred_x = 0; 1458 | state->cursor_at_end_of_line = 0; 1459 | state->initialized = 1; 1460 | state->single_line = (unsigned char) is_single_line; 1461 | state->insert_mode = 0; 1462 | state->row_count_per_page = 0; 1463 | } 1464 | 1465 | // API initialize 1466 | static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1467 | { 1468 | stb_textedit_clear_state(state, is_single_line); 1469 | } 1470 | 1471 | #if defined(__GNUC__) || defined(__clang__) 1472 | #pragma GCC diagnostic push 1473 | #pragma GCC diagnostic ignored "-Wcast-qual" 1474 | #endif 1475 | 1476 | static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len) 1477 | { 1478 | return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len); 1479 | } 1480 | 1481 | #if defined(__GNUC__) || defined(__clang__) 1482 | #pragma GCC diagnostic pop 1483 | #endif 1484 | 1485 | #endif//IMSTB_TEXTEDIT_IMPLEMENTATION 1486 | 1487 | /* 1488 | ------------------------------------------------------------------------------ 1489 | This software is available under 2 licenses -- choose whichever you prefer. 1490 | ------------------------------------------------------------------------------ 1491 | ALTERNATIVE A - MIT License 1492 | Copyright (c) 2017 Sean Barrett 1493 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1494 | this software and associated documentation files (the "Software"), to deal in 1495 | the Software without restriction, including without limitation the rights to 1496 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1497 | of the Software, and to permit persons to whom the Software is furnished to do 1498 | so, subject to the following conditions: 1499 | The above copyright notice and this permission notice shall be included in all 1500 | copies or substantial portions of the Software. 1501 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1502 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1503 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1504 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1505 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1506 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1507 | SOFTWARE. 1508 | ------------------------------------------------------------------------------ 1509 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1510 | This is free and unencumbered software released into the public domain. 1511 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1512 | software, either in source code form or as a compiled binary, for any purpose, 1513 | commercial or non-commercial, and by any means. 1514 | In jurisdictions that recognize copyright laws, the author or authors of this 1515 | software dedicate any and all copyright interest in the software to the public 1516 | domain. We make this dedication for the benefit of the public at large and to 1517 | the detriment of our heirs and successors. We intend this dedication to be an 1518 | overt act of relinquishment in perpetuity of all present and future rights to 1519 | this software under copyright law. 1520 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1521 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1522 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1523 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1524 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1525 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1526 | ------------------------------------------------------------------------------ 1527 | */ 1528 | --------------------------------------------------------------------------------