├── .gitignore ├── Dockerfile ├── README.MD ├── SConstruct ├── external ├── imconfig.h ├── imgui.cpp ├── imgui.h ├── imgui_demo.cpp ├── imgui_draw.cpp ├── imgui_internal.h ├── imgui_widgets.cpp ├── imstb_rectpack.h ├── imstb_textedit.h └── imstb_truetype.h ├── out └── .gitignore ├── shell.html └── src ├── draw.cpp ├── draw.h ├── em.cpp ├── funimgui.cpp ├── funimgui.h ├── main.cpp └── main.h /.gitignore: -------------------------------------------------------------------------------- 1 | .sconsign.dblite 2 | *.swp 3 | custom.py 4 | index.html.map 5 | index.html 6 | index.js 7 | index.wasm 8 | index.wast 9 | *.o 10 | *.a 11 | *.pyc 12 | *.map 13 | node_modules 14 | .DS_Store 15 | 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch 2 | RUN apt update 3 | RUN apt install -y build-essential cmake curl git openjdk-8-jre python scons 4 | 5 | RUN git clone https://github.com/emscripten-core/emsdk.git /emsdk 6 | 7 | #RUN cd /emsdk && git pull 8 | RUN cd /emsdk && ./emsdk install sdk-1.39.3-64bit 9 | RUN cd /emsdk && ./emsdk activate sdk-1.39.3-64bit 10 | #RUN ln -sf /bin/bash /bin/sh 11 | RUN /bin/bash -c "cd /emsdk && source ./emsdk_env.sh" 12 | RUN echo 'source /emsdk/emsdk_env.sh' >> /etc/bash.bashrc 13 | RUN mkdir /build 14 | WORKDIR /build 15 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 2 | How To Build 3 | ============ 4 | 5 | docker build . -t emenv 6 | docker run -it -v $PWD:/build emenv 7 | scons 8 | 9 | Your build artifacts will now be in `out/` 10 | 11 | Combine the `shell.html` from the project root with the build artifacts to see the result. 12 | You can just copy `shell.html` into the `out/` folder and open shell.html in a browser window. 13 | Firefox will open the app as-is form a file path. Chrome and Safari both require a webserver. 14 | Try [Caddy](https://caddyserver.com/) for a fast in place webserver. 15 | 16 | 17 | Debugging 18 | ========= 19 | 20 | * Docker is not part of my debugging workflow. It's there to make builds reproduceable and documented 21 | * Install and activate the [emsdk](https://emscripten.org/docs/getting_started/downloads.html). Add the appropriate emsdk_env file for your platform. 22 | * Copy shell.html to index.html in the project root. Don't use the `out/` folder here. 23 | * run scons as `scons RELEASE=0 EMSDK=/path/to/your/emsdk OUTDIR=$PWD` 24 | * We are not using the `out/` folder. This will help make sourcemaps line up. 25 | * The params to scons are sticky. You can just run `scons` going forward. 26 | * run [Caddy](http://caddyserver.com/) in the project root. 27 | * We are not using the `out/` folder. This is so that sourcemaps line up correctly. 28 | * Go to the caddy webserver in your browser, by default it's at [http://localhost:2015](http://localhost:2015) 29 | * Open the Developer Tools for your browser. 30 | * The *Console* tab is helpful for finding things that have gone wrong. 31 | * Under the *Sources* tab, you can navigate cpp files, and place breakpoints. 32 | * Debugging appears to work fine. 33 | * Chrome appears to have a slightly better debugger as far as being able to click on callstacks and jump to code. 34 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import os 2 | vars = Variables('custom.py') 3 | vars.Add(BoolVariable('RELEASE','set to 1 for ', 1)) 4 | vars.Add(PathVariable('EMSDK', "path to emsdk dir", os.environ.get('EMSDK'))) 5 | vars.Add(PathVariable('OUTDIR', "path to output dir", 'out')) 6 | env = Environment(variables = vars) 7 | vars.Save('custom.py', env) 8 | 9 | 10 | #env.Append( CPPPATH=['src', 'external'] ) 11 | if env['RELEASE']: 12 | flags = '-s WASM=1 -Oz' 13 | linkerflags = '' 14 | else: 15 | flags = '-O0 -s WASM=1 -g4 -s ASSERTIONS=2 -s SAFE_HEAP=1 -s DEMANGLE_SUPPORT=1 --source-map-base /' 16 | linkerflags = '' 17 | 18 | env.StaticLibrary('imgui',source=[ 19 | 'external/imgui.cpp', 20 | 'external/imgui_demo.cpp', 21 | 'external/imgui_draw.cpp', 22 | 'external/imgui_widgets.cpp' 23 | ]) 24 | env.StaticLibrary('core', source=[ 25 | 'src/draw.cpp', 26 | 'src/funimgui.cpp', 27 | 'src/main.cpp', 28 | ], CPPPATH=['external']) 29 | index = env.Program(target='{}/index.js'.format(env['OUTDIR']), source=[ 30 | 'src/em.cpp' 31 | ], LIBS=['core', 'imgui'], LIBPATH='.') 32 | Default(index) 33 | 34 | env['CC'] = 'emcc' 35 | env['CXX'] = 'em++' 36 | env['AR'] = 'emar' 37 | env['RANLIB'] = 'emranlib' 38 | env['CXXFLAGS'] = '-std=c++14 ' + flags 39 | env['LINKFLAGS'] = flags + " --bind " + linkerflags 40 | env['ENV']['PATH'] += ':{}'.format(env['EMSDK']) 41 | env['ENV']['PATH'] += ':{}/upstream/emscripten'.format(env['EMSDK']) 42 | 43 | env['ENV']['EMSDK'] = env['EMSDK'] 44 | -------------------------------------------------------------------------------- /external/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 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/branch with your modifications to imconfig.h) 7 | // B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" 8 | // If you do so you need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include 9 | // the imgui*.cpp files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. 10 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 11 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 12 | //----------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | //---- Define assertion handler. Defaults to calling assert(). 17 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 18 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 19 | 20 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows 21 | // 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. 22 | //#define IMGUI_API __declspec( dllexport ) 23 | //#define IMGUI_API __declspec( dllimport ) 24 | 25 | //---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 26 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 27 | 28 | //---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) 29 | // It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in imgui_demo.cpp. 30 | //#define IMGUI_DISABLE_DEMO_WINDOWS 31 | //#define IMGUI_DISABLE_METRICS_WINDOW 32 | 33 | //---- Don't implement some functions to reduce linkage requirements. 34 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 35 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 36 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). 37 | //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices'). 38 | //#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself if you don't want to link with vsnprintf. 39 | //#define IMGUI_DISABLE_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare your prototypes in imconfig.h. 40 | //#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(). 41 | 42 | //---- Include imgui_user.h at the end of imgui.h as a convenience 43 | //#define IMGUI_INCLUDE_IMGUI_USER_H 44 | 45 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 46 | //#define IMGUI_USE_BGRA_PACKED_COLOR 47 | 48 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 49 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 50 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 51 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 52 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 53 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 54 | 55 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 56 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 57 | /* 58 | #define IM_VEC2_CLASS_EXTRA \ 59 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 60 | operator MyVec2() const { return MyVec2(x,y); } 61 | 62 | #define IM_VEC4_CLASS_EXTRA \ 63 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 64 | operator MyVec4() const { return MyVec4(x,y,z,w); } 65 | */ 66 | 67 | //---- Using 32-bits vertex indices (default is 16-bits) is one way to allow large meshes with more than 64K vertices. 68 | // Your renderer back-end will need to support it (most example renderer back-ends support both 16/32-bits indices). 69 | // Another way to allow large meshes while keeping 16-bits indices is to handle ImDrawCmd::VtxOffset in your renderer. 70 | // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. 71 | //#define ImDrawIdx unsigned int 72 | 73 | //---- Override ImDrawCallback signature (will need to modify renderer back-ends accordingly) 74 | //struct ImDrawList; 75 | //struct ImDrawCmd; 76 | //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); 77 | //#define ImDrawCallback MyImDrawCallback 78 | 79 | //---- Debug Tools 80 | // Use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging. 81 | //#define IM_DEBUG_BREAK IM_ASSERT(0) 82 | //#define IM_DEBUG_BREAK __debugbreak() 83 | // Have the Item Picker break in the ItemAdd() function instead of ItemHoverable() - which is earlier in the code, will catch a few extra items, allow picking items other than Hovered one. 84 | // This adds a small runtime cost which is why it is not enabled by default. 85 | //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX 86 | 87 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 88 | /* 89 | namespace ImGui 90 | { 91 | void MyFunction(const char* name, const MyMatrix44& v); 92 | } 93 | */ 94 | -------------------------------------------------------------------------------- /external/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_rect_pack.h 1.00. 3 | // Those changes would need to be pushed into nothings/stb: 4 | // - Added STBRP__CDECL 5 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_rect_pack.h - v1.00 - public domain - rectangle packing 8 | // Sean Barrett 2014 9 | // 10 | // Useful for e.g. packing rectangular textures into an atlas. 11 | // Does not do rotation. 12 | // 13 | // Not necessarily the awesomest packing method, but better than 14 | // the totally naive one in stb_truetype (which is primarily what 15 | // this is meant to replace). 16 | // 17 | // Has only had a few tests run, may have issues. 18 | // 19 | // More docs to come. 20 | // 21 | // No memory allocations; uses qsort() and assert() from stdlib. 22 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 23 | // 24 | // This library currently uses the Skyline Bottom-Left algorithm. 25 | // 26 | // Please note: better rectangle packers are welcome! Please 27 | // implement them to the same API, but with a different init 28 | // function. 29 | // 30 | // Credits 31 | // 32 | // Library 33 | // Sean Barrett 34 | // Minor features 35 | // Martins Mozeiko 36 | // github:IntellectualKitty 37 | // 38 | // Bugfixes / warning fixes 39 | // Jeremy Jaussaud 40 | // Fabian Giesen 41 | // 42 | // Version history: 43 | // 44 | // 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles 45 | // 0.99 (2019-02-07) warning fixes 46 | // 0.11 (2017-03-03) return packing success/fail result 47 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 48 | // 0.09 (2016-08-27) fix compiler warnings 49 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 50 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 51 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 52 | // 0.05: added STBRP_ASSERT to allow replacing assert 53 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 54 | // 0.01: initial release 55 | // 56 | // LICENSE 57 | // 58 | // See end of file for license information. 59 | 60 | ////////////////////////////////////////////////////////////////////////////// 61 | // 62 | // INCLUDE SECTION 63 | // 64 | 65 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 66 | #define STB_INCLUDE_STB_RECT_PACK_H 67 | 68 | #define STB_RECT_PACK_VERSION 1 69 | 70 | #ifdef STBRP_STATIC 71 | #define STBRP_DEF static 72 | #else 73 | #define STBRP_DEF extern 74 | #endif 75 | 76 | #ifdef __cplusplus 77 | extern "C" { 78 | #endif 79 | 80 | typedef struct stbrp_context stbrp_context; 81 | typedef struct stbrp_node stbrp_node; 82 | typedef struct stbrp_rect stbrp_rect; 83 | 84 | #ifdef STBRP_LARGE_RECTS 85 | typedef int stbrp_coord; 86 | #else 87 | typedef unsigned short stbrp_coord; 88 | #endif 89 | 90 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 91 | // Assign packed locations to rectangles. The rectangles are of type 92 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 93 | // are 'num_rects' many of them. 94 | // 95 | // Rectangles which are successfully packed have the 'was_packed' flag 96 | // set to a non-zero value and 'x' and 'y' store the minimum location 97 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 98 | // if you imagine y increasing downwards). Rectangles which do not fit 99 | // have the 'was_packed' flag set to 0. 100 | // 101 | // You should not try to access the 'rects' array from another thread 102 | // while this function is running, as the function temporarily reorders 103 | // the array while it executes. 104 | // 105 | // To pack into another rectangle, you need to call stbrp_init_target 106 | // again. To continue packing into the same rectangle, you can call 107 | // this function again. Calling this multiple times with multiple rect 108 | // arrays will probably produce worse packing results than calling it 109 | // a single time with the full rectangle array, but the option is 110 | // available. 111 | // 112 | // The function returns 1 if all of the rectangles were successfully 113 | // packed and 0 otherwise. 114 | 115 | struct stbrp_rect 116 | { 117 | // reserved for your use: 118 | int id; 119 | 120 | // input: 121 | stbrp_coord w, h; 122 | 123 | // output: 124 | stbrp_coord x, y; 125 | int was_packed; // non-zero if valid packing 126 | 127 | }; // 16 bytes, nominally 128 | 129 | 130 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 131 | // Initialize a rectangle packer to: 132 | // pack a rectangle that is 'width' by 'height' in dimensions 133 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 134 | // 135 | // You must call this function every time you start packing into a new target. 136 | // 137 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 138 | // the following stbrp_pack_rects() call (or calls), but can be freed after 139 | // the call (or calls) finish. 140 | // 141 | // Note: to guarantee best results, either: 142 | // 1. make sure 'num_nodes' >= 'width' 143 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 144 | // 145 | // If you don't do either of the above things, widths will be quantized to multiples 146 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 147 | // 148 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 149 | // may run out of temporary storage and be unable to pack some rectangles. 150 | 151 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 152 | // Optionally call this function after init but before doing any packing to 153 | // change the handling of the out-of-temp-memory scenario, described above. 154 | // If you call init again, this will be reset to the default (false). 155 | 156 | 157 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 158 | // Optionally select which packing heuristic the library should use. Different 159 | // heuristics will produce better/worse results for different data sets. 160 | // If you call init again, this will be reset to the default. 161 | 162 | enum 163 | { 164 | STBRP_HEURISTIC_Skyline_default=0, 165 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 166 | STBRP_HEURISTIC_Skyline_BF_sortHeight 167 | }; 168 | 169 | 170 | ////////////////////////////////////////////////////////////////////////////// 171 | // 172 | // the details of the following structures don't matter to you, but they must 173 | // be visible so you can handle the memory allocations for them 174 | 175 | struct stbrp_node 176 | { 177 | stbrp_coord x,y; 178 | stbrp_node *next; 179 | }; 180 | 181 | struct stbrp_context 182 | { 183 | int width; 184 | int height; 185 | int align; 186 | int init_mode; 187 | int heuristic; 188 | int num_nodes; 189 | stbrp_node *active_head; 190 | stbrp_node *free_head; 191 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 192 | }; 193 | 194 | #ifdef __cplusplus 195 | } 196 | #endif 197 | 198 | #endif 199 | 200 | ////////////////////////////////////////////////////////////////////////////// 201 | // 202 | // IMPLEMENTATION SECTION 203 | // 204 | 205 | #ifdef STB_RECT_PACK_IMPLEMENTATION 206 | #ifndef STBRP_SORT 207 | #include 208 | #define STBRP_SORT qsort 209 | #endif 210 | 211 | #ifndef STBRP_ASSERT 212 | #include 213 | #define STBRP_ASSERT assert 214 | #endif 215 | 216 | // [DEAR IMGUI] Added STBRP__CDECL 217 | #ifdef _MSC_VER 218 | #define STBRP__NOTUSED(v) (void)(v) 219 | #define STBRP__CDECL __cdecl 220 | #else 221 | #define STBRP__NOTUSED(v) (void)sizeof(v) 222 | #define STBRP__CDECL 223 | #endif 224 | 225 | enum 226 | { 227 | STBRP__INIT_skyline = 1 228 | }; 229 | 230 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 231 | { 232 | switch (context->init_mode) { 233 | case STBRP__INIT_skyline: 234 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 235 | context->heuristic = heuristic; 236 | break; 237 | default: 238 | STBRP_ASSERT(0); 239 | } 240 | } 241 | 242 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 243 | { 244 | if (allow_out_of_mem) 245 | // if it's ok to run out of memory, then don't bother aligning them; 246 | // this gives better packing, but may fail due to OOM (even though 247 | // the rectangles easily fit). @TODO a smarter approach would be to only 248 | // quantize once we've hit OOM, then we could get rid of this parameter. 249 | context->align = 1; 250 | else { 251 | // if it's not ok to run out of memory, then quantize the widths 252 | // so that num_nodes is always enough nodes. 253 | // 254 | // I.e. num_nodes * align >= width 255 | // align >= width / num_nodes 256 | // align = ceil(width/num_nodes) 257 | 258 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 259 | } 260 | } 261 | 262 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 263 | { 264 | int i; 265 | #ifndef STBRP_LARGE_RECTS 266 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 267 | #endif 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 | #ifdef STBRP_LARGE_RECTS 287 | context->extra[1].y = (1<<30); 288 | #else 289 | context->extra[1].y = 65535; 290 | #endif 291 | context->extra[1].next = NULL; 292 | } 293 | 294 | // find minimum y position if it starts at x1 295 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 296 | { 297 | stbrp_node *node = first; 298 | int x1 = x0 + width; 299 | int min_y, visited_width, waste_area; 300 | 301 | STBRP__NOTUSED(c); 302 | 303 | STBRP_ASSERT(first->x <= x0); 304 | 305 | #if 0 306 | // skip in case we're past the node 307 | while (node->next->x <= x0) 308 | ++node; 309 | #else 310 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 311 | #endif 312 | 313 | STBRP_ASSERT(node->x <= x0); 314 | 315 | min_y = 0; 316 | waste_area = 0; 317 | visited_width = 0; 318 | while (node->x < x1) { 319 | if (node->y > min_y) { 320 | // raise min_y higher. 321 | // we've accounted for all waste up to min_y, 322 | // but we'll now add more waste for everything we've visted 323 | waste_area += visited_width * (node->y - min_y); 324 | min_y = node->y; 325 | // the first time through, visited_width might be reduced 326 | if (node->x < x0) 327 | visited_width += node->next->x - x0; 328 | else 329 | visited_width += node->next->x - node->x; 330 | } else { 331 | // add waste area 332 | int under_width = node->next->x - node->x; 333 | if (under_width + visited_width > width) 334 | under_width = width - visited_width; 335 | waste_area += under_width * (min_y - node->y); 336 | visited_width += under_width; 337 | } 338 | node = node->next; 339 | } 340 | 341 | *pwaste = waste_area; 342 | return min_y; 343 | } 344 | 345 | typedef struct 346 | { 347 | int x,y; 348 | stbrp_node **prev_link; 349 | } stbrp__findresult; 350 | 351 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 352 | { 353 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 354 | stbrp__findresult fr; 355 | stbrp_node **prev, *node, *tail, **best = NULL; 356 | 357 | // align to multiple of c->align 358 | width = (width + c->align - 1); 359 | width -= width % c->align; 360 | STBRP_ASSERT(width % c->align == 0); 361 | 362 | // if it can't possibly fit, bail immediately 363 | if (width > c->width || height > c->height) { 364 | fr.prev_link = NULL; 365 | fr.x = fr.y = 0; 366 | return fr; 367 | } 368 | 369 | node = c->active_head; 370 | prev = &c->active_head; 371 | while (node->x + width <= c->width) { 372 | int y,waste; 373 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 374 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 375 | // bottom left 376 | if (y < best_y) { 377 | best_y = y; 378 | best = prev; 379 | } 380 | } else { 381 | // best-fit 382 | if (y + height <= c->height) { 383 | // can only use it if it first vertically 384 | if (y < best_y || (y == best_y && waste < best_waste)) { 385 | best_y = y; 386 | best_waste = waste; 387 | best = prev; 388 | } 389 | } 390 | } 391 | prev = &node->next; 392 | node = node->next; 393 | } 394 | 395 | best_x = (best == NULL) ? 0 : (*best)->x; 396 | 397 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 398 | // 399 | // e.g, if fitting 400 | // 401 | // ____________________ 402 | // |____________________| 403 | // 404 | // into 405 | // 406 | // | | 407 | // | ____________| 408 | // |____________| 409 | // 410 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 411 | // 412 | // This makes BF take about 2x the time 413 | 414 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 415 | tail = c->active_head; 416 | node = c->active_head; 417 | prev = &c->active_head; 418 | // find first node that's admissible 419 | while (tail->x < width) 420 | tail = tail->next; 421 | while (tail) { 422 | int xpos = tail->x - width; 423 | int y,waste; 424 | STBRP_ASSERT(xpos >= 0); 425 | // find the left position that matches this 426 | while (node->next->x <= xpos) { 427 | prev = &node->next; 428 | node = node->next; 429 | } 430 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 431 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 432 | if (y + height <= c->height) { 433 | if (y <= best_y) { 434 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 435 | best_x = xpos; 436 | STBRP_ASSERT(y <= best_y); 437 | best_y = y; 438 | best_waste = waste; 439 | best = prev; 440 | } 441 | } 442 | } 443 | tail = tail->next; 444 | } 445 | } 446 | 447 | fr.prev_link = best; 448 | fr.x = best_x; 449 | fr.y = best_y; 450 | return fr; 451 | } 452 | 453 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 454 | { 455 | // find best position according to heuristic 456 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 457 | stbrp_node *node, *cur; 458 | 459 | // bail if: 460 | // 1. it failed 461 | // 2. the best node doesn't fit (we don't always check this) 462 | // 3. we're out of memory 463 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 464 | res.prev_link = NULL; 465 | return res; 466 | } 467 | 468 | // on success, create new node 469 | node = context->free_head; 470 | node->x = (stbrp_coord) res.x; 471 | node->y = (stbrp_coord) (res.y + height); 472 | 473 | context->free_head = node->next; 474 | 475 | // insert the new node into the right starting point, and 476 | // let 'cur' point to the remaining nodes needing to be 477 | // stiched back in 478 | 479 | cur = *res.prev_link; 480 | if (cur->x < res.x) { 481 | // preserve the existing one, so start testing with the next one 482 | stbrp_node *next = cur->next; 483 | cur->next = node; 484 | cur = next; 485 | } else { 486 | *res.prev_link = node; 487 | } 488 | 489 | // from here, traverse cur and free the nodes, until we get to one 490 | // that shouldn't be freed 491 | while (cur->next && cur->next->x <= res.x + width) { 492 | stbrp_node *next = cur->next; 493 | // move the current node to the free list 494 | cur->next = context->free_head; 495 | context->free_head = cur; 496 | cur = next; 497 | } 498 | 499 | // stitch the list back in 500 | node->next = cur; 501 | 502 | if (cur->x < res.x + width) 503 | cur->x = (stbrp_coord) (res.x + width); 504 | 505 | #ifdef _DEBUG 506 | cur = context->active_head; 507 | while (cur->x < context->width) { 508 | STBRP_ASSERT(cur->x < cur->next->x); 509 | cur = cur->next; 510 | } 511 | STBRP_ASSERT(cur->next == NULL); 512 | 513 | { 514 | int count=0; 515 | cur = context->active_head; 516 | while (cur) { 517 | cur = cur->next; 518 | ++count; 519 | } 520 | cur = context->free_head; 521 | while (cur) { 522 | cur = cur->next; 523 | ++count; 524 | } 525 | STBRP_ASSERT(count == context->num_nodes+2); 526 | } 527 | #endif 528 | 529 | return res; 530 | } 531 | 532 | // [DEAR IMGUI] Added STBRP__CDECL 533 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 534 | { 535 | const stbrp_rect *p = (const stbrp_rect *) a; 536 | const stbrp_rect *q = (const stbrp_rect *) b; 537 | if (p->h > q->h) 538 | return -1; 539 | if (p->h < q->h) 540 | return 1; 541 | return (p->w > q->w) ? -1 : (p->w < q->w); 542 | } 543 | 544 | // [DEAR IMGUI] Added STBRP__CDECL 545 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 546 | { 547 | const stbrp_rect *p = (const stbrp_rect *) a; 548 | const stbrp_rect *q = (const stbrp_rect *) b; 549 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 550 | } 551 | 552 | #ifdef STBRP_LARGE_RECTS 553 | #define STBRP__MAXVAL 0xffffffff 554 | #else 555 | #define STBRP__MAXVAL 0xffff 556 | #endif 557 | 558 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 559 | { 560 | int i, all_rects_packed = 1; 561 | 562 | // we use the 'was_packed' field internally to allow sorting/unsorting 563 | for (i=0; i < num_rects; ++i) { 564 | rects[i].was_packed = i; 565 | } 566 | 567 | // sort according to heuristic 568 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 569 | 570 | for (i=0; i < num_rects; ++i) { 571 | if (rects[i].w == 0 || rects[i].h == 0) { 572 | rects[i].x = rects[i].y = 0; // empty rect needs no space 573 | } else { 574 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 575 | if (fr.prev_link) { 576 | rects[i].x = (stbrp_coord) fr.x; 577 | rects[i].y = (stbrp_coord) fr.y; 578 | } else { 579 | rects[i].x = rects[i].y = STBRP__MAXVAL; 580 | } 581 | } 582 | } 583 | 584 | // unsort 585 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 586 | 587 | // set was_packed flags and all_rects_packed status 588 | for (i=0; i < num_rects; ++i) { 589 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 590 | if (!rects[i].was_packed) 591 | all_rects_packed = 0; 592 | } 593 | 594 | // return the all_rects_packed status 595 | return all_rects_packed; 596 | } 597 | #endif 598 | 599 | /* 600 | ------------------------------------------------------------------------------ 601 | This software is available under 2 licenses -- choose whichever you prefer. 602 | ------------------------------------------------------------------------------ 603 | ALTERNATIVE A - MIT License 604 | Copyright (c) 2017 Sean Barrett 605 | Permission is hereby granted, free of charge, to any person obtaining a copy of 606 | this software and associated documentation files (the "Software"), to deal in 607 | the Software without restriction, including without limitation the rights to 608 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 609 | of the Software, and to permit persons to whom the Software is furnished to do 610 | so, subject to the following conditions: 611 | The above copyright notice and this permission notice shall be included in all 612 | copies or substantial portions of the Software. 613 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 614 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 615 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 616 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 617 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 618 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 619 | SOFTWARE. 620 | ------------------------------------------------------------------------------ 621 | ALTERNATIVE B - Public Domain (www.unlicense.org) 622 | This is free and unencumbered software released into the public domain. 623 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 624 | software, either in source code form or as a compiled binary, for any purpose, 625 | commercial or non-commercial, and by any means. 626 | In jurisdictions that recognize copyright laws, the author or authors of this 627 | software dedicate any and all copyright interest in the software to the public 628 | domain. We make this dedication for the benefit of the public at large and to 629 | the detriment of our heirs and successors. We intend this dedication to be an 630 | overt act of relinquishment in perpetuity of all present and future rights to 631 | this software under copyright law. 632 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 633 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 634 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 635 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 636 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 637 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 638 | ------------------------------------------------------------------------------ 639 | */ 640 | -------------------------------------------------------------------------------- /external/imstb_textedit.h: -------------------------------------------------------------------------------- 1 | // [DEAR IMGUI] 2 | // This is a slightly modified version of stb_textedit.h 1.13. 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 | // Grep for [DEAR IMGUI] to find the changes. 6 | 7 | // stb_textedit.h - v1.13 - public domain - Sean Barrett 8 | // Development of this library was sponsored by RAD Game Tools 9 | // 10 | // This C header file implements the guts of a multi-line text-editing 11 | // widget; you implement display, word-wrapping, and low-level string 12 | // insertion/deletion, and stb_textedit will map user inputs into 13 | // insertions & deletions, plus updates to the cursor position, 14 | // selection state, and undo state. 15 | // 16 | // It is intended for use in games and other systems that need to build 17 | // their own custom widgets and which do not have heavy text-editing 18 | // requirements (this library is not recommended for use for editing large 19 | // texts, as its performance does not scale and it has limited undo). 20 | // 21 | // Non-trivial behaviors are modelled after Windows text controls. 22 | // 23 | // 24 | // LICENSE 25 | // 26 | // See end of file for license information. 27 | // 28 | // 29 | // DEPENDENCIES 30 | // 31 | // Uses the C runtime function 'memmove', which you can override 32 | // by defining STB_TEXTEDIT_memmove before the implementation. 33 | // Uses no other functions. Performs no runtime allocations. 34 | // 35 | // 36 | // VERSION HISTORY 37 | // 38 | // 1.13 (2019-02-07) fix bug in undo size management 39 | // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 40 | // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 41 | // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual 42 | // 1.9 (2016-08-27) customizable move-by-word 43 | // 1.8 (2016-04-02) better keyboard handling when mouse button is down 44 | // 1.7 (2015-09-13) change y range handling in case baseline is non-0 45 | // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 46 | // 1.5 (2014-09-10) add support for secondary keys for OS X 47 | // 1.4 (2014-08-17) fix signed/unsigned warnings 48 | // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 49 | // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 50 | // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 51 | // 1.0 (2012-07-26) improve documentation, initial public release 52 | // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 53 | // 0.2 (2011-11-28) fixes to undo/redo 54 | // 0.1 (2010-07-08) initial version 55 | // 56 | // ADDITIONAL CONTRIBUTORS 57 | // 58 | // Ulf Winklemann: move-by-word in 1.1 59 | // Fabian Giesen: secondary key inputs in 1.5 60 | // Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 61 | // 62 | // Bugfixes: 63 | // Scott Graham 64 | // Daniel Keller 65 | // Omar Cornut 66 | // Dan Thompson 67 | // 68 | // USAGE 69 | // 70 | // This file behaves differently depending on what symbols you define 71 | // before including it. 72 | // 73 | // 74 | // Header-file mode: 75 | // 76 | // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 77 | // it will operate in "header file" mode. In this mode, it declares a 78 | // single public symbol, STB_TexteditState, which encapsulates the current 79 | // state of a text widget (except for the string, which you will store 80 | // separately). 81 | // 82 | // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 83 | // primitive type that defines a single character (e.g. char, wchar_t, etc). 84 | // 85 | // To save space or increase undo-ability, you can optionally define the 86 | // following things that are used by the undo system: 87 | // 88 | // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 89 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 90 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 91 | // 92 | // If you don't define these, they are set to permissive types and 93 | // moderate sizes. The undo system does no memory allocations, so 94 | // it grows STB_TexteditState by the worst-case storage which is (in bytes): 95 | // 96 | // [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT 97 | // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT 98 | // 99 | // 100 | // Implementation mode: 101 | // 102 | // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 103 | // will compile the implementation of the text edit widget, depending 104 | // on a large number of symbols which must be defined before the include. 105 | // 106 | // The implementation is defined only as static functions. You will then 107 | // need to provide your own APIs in the same file which will access the 108 | // static functions. 109 | // 110 | // The basic concept is that you provide a "string" object which 111 | // behaves like an array of characters. stb_textedit uses indices to 112 | // refer to positions in the string, implicitly representing positions 113 | // in the displayed textedit. This is true for both plain text and 114 | // rich text; even with rich text stb_truetype interacts with your 115 | // code as if there was an array of all the displayed characters. 116 | // 117 | // Symbols that must be the same in header-file and implementation mode: 118 | // 119 | // STB_TEXTEDIT_CHARTYPE the character type 120 | // STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 121 | // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 122 | // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 123 | // 124 | // Symbols you must define for implementation mode: 125 | // 126 | // STB_TEXTEDIT_STRING the type of object representing a string being edited, 127 | // typically this is a wrapper object with other data you need 128 | // 129 | // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 130 | // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 131 | // starting from character #n (see discussion below) 132 | // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 133 | // to the xpos of the i+1'th char for a line of characters 134 | // starting at character #n (i.e. accounts for kerning 135 | // with previous char) 136 | // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 137 | // (return type is int, -1 means not valid to insert) 138 | // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 139 | // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 140 | // as manually wordwrapping for end-of-line positioning 141 | // 142 | // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 143 | // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 144 | // 145 | // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 146 | // 147 | // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 148 | // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 149 | // STB_TEXTEDIT_K_UP keyboard input to move cursor up 150 | // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 151 | // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 152 | // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 153 | // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 154 | // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 155 | // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 156 | // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 157 | // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 158 | // STB_TEXTEDIT_K_REDO keyboard input to perform redo 159 | // 160 | // Optional: 161 | // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 162 | // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 163 | // required for default WORDLEFT/WORDRIGHT handlers 164 | // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 165 | // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 166 | // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 167 | // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 168 | // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 169 | // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 170 | // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 171 | // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 172 | // 173 | // Todo: 174 | // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 175 | // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 176 | // 177 | // Keyboard input must be encoded as a single integer value; e.g. a character code 178 | // and some bitflags that represent shift states. to simplify the interface, SHIFT must 179 | // be a bitflag, so we can test the shifted state of cursor movements to allow selection, 180 | // i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 181 | // 182 | // You can encode other things, such as CONTROL or ALT, in additional bits, and 183 | // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 184 | // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 185 | // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 186 | // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 187 | // API below. The control keys will only match WM_KEYDOWN events because of the 188 | // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 189 | // bit so it only decodes WM_CHAR events. 190 | // 191 | // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 192 | // row of characters assuming they start on the i'th character--the width and 193 | // the height and the number of characters consumed. This allows this library 194 | // to traverse the entire layout incrementally. You need to compute word-wrapping 195 | // here. 196 | // 197 | // Each textfield keeps its own insert mode state, which is not how normal 198 | // applications work. To keep an app-wide insert mode, update/copy the 199 | // "insert_mode" field of STB_TexteditState before/after calling API functions. 200 | // 201 | // API 202 | // 203 | // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 204 | // 205 | // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 206 | // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 207 | // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 208 | // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 209 | // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) 210 | // 211 | // Each of these functions potentially updates the string and updates the 212 | // state. 213 | // 214 | // initialize_state: 215 | // set the textedit state to a known good default state when initially 216 | // constructing the textedit. 217 | // 218 | // click: 219 | // call this with the mouse x,y on a mouse down; it will update the cursor 220 | // and reset the selection start/end to the cursor point. the x,y must 221 | // be relative to the text widget, with (0,0) being the top left. 222 | // 223 | // drag: 224 | // call this with the mouse x,y on a mouse drag/up; it will update the 225 | // cursor and the selection end point 226 | // 227 | // cut: 228 | // call this to delete the current selection; returns true if there was 229 | // one. you should FIRST copy the current selection to the system paste buffer. 230 | // (To copy, just copy the current selection out of the string yourself.) 231 | // 232 | // paste: 233 | // call this to paste text at the current cursor point or over the current 234 | // selection if there is one. 235 | // 236 | // key: 237 | // call this for keyboard inputs sent to the textfield. you can use it 238 | // for "key down" events or for "translated" key events. if you need to 239 | // do both (as in Win32), or distinguish Unicode characters from control 240 | // inputs, set a high bit to distinguish the two; then you can define the 241 | // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 242 | // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 243 | // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to 244 | // anything other type you wante before including. 245 | // 246 | // 247 | // When rendering, you can read the cursor position and selection state from 248 | // the STB_TexteditState. 249 | // 250 | // 251 | // Notes: 252 | // 253 | // This is designed to be usable in IMGUI, so it allows for the possibility of 254 | // running in an IMGUI that has NOT cached the multi-line layout. For this 255 | // reason, it provides an interface that is compatible with computing the 256 | // layout incrementally--we try to make sure we make as few passes through 257 | // as possible. (For example, to locate the mouse pointer in the text, we 258 | // could define functions that return the X and Y positions of characters 259 | // and binary search Y and then X, but if we're doing dynamic layout this 260 | // will run the layout algorithm many times, so instead we manually search 261 | // forward in one pass. Similar logic applies to e.g. up-arrow and 262 | // down-arrow movement.) 263 | // 264 | // If it's run in a widget that *has* cached the layout, then this is less 265 | // efficient, but it's not horrible on modern computers. But you wouldn't 266 | // want to edit million-line files with it. 267 | 268 | 269 | //////////////////////////////////////////////////////////////////////////// 270 | //////////////////////////////////////////////////////////////////////////// 271 | //// 272 | //// Header-file mode 273 | //// 274 | //// 275 | 276 | #ifndef INCLUDE_STB_TEXTEDIT_H 277 | #define INCLUDE_STB_TEXTEDIT_H 278 | 279 | //////////////////////////////////////////////////////////////////////// 280 | // 281 | // STB_TexteditState 282 | // 283 | // Definition of STB_TexteditState which you should store 284 | // per-textfield; it includes cursor position, selection state, 285 | // and undo state. 286 | // 287 | 288 | #ifndef STB_TEXTEDIT_UNDOSTATECOUNT 289 | #define STB_TEXTEDIT_UNDOSTATECOUNT 99 290 | #endif 291 | #ifndef STB_TEXTEDIT_UNDOCHARCOUNT 292 | #define STB_TEXTEDIT_UNDOCHARCOUNT 999 293 | #endif 294 | #ifndef STB_TEXTEDIT_CHARTYPE 295 | #define STB_TEXTEDIT_CHARTYPE int 296 | #endif 297 | #ifndef STB_TEXTEDIT_POSITIONTYPE 298 | #define STB_TEXTEDIT_POSITIONTYPE int 299 | #endif 300 | 301 | typedef struct 302 | { 303 | // private data 304 | STB_TEXTEDIT_POSITIONTYPE where; 305 | STB_TEXTEDIT_POSITIONTYPE insert_length; 306 | STB_TEXTEDIT_POSITIONTYPE delete_length; 307 | int char_storage; 308 | } StbUndoRecord; 309 | 310 | typedef struct 311 | { 312 | // private data 313 | StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; 314 | STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; 315 | short undo_point, redo_point; 316 | int undo_char_point, redo_char_point; 317 | } StbUndoState; 318 | 319 | typedef struct 320 | { 321 | ///////////////////// 322 | // 323 | // public data 324 | // 325 | 326 | int cursor; 327 | // position of the text cursor within the string 328 | 329 | int select_start; // selection start point 330 | int select_end; 331 | // selection start and end point in characters; if equal, no selection. 332 | // note that start may be less than or greater than end (e.g. when 333 | // dragging the mouse, start is where the initial click was, and you 334 | // can drag in either direction) 335 | 336 | unsigned char insert_mode; 337 | // each textfield keeps its own insert mode state. to keep an app-wide 338 | // insert mode, copy this value in/out of the app state 339 | 340 | ///////////////////// 341 | // 342 | // private data 343 | // 344 | unsigned char cursor_at_end_of_line; // not implemented yet 345 | unsigned char initialized; 346 | unsigned char has_preferred_x; 347 | unsigned char single_line; 348 | unsigned char padding1, padding2, padding3; 349 | float preferred_x; // this determines where the cursor up/down tries to seek to along x 350 | StbUndoState undostate; 351 | } STB_TexteditState; 352 | 353 | 354 | //////////////////////////////////////////////////////////////////////// 355 | // 356 | // StbTexteditRow 357 | // 358 | // Result of layout query, used by stb_textedit to determine where 359 | // the text in each row is. 360 | 361 | // result of layout query 362 | typedef struct 363 | { 364 | float x0,x1; // starting x location, end x location (allows for align=right, etc) 365 | float baseline_y_delta; // position of baseline relative to previous row's baseline 366 | float ymin,ymax; // height of row above and below baseline 367 | int num_chars; 368 | } StbTexteditRow; 369 | #endif //INCLUDE_STB_TEXTEDIT_H 370 | 371 | 372 | //////////////////////////////////////////////////////////////////////////// 373 | //////////////////////////////////////////////////////////////////////////// 374 | //// 375 | //// Implementation mode 376 | //// 377 | //// 378 | 379 | 380 | // implementation isn't include-guarded, since it might have indirectly 381 | // included just the "header" portion 382 | #ifdef STB_TEXTEDIT_IMPLEMENTATION 383 | 384 | #ifndef STB_TEXTEDIT_memmove 385 | #include 386 | #define STB_TEXTEDIT_memmove memmove 387 | #endif 388 | 389 | 390 | ///////////////////////////////////////////////////////////////////////////// 391 | // 392 | // Mouse input handling 393 | // 394 | 395 | // traverse the layout to locate the nearest character to a display position 396 | static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) 397 | { 398 | StbTexteditRow r; 399 | int n = STB_TEXTEDIT_STRINGLEN(str); 400 | float base_y = 0, prev_x; 401 | int i=0, k; 402 | 403 | r.x0 = r.x1 = 0; 404 | r.ymin = r.ymax = 0; 405 | r.num_chars = 0; 406 | 407 | // search rows to find one that straddles 'y' 408 | while (i < n) { 409 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 410 | if (r.num_chars <= 0) 411 | return n; 412 | 413 | if (i==0 && y < base_y + r.ymin) 414 | return 0; 415 | 416 | if (y < base_y + r.ymax) 417 | break; 418 | 419 | i += r.num_chars; 420 | base_y += r.baseline_y_delta; 421 | } 422 | 423 | // below all text, return 'after' last character 424 | if (i >= n) 425 | return n; 426 | 427 | // check if it's before the beginning of the line 428 | if (x < r.x0) 429 | return i; 430 | 431 | // check if it's before the end of the line 432 | if (x < r.x1) { 433 | // search characters in row for one that straddles 'x' 434 | prev_x = r.x0; 435 | for (k=0; k < r.num_chars; ++k) { 436 | float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 437 | if (x < prev_x+w) { 438 | if (x < prev_x+w/2) 439 | return k+i; 440 | else 441 | return k+i+1; 442 | } 443 | prev_x += w; 444 | } 445 | // shouldn't happen, but if it does, fall through to end-of-line case 446 | } 447 | 448 | // if the last character is a newline, return that. otherwise return 'after' the last character 449 | if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 450 | return i+r.num_chars-1; 451 | else 452 | return i+r.num_chars; 453 | } 454 | 455 | // API click: on mouse down, move the cursor to the clicked location, and reset the selection 456 | static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 457 | { 458 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 459 | // goes off the top or bottom of the text 460 | if( state->single_line ) 461 | { 462 | StbTexteditRow r; 463 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 464 | y = r.ymin; 465 | } 466 | 467 | state->cursor = stb_text_locate_coord(str, x, y); 468 | state->select_start = state->cursor; 469 | state->select_end = state->cursor; 470 | state->has_preferred_x = 0; 471 | } 472 | 473 | // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 474 | static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 475 | { 476 | int p = 0; 477 | 478 | // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 479 | // goes off the top or bottom of the text 480 | if( state->single_line ) 481 | { 482 | StbTexteditRow r; 483 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 484 | y = r.ymin; 485 | } 486 | 487 | if (state->select_start == state->select_end) 488 | state->select_start = state->cursor; 489 | 490 | p = stb_text_locate_coord(str, x, y); 491 | state->cursor = state->select_end = p; 492 | } 493 | 494 | ///////////////////////////////////////////////////////////////////////////// 495 | // 496 | // Keyboard input handling 497 | // 498 | 499 | // forward declarations 500 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 501 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 502 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 503 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 504 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 505 | 506 | typedef struct 507 | { 508 | float x,y; // position of n'th character 509 | float height; // height of line 510 | int first_char, length; // first char of row, and length 511 | int prev_first; // first char of previous row 512 | } StbFindState; 513 | 514 | // find the x/y location of a character, and remember info about the previous row in 515 | // case we get a move-up event (for page up, we'll have to rescan) 516 | static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) 517 | { 518 | StbTexteditRow r; 519 | int prev_start = 0; 520 | int z = STB_TEXTEDIT_STRINGLEN(str); 521 | int i=0, first; 522 | 523 | if (n == z) { 524 | // if it's at the end, then find the last line -- simpler than trying to 525 | // explicitly handle this case in the regular code 526 | if (single_line) { 527 | STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 528 | find->y = 0; 529 | find->first_char = 0; 530 | find->length = z; 531 | find->height = r.ymax - r.ymin; 532 | find->x = r.x1; 533 | } else { 534 | find->y = 0; 535 | find->x = 0; 536 | find->height = 1; 537 | while (i < z) { 538 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 539 | prev_start = i; 540 | i += r.num_chars; 541 | } 542 | find->first_char = i; 543 | find->length = 0; 544 | find->prev_first = prev_start; 545 | } 546 | return; 547 | } 548 | 549 | // search rows to find the one that straddles character n 550 | find->y = 0; 551 | 552 | for(;;) { 553 | STB_TEXTEDIT_LAYOUTROW(&r, str, i); 554 | if (n < i + r.num_chars) 555 | break; 556 | prev_start = i; 557 | i += r.num_chars; 558 | find->y += r.baseline_y_delta; 559 | } 560 | 561 | find->first_char = first = i; 562 | find->length = r.num_chars; 563 | find->height = r.ymax - r.ymin; 564 | find->prev_first = prev_start; 565 | 566 | // now scan to find xpos 567 | find->x = r.x0; 568 | for (i=0; first+i < n; ++i) 569 | find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 570 | } 571 | 572 | #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 573 | 574 | // make the selection/cursor state valid if client altered the string 575 | static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 576 | { 577 | int n = STB_TEXTEDIT_STRINGLEN(str); 578 | if (STB_TEXT_HAS_SELECTION(state)) { 579 | if (state->select_start > n) state->select_start = n; 580 | if (state->select_end > n) state->select_end = n; 581 | // if clamping forced them to be equal, move the cursor to match 582 | if (state->select_start == state->select_end) 583 | state->cursor = state->select_start; 584 | } 585 | if (state->cursor > n) state->cursor = n; 586 | } 587 | 588 | // delete characters while updating undo 589 | static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 590 | { 591 | stb_text_makeundo_delete(str, state, where, len); 592 | STB_TEXTEDIT_DELETECHARS(str, where, len); 593 | state->has_preferred_x = 0; 594 | } 595 | 596 | // delete the section 597 | static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 598 | { 599 | stb_textedit_clamp(str, state); 600 | if (STB_TEXT_HAS_SELECTION(state)) { 601 | if (state->select_start < state->select_end) { 602 | stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 603 | state->select_end = state->cursor = state->select_start; 604 | } else { 605 | stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 606 | state->select_start = state->cursor = state->select_end; 607 | } 608 | state->has_preferred_x = 0; 609 | } 610 | } 611 | 612 | // canoncialize the selection so start <= end 613 | static void stb_textedit_sortselection(STB_TexteditState *state) 614 | { 615 | if (state->select_end < state->select_start) { 616 | int temp = state->select_end; 617 | state->select_end = state->select_start; 618 | state->select_start = temp; 619 | } 620 | } 621 | 622 | // move cursor to first character of selection 623 | static void stb_textedit_move_to_first(STB_TexteditState *state) 624 | { 625 | if (STB_TEXT_HAS_SELECTION(state)) { 626 | stb_textedit_sortselection(state); 627 | state->cursor = state->select_start; 628 | state->select_end = state->select_start; 629 | state->has_preferred_x = 0; 630 | } 631 | } 632 | 633 | // move cursor to last character of selection 634 | static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 635 | { 636 | if (STB_TEXT_HAS_SELECTION(state)) { 637 | stb_textedit_sortselection(state); 638 | stb_textedit_clamp(str, state); 639 | state->cursor = state->select_end; 640 | state->select_start = state->select_end; 641 | state->has_preferred_x = 0; 642 | } 643 | } 644 | 645 | #ifdef STB_TEXTEDIT_IS_SPACE 646 | static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) 647 | { 648 | return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 649 | } 650 | 651 | #ifndef STB_TEXTEDIT_MOVEWORDLEFT 652 | static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) 653 | { 654 | --c; // always move at least one character 655 | while( c >= 0 && !is_word_boundary( str, c ) ) 656 | --c; 657 | 658 | if( c < 0 ) 659 | c = 0; 660 | 661 | return c; 662 | } 663 | #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 664 | #endif 665 | 666 | #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 667 | static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) 668 | { 669 | const int len = STB_TEXTEDIT_STRINGLEN(str); 670 | ++c; // always move at least one character 671 | while( c < len && !is_word_boundary( str, c ) ) 672 | ++c; 673 | 674 | if( c > len ) 675 | c = len; 676 | 677 | return c; 678 | } 679 | #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 680 | #endif 681 | 682 | #endif 683 | 684 | // update selection and cursor to match each other 685 | static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 686 | { 687 | if (!STB_TEXT_HAS_SELECTION(state)) 688 | state->select_start = state->select_end = state->cursor; 689 | else 690 | state->cursor = state->select_end; 691 | } 692 | 693 | // API cut: delete selection 694 | static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 695 | { 696 | if (STB_TEXT_HAS_SELECTION(state)) { 697 | stb_textedit_delete_selection(str,state); // implicitly clamps 698 | state->has_preferred_x = 0; 699 | return 1; 700 | } 701 | return 0; 702 | } 703 | 704 | // API paste: replace existing selection with passed-in text 705 | static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 706 | { 707 | // if there's a selection, the paste should delete it 708 | stb_textedit_clamp(str, state); 709 | stb_textedit_delete_selection(str,state); 710 | // try to insert the characters 711 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { 712 | stb_text_makeundo_insert(state, state->cursor, len); 713 | state->cursor += len; 714 | state->has_preferred_x = 0; 715 | return 1; 716 | } 717 | // remove the undo since we didn't actually insert the characters 718 | if (state->undostate.undo_point) 719 | --state->undostate.undo_point; 720 | return 0; 721 | } 722 | 723 | #ifndef STB_TEXTEDIT_KEYTYPE 724 | #define STB_TEXTEDIT_KEYTYPE int 725 | #endif 726 | 727 | // API key: process a keyboard input 728 | static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 729 | { 730 | retry: 731 | switch (key) { 732 | default: { 733 | int c = STB_TEXTEDIT_KEYTOTEXT(key); 734 | if (c > 0) { 735 | STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; 736 | 737 | // can't add newline in single-line mode 738 | if (c == '\n' && state->single_line) 739 | break; 740 | 741 | if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 742 | stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 743 | STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 744 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 745 | ++state->cursor; 746 | state->has_preferred_x = 0; 747 | } 748 | } else { 749 | stb_textedit_delete_selection(str,state); // implicitly clamps 750 | if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 751 | stb_text_makeundo_insert(state, state->cursor, 1); 752 | ++state->cursor; 753 | state->has_preferred_x = 0; 754 | } 755 | } 756 | } 757 | break; 758 | } 759 | 760 | #ifdef STB_TEXTEDIT_K_INSERT 761 | case STB_TEXTEDIT_K_INSERT: 762 | state->insert_mode = !state->insert_mode; 763 | break; 764 | #endif 765 | 766 | case STB_TEXTEDIT_K_UNDO: 767 | stb_text_undo(str, state); 768 | state->has_preferred_x = 0; 769 | break; 770 | 771 | case STB_TEXTEDIT_K_REDO: 772 | stb_text_redo(str, state); 773 | state->has_preferred_x = 0; 774 | break; 775 | 776 | case STB_TEXTEDIT_K_LEFT: 777 | // if currently there's a selection, move cursor to start of selection 778 | if (STB_TEXT_HAS_SELECTION(state)) 779 | stb_textedit_move_to_first(state); 780 | else 781 | if (state->cursor > 0) 782 | --state->cursor; 783 | state->has_preferred_x = 0; 784 | break; 785 | 786 | case STB_TEXTEDIT_K_RIGHT: 787 | // if currently there's a selection, move cursor to end of selection 788 | if (STB_TEXT_HAS_SELECTION(state)) 789 | stb_textedit_move_to_last(str, state); 790 | else 791 | ++state->cursor; 792 | stb_textedit_clamp(str, state); 793 | state->has_preferred_x = 0; 794 | break; 795 | 796 | case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 797 | stb_textedit_clamp(str, state); 798 | stb_textedit_prep_selection_at_cursor(state); 799 | // move selection left 800 | if (state->select_end > 0) 801 | --state->select_end; 802 | state->cursor = state->select_end; 803 | state->has_preferred_x = 0; 804 | break; 805 | 806 | #ifdef STB_TEXTEDIT_MOVEWORDLEFT 807 | case STB_TEXTEDIT_K_WORDLEFT: 808 | if (STB_TEXT_HAS_SELECTION(state)) 809 | stb_textedit_move_to_first(state); 810 | else { 811 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 812 | stb_textedit_clamp( str, state ); 813 | } 814 | break; 815 | 816 | case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 817 | if( !STB_TEXT_HAS_SELECTION( state ) ) 818 | stb_textedit_prep_selection_at_cursor(state); 819 | 820 | state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 821 | state->select_end = state->cursor; 822 | 823 | stb_textedit_clamp( str, state ); 824 | break; 825 | #endif 826 | 827 | #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 828 | case STB_TEXTEDIT_K_WORDRIGHT: 829 | if (STB_TEXT_HAS_SELECTION(state)) 830 | stb_textedit_move_to_last(str, state); 831 | else { 832 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 833 | stb_textedit_clamp( str, state ); 834 | } 835 | break; 836 | 837 | case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 838 | if( !STB_TEXT_HAS_SELECTION( state ) ) 839 | stb_textedit_prep_selection_at_cursor(state); 840 | 841 | state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 842 | state->select_end = state->cursor; 843 | 844 | stb_textedit_clamp( str, state ); 845 | break; 846 | #endif 847 | 848 | case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 849 | stb_textedit_prep_selection_at_cursor(state); 850 | // move selection right 851 | ++state->select_end; 852 | stb_textedit_clamp(str, state); 853 | state->cursor = state->select_end; 854 | state->has_preferred_x = 0; 855 | break; 856 | 857 | case STB_TEXTEDIT_K_DOWN: 858 | case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { 859 | StbFindState find; 860 | StbTexteditRow row; 861 | int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 862 | 863 | if (state->single_line) { 864 | // on windows, up&down in single-line behave like left&right 865 | key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 866 | goto retry; 867 | } 868 | 869 | if (sel) 870 | stb_textedit_prep_selection_at_cursor(state); 871 | else if (STB_TEXT_HAS_SELECTION(state)) 872 | stb_textedit_move_to_last(str,state); 873 | 874 | // compute current position of cursor point 875 | stb_textedit_clamp(str, state); 876 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 877 | 878 | // now find character position down a row 879 | if (find.length) { 880 | float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 881 | float x; 882 | int start = find.first_char + find.length; 883 | state->cursor = start; 884 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 885 | x = row.x0; 886 | for (i=0; i < row.num_chars; ++i) { 887 | float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 888 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 889 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 890 | break; 891 | #endif 892 | x += dx; 893 | if (x > goal_x) 894 | break; 895 | ++state->cursor; 896 | } 897 | stb_textedit_clamp(str, state); 898 | 899 | state->has_preferred_x = 1; 900 | state->preferred_x = goal_x; 901 | 902 | if (sel) 903 | state->select_end = state->cursor; 904 | } 905 | break; 906 | } 907 | 908 | case STB_TEXTEDIT_K_UP: 909 | case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { 910 | StbFindState find; 911 | StbTexteditRow row; 912 | int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 913 | 914 | if (state->single_line) { 915 | // on windows, up&down become left&right 916 | key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 917 | goto retry; 918 | } 919 | 920 | if (sel) 921 | stb_textedit_prep_selection_at_cursor(state); 922 | else if (STB_TEXT_HAS_SELECTION(state)) 923 | stb_textedit_move_to_first(state); 924 | 925 | // compute current position of cursor point 926 | stb_textedit_clamp(str, state); 927 | stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 928 | 929 | // can only go up if there's a previous row 930 | if (find.prev_first != find.first_char) { 931 | // now find character position up a row 932 | float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 933 | float x; 934 | state->cursor = find.prev_first; 935 | STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 936 | x = row.x0; 937 | for (i=0; i < row.num_chars; ++i) { 938 | float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 939 | #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 940 | if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 941 | break; 942 | #endif 943 | x += dx; 944 | if (x > goal_x) 945 | break; 946 | ++state->cursor; 947 | } 948 | stb_textedit_clamp(str, state); 949 | 950 | state->has_preferred_x = 1; 951 | state->preferred_x = goal_x; 952 | 953 | if (sel) 954 | state->select_end = state->cursor; 955 | } 956 | break; 957 | } 958 | 959 | case STB_TEXTEDIT_K_DELETE: 960 | case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 961 | if (STB_TEXT_HAS_SELECTION(state)) 962 | stb_textedit_delete_selection(str, state); 963 | else { 964 | int n = STB_TEXTEDIT_STRINGLEN(str); 965 | if (state->cursor < n) 966 | stb_textedit_delete(str, state, state->cursor, 1); 967 | } 968 | state->has_preferred_x = 0; 969 | break; 970 | 971 | case STB_TEXTEDIT_K_BACKSPACE: 972 | case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 973 | if (STB_TEXT_HAS_SELECTION(state)) 974 | stb_textedit_delete_selection(str, state); 975 | else { 976 | stb_textedit_clamp(str, state); 977 | if (state->cursor > 0) { 978 | stb_textedit_delete(str, state, state->cursor-1, 1); 979 | --state->cursor; 980 | } 981 | } 982 | state->has_preferred_x = 0; 983 | break; 984 | 985 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 986 | case STB_TEXTEDIT_K_TEXTSTART2: 987 | #endif 988 | case STB_TEXTEDIT_K_TEXTSTART: 989 | state->cursor = state->select_start = state->select_end = 0; 990 | state->has_preferred_x = 0; 991 | break; 992 | 993 | #ifdef STB_TEXTEDIT_K_TEXTEND2 994 | case STB_TEXTEDIT_K_TEXTEND2: 995 | #endif 996 | case STB_TEXTEDIT_K_TEXTEND: 997 | state->cursor = STB_TEXTEDIT_STRINGLEN(str); 998 | state->select_start = state->select_end = 0; 999 | state->has_preferred_x = 0; 1000 | break; 1001 | 1002 | #ifdef STB_TEXTEDIT_K_TEXTSTART2 1003 | case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 1004 | #endif 1005 | case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 1006 | stb_textedit_prep_selection_at_cursor(state); 1007 | state->cursor = state->select_end = 0; 1008 | state->has_preferred_x = 0; 1009 | break; 1010 | 1011 | #ifdef STB_TEXTEDIT_K_TEXTEND2 1012 | case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 1013 | #endif 1014 | case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 1015 | stb_textedit_prep_selection_at_cursor(state); 1016 | state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 1017 | state->has_preferred_x = 0; 1018 | break; 1019 | 1020 | 1021 | #ifdef STB_TEXTEDIT_K_LINESTART2 1022 | case STB_TEXTEDIT_K_LINESTART2: 1023 | #endif 1024 | case STB_TEXTEDIT_K_LINESTART: 1025 | stb_textedit_clamp(str, state); 1026 | stb_textedit_move_to_first(state); 1027 | if (state->single_line) 1028 | state->cursor = 0; 1029 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1030 | --state->cursor; 1031 | state->has_preferred_x = 0; 1032 | break; 1033 | 1034 | #ifdef STB_TEXTEDIT_K_LINEEND2 1035 | case STB_TEXTEDIT_K_LINEEND2: 1036 | #endif 1037 | case STB_TEXTEDIT_K_LINEEND: { 1038 | int n = STB_TEXTEDIT_STRINGLEN(str); 1039 | stb_textedit_clamp(str, state); 1040 | stb_textedit_move_to_first(state); 1041 | if (state->single_line) 1042 | state->cursor = n; 1043 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1044 | ++state->cursor; 1045 | state->has_preferred_x = 0; 1046 | break; 1047 | } 1048 | 1049 | #ifdef STB_TEXTEDIT_K_LINESTART2 1050 | case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1051 | #endif 1052 | case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1053 | stb_textedit_clamp(str, state); 1054 | stb_textedit_prep_selection_at_cursor(state); 1055 | if (state->single_line) 1056 | state->cursor = 0; 1057 | else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1058 | --state->cursor; 1059 | state->select_end = state->cursor; 1060 | state->has_preferred_x = 0; 1061 | break; 1062 | 1063 | #ifdef STB_TEXTEDIT_K_LINEEND2 1064 | case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1065 | #endif 1066 | case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1067 | int n = STB_TEXTEDIT_STRINGLEN(str); 1068 | stb_textedit_clamp(str, state); 1069 | stb_textedit_prep_selection_at_cursor(state); 1070 | if (state->single_line) 1071 | state->cursor = n; 1072 | else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1073 | ++state->cursor; 1074 | state->select_end = state->cursor; 1075 | state->has_preferred_x = 0; 1076 | break; 1077 | } 1078 | 1079 | // @TODO: 1080 | // STB_TEXTEDIT_K_PGUP - move cursor up a page 1081 | // STB_TEXTEDIT_K_PGDOWN - move cursor down a page 1082 | } 1083 | } 1084 | 1085 | ///////////////////////////////////////////////////////////////////////////// 1086 | // 1087 | // Undo processing 1088 | // 1089 | // @OPTIMIZE: the undo/redo buffer should be circular 1090 | 1091 | static void stb_textedit_flush_redo(StbUndoState *state) 1092 | { 1093 | state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1094 | state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1095 | } 1096 | 1097 | // discard the oldest entry in the undo list 1098 | static void stb_textedit_discard_undo(StbUndoState *state) 1099 | { 1100 | if (state->undo_point > 0) { 1101 | // if the 0th undo state has characters, clean those up 1102 | if (state->undo_rec[0].char_storage >= 0) { 1103 | int n = state->undo_rec[0].insert_length, i; 1104 | // delete n characters from all other records 1105 | state->undo_char_point -= n; 1106 | STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); 1107 | for (i=0; i < state->undo_point; ++i) 1108 | if (state->undo_rec[i].char_storage >= 0) 1109 | state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 1110 | } 1111 | --state->undo_point; 1112 | STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 1113 | } 1114 | } 1115 | 1116 | // discard the oldest entry in the redo list--it's bad if this 1117 | // ever happens, but because undo & redo have to store the actual 1118 | // characters in different cases, the redo character buffer can 1119 | // fill up even though the undo buffer didn't 1120 | static void stb_textedit_discard_redo(StbUndoState *state) 1121 | { 1122 | int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; 1123 | 1124 | if (state->redo_point <= k) { 1125 | // if the k'th undo state has characters, clean those up 1126 | if (state->undo_rec[k].char_storage >= 0) { 1127 | int n = state->undo_rec[k].insert_length, i; 1128 | // move the remaining redo character data to the end of the buffer 1129 | state->redo_char_point += n; 1130 | STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); 1131 | // adjust the position of all the other records to account for above memmove 1132 | for (i=state->redo_point; i < k; ++i) 1133 | if (state->undo_rec[i].char_storage >= 0) 1134 | state->undo_rec[i].char_storage += n; 1135 | } 1136 | // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 1137 | // {DEAR IMGUI] 1138 | size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 1139 | const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 1140 | const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 1141 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 1142 | IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 1143 | STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 1144 | 1145 | // now move redo_point to point to the new one 1146 | ++state->redo_point; 1147 | } 1148 | } 1149 | 1150 | static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1151 | { 1152 | // any time we create a new undo record, we discard redo 1153 | stb_textedit_flush_redo(state); 1154 | 1155 | // if we have no free records, we have to make room, by sliding the 1156 | // existing records down 1157 | if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1158 | stb_textedit_discard_undo(state); 1159 | 1160 | // if the characters to store won't possibly fit in the buffer, we can't undo 1161 | if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { 1162 | state->undo_point = 0; 1163 | state->undo_char_point = 0; 1164 | return NULL; 1165 | } 1166 | 1167 | // if we don't have enough free characters in the buffer, we have to make room 1168 | while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) 1169 | stb_textedit_discard_undo(state); 1170 | 1171 | return &state->undo_rec[state->undo_point++]; 1172 | } 1173 | 1174 | static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1175 | { 1176 | StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1177 | if (r == NULL) 1178 | return NULL; 1179 | 1180 | r->where = pos; 1181 | r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; 1182 | r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; 1183 | 1184 | if (insert_len == 0) { 1185 | r->char_storage = -1; 1186 | return NULL; 1187 | } else { 1188 | r->char_storage = state->undo_char_point; 1189 | state->undo_char_point += insert_len; 1190 | return &state->undo_char[r->char_storage]; 1191 | } 1192 | } 1193 | 1194 | static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1195 | { 1196 | StbUndoState *s = &state->undostate; 1197 | StbUndoRecord u, *r; 1198 | if (s->undo_point == 0) 1199 | return; 1200 | 1201 | // we need to do two things: apply the undo record, and create a redo record 1202 | u = s->undo_rec[s->undo_point-1]; 1203 | r = &s->undo_rec[s->redo_point-1]; 1204 | r->char_storage = -1; 1205 | 1206 | r->insert_length = u.delete_length; 1207 | r->delete_length = u.insert_length; 1208 | r->where = u.where; 1209 | 1210 | if (u.delete_length) { 1211 | // if the undo record says to delete characters, then the redo record will 1212 | // need to re-insert the characters that get deleted, so we need to store 1213 | // them. 1214 | 1215 | // there are three cases: 1216 | // there's enough room to store the characters 1217 | // characters stored for *redoing* don't leave room for redo 1218 | // characters stored for *undoing* don't leave room for redo 1219 | // if the last is true, we have to bail 1220 | 1221 | if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { 1222 | // the undo records take up too much character space; there's no space to store the redo characters 1223 | r->insert_length = 0; 1224 | } else { 1225 | int i; 1226 | 1227 | // there's definitely room to store the characters eventually 1228 | while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1229 | // should never happen: 1230 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1231 | return; 1232 | // there's currently not enough room, so discard a redo record 1233 | stb_textedit_discard_redo(s); 1234 | } 1235 | r = &s->undo_rec[s->redo_point-1]; 1236 | 1237 | r->char_storage = s->redo_char_point - u.delete_length; 1238 | s->redo_char_point = s->redo_char_point - u.delete_length; 1239 | 1240 | // now save the characters 1241 | for (i=0; i < u.delete_length; ++i) 1242 | s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1243 | } 1244 | 1245 | // now we can carry out the deletion 1246 | STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1247 | } 1248 | 1249 | // check type of recorded action: 1250 | if (u.insert_length) { 1251 | // easy case: was a deletion, so we need to insert n characters 1252 | STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1253 | s->undo_char_point -= u.insert_length; 1254 | } 1255 | 1256 | state->cursor = u.where + u.insert_length; 1257 | 1258 | s->undo_point--; 1259 | s->redo_point--; 1260 | } 1261 | 1262 | static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1263 | { 1264 | StbUndoState *s = &state->undostate; 1265 | StbUndoRecord *u, r; 1266 | if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1267 | return; 1268 | 1269 | // we need to do two things: apply the redo record, and create an undo record 1270 | u = &s->undo_rec[s->undo_point]; 1271 | r = s->undo_rec[s->redo_point]; 1272 | 1273 | // we KNOW there must be room for the undo record, because the redo record 1274 | // was derived from an undo record 1275 | 1276 | u->delete_length = r.insert_length; 1277 | u->insert_length = r.delete_length; 1278 | u->where = r.where; 1279 | u->char_storage = -1; 1280 | 1281 | if (r.delete_length) { 1282 | // the redo record requires us to delete characters, so the undo record 1283 | // needs to store the characters 1284 | 1285 | if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1286 | u->insert_length = 0; 1287 | u->delete_length = 0; 1288 | } else { 1289 | int i; 1290 | u->char_storage = s->undo_char_point; 1291 | s->undo_char_point = s->undo_char_point + u->insert_length; 1292 | 1293 | // now save the characters 1294 | for (i=0; i < u->insert_length; ++i) 1295 | s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1296 | } 1297 | 1298 | STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1299 | } 1300 | 1301 | if (r.insert_length) { 1302 | // easy case: need to insert n characters 1303 | STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1304 | s->redo_char_point += r.insert_length; 1305 | } 1306 | 1307 | state->cursor = r.where + r.insert_length; 1308 | 1309 | s->undo_point++; 1310 | s->redo_point++; 1311 | } 1312 | 1313 | static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1314 | { 1315 | stb_text_createundo(&state->undostate, where, 0, length); 1316 | } 1317 | 1318 | static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1319 | { 1320 | int i; 1321 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1322 | if (p) { 1323 | for (i=0; i < length; ++i) 1324 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1325 | } 1326 | } 1327 | 1328 | static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1329 | { 1330 | int i; 1331 | STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1332 | if (p) { 1333 | for (i=0; i < old_length; ++i) 1334 | p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1335 | } 1336 | } 1337 | 1338 | // reset the state to default 1339 | static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1340 | { 1341 | state->undostate.undo_point = 0; 1342 | state->undostate.undo_char_point = 0; 1343 | state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1344 | state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1345 | state->select_end = state->select_start = 0; 1346 | state->cursor = 0; 1347 | state->has_preferred_x = 0; 1348 | state->preferred_x = 0; 1349 | state->cursor_at_end_of_line = 0; 1350 | state->initialized = 1; 1351 | state->single_line = (unsigned char) is_single_line; 1352 | state->insert_mode = 0; 1353 | } 1354 | 1355 | // API initialize 1356 | static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1357 | { 1358 | stb_textedit_clear_state(state, is_single_line); 1359 | } 1360 | 1361 | #if defined(__GNUC__) || defined(__clang__) 1362 | #pragma GCC diagnostic push 1363 | #pragma GCC diagnostic ignored "-Wcast-qual" 1364 | #endif 1365 | 1366 | static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) 1367 | { 1368 | return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); 1369 | } 1370 | 1371 | #if defined(__GNUC__) || defined(__clang__) 1372 | #pragma GCC diagnostic pop 1373 | #endif 1374 | 1375 | #endif//STB_TEXTEDIT_IMPLEMENTATION 1376 | 1377 | /* 1378 | ------------------------------------------------------------------------------ 1379 | This software is available under 2 licenses -- choose whichever you prefer. 1380 | ------------------------------------------------------------------------------ 1381 | ALTERNATIVE A - MIT License 1382 | Copyright (c) 2017 Sean Barrett 1383 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1384 | this software and associated documentation files (the "Software"), to deal in 1385 | the Software without restriction, including without limitation the rights to 1386 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1387 | of the Software, and to permit persons to whom the Software is furnished to do 1388 | so, subject to the following conditions: 1389 | The above copyright notice and this permission notice shall be included in all 1390 | copies or substantial portions of the Software. 1391 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1392 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1393 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1394 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1395 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1396 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1397 | SOFTWARE. 1398 | ------------------------------------------------------------------------------ 1399 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1400 | This is free and unencumbered software released into the public domain. 1401 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1402 | software, either in source code form or as a compiled binary, for any purpose, 1403 | commercial or non-commercial, and by any means. 1404 | In jurisdictions that recognize copyright laws, the author or authors of this 1405 | software dedicate any and all copyright interest in the software to the public 1406 | domain. We make this dedication for the benefit of the public at large and to 1407 | the detriment of our heirs and successors. We intend this dedication to be an 1408 | overt act of relinquishment in perpetuity of all present and future rights to 1409 | this software under copyright law. 1410 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1411 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1412 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1413 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1414 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1415 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1416 | ------------------------------------------------------------------------------ 1417 | */ 1418 | -------------------------------------------------------------------------------- /out/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | * 3 | -------------------------------------------------------------------------------- /shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | 24 | 25 |
26 | 27 |
28 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/draw.cpp: -------------------------------------------------------------------------------- 1 | #include "draw.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | const int LOGFLAGS = EM_LOG_CONSOLE|EM_LOG_C_STACK|EM_LOG_DEMANGLE; 9 | 10 | bool Draw::init() 11 | { 12 | EmscriptenWebGLContextAttributes attrs; 13 | emscripten_webgl_init_context_attributes(&attrs); 14 | 15 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(nullptr, &attrs); 16 | if(context > 0) 17 | { 18 | EMSCRIPTEN_RESULT result = emscripten_webgl_make_context_current(context); 19 | if(EMSCRIPTEN_RESULT_SUCCESS == result) 20 | { 21 | EmscriptenFullscreenStrategy strategy{}; 22 | strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_DEFAULT; 23 | strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; 24 | strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; 25 | result = emscripten_enter_soft_fullscreen("canvas", &strategy); 26 | return EMSCRIPTEN_RESULT_SUCCESS == result; 27 | } 28 | } 29 | EM_ASM("document.body.innerText = 'Your browser does not support WebGL.';"); 30 | return false; 31 | } 32 | 33 | void Draw::printErrors() 34 | { 35 | int error = GL_NO_ERROR; 36 | while((error = glGetError()) != GL_NO_ERROR) 37 | { 38 | emscripten_log(LOGFLAGS, "glerror: %d", error); 39 | } 40 | } 41 | 42 | void Draw::shaderErrors(int shader) 43 | { 44 | int ok = 0; 45 | glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); 46 | if(!ok) { 47 | printf("Shader error\n"); 48 | int infoLen = 0; 49 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); 50 | if(infoLen > 1) 51 | { 52 | char* infoLog = new char[infoLen+1]; 53 | glGetShaderInfoLog(shader, infoLen, NULL, infoLog); 54 | printf("Shader failed:\n%s\n ", infoLog); 55 | delete[] infoLog; 56 | } 57 | } 58 | } 59 | void Draw::clear() 60 | { 61 | glClearColor(0.1f,0.1f,0.1f,1); 62 | glClear(GL_COLOR_BUFFER_BIT); 63 | } 64 | -------------------------------------------------------------------------------- /src/draw.h: -------------------------------------------------------------------------------- 1 | class Draw 2 | { 3 | public: 4 | bool init(); 5 | static void printErrors(); 6 | static void shaderErrors(int shader); 7 | static void clear(); 8 | }; 9 | 10 | #define GL_CHECKED(command)\ 11 | command;\ 12 | for(int error = glGetError(); (error=glGetError()); error != GL_NO_ERROR)\ 13 | {\ 14 | emscripten_log(EM_LOG_CONSOLE|EM_LOG_C_STACK|EM_LOG_DEMANGLE, "glerror: %d", error);\ 15 | } 16 | -------------------------------------------------------------------------------- /src/em.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __EMSCRIPTEN__ 2 | #include "main.h" 3 | #include 4 | #include 5 | EMSCRIPTEN_KEEPALIVE 6 | int main() { 7 | bool bInitialized = init(); 8 | if( bInitialized ) 9 | { 10 | emscripten_set_main_loop(loop, 0, 1); 11 | } 12 | } 13 | #endif // #ifdef __EMSCRIPTEN__ 14 | -------------------------------------------------------------------------------- /src/funimgui.cpp: -------------------------------------------------------------------------------- 1 | #include "funimgui.h" 2 | #include "draw.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define GL_GLEXT_PROTOTYPES 8 | #include 9 | #include 10 | 11 | #define DEBUGPRINT_KEYBOARD 1 12 | 13 | const char* FunImGui::vertexShader = 14 | "uniform mat4 ProjMtx; \n" 15 | "attribute vec2 Position; \n" 16 | "attribute vec2 UV; \n" 17 | "attribute vec4 Color; \n" 18 | "varying vec2 Frag_UV; \n" 19 | "varying vec4 Frag_Color; \n" 20 | "void main() \n" 21 | "{ \n" 22 | " Frag_UV = UV; \n" 23 | " Frag_Color = Color; \n" 24 | " gl_Position = ProjMtx * vec4(Position.xy, 0, 1); \n" 25 | "} \n"; 26 | 27 | const char* FunImGui::fragmentShader = 28 | "uniform sampler2D Texture; \n" 29 | "varying mediump vec2 Frag_UV; \n" 30 | "varying mediump vec4 Frag_Color; \n" 31 | "void main() \n" 32 | "{ \n" 33 | " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV); \n" 34 | "} \n"; 35 | 36 | int FunImGui::m_shaderHandle = -1; 37 | int FunImGui::m_texture = -1; 38 | int FunImGui::m_projectionMatrix = -1; 39 | int FunImGui::m_position = -1; 40 | int FunImGui::m_uv = -1; 41 | int FunImGui::m_color = -1; 42 | unsigned int FunImGui::m_vao = -1; 43 | unsigned int FunImGui::m_vbo = -1; 44 | unsigned int FunImGui::m_elements = -1; 45 | unsigned int FunImGui::m_fontTexture = -1; 46 | 47 | void FunImGui::init() 48 | { 49 | ImGui::CreateContext(); 50 | ImGuiIO& io = ImGui::GetIO(); 51 | 52 | io.KeyMap[ImGuiKey_Tab] = 9; 53 | io.KeyMap[ImGuiKey_LeftArrow] = 37; 54 | io.KeyMap[ImGuiKey_RightArrow] = 39; 55 | io.KeyMap[ImGuiKey_UpArrow] = 38; 56 | io.KeyMap[ImGuiKey_DownArrow] = 40; 57 | io.KeyMap[ImGuiKey_PageUp] = 33; 58 | io.KeyMap[ImGuiKey_PageDown] = 34; 59 | io.KeyMap[ImGuiKey_Home] = 36; 60 | io.KeyMap[ImGuiKey_End] = 35; 61 | io.KeyMap[ImGuiKey_Delete] = 46; 62 | io.KeyMap[ImGuiKey_Backspace] = 8; 63 | io.KeyMap[ImGuiKey_Enter] = 13; 64 | io.KeyMap[ImGuiKey_Escape] = 27; 65 | io.KeyMap[ImGuiKey_A] = 65; 66 | io.KeyMap[ImGuiKey_C] = 67; 67 | io.KeyMap[ImGuiKey_V] = 86; 68 | io.KeyMap[ImGuiKey_X] = 88; 69 | io.KeyMap[ImGuiKey_Y] = 89; 70 | io.KeyMap[ImGuiKey_Z] = 90; 71 | 72 | io.RenderDrawListsFn = RenderDrawLists; 73 | io.ClipboardUserData = nullptr; 74 | initGraphics(); 75 | 76 | io.KeyRepeatDelay = 1.0f; 77 | io.KeyRepeatRate = 0.5f; 78 | 79 | //io.MouseDrawCursor = true; 80 | emscripten_set_mousemove_callback(nullptr, nullptr, false, &FunImGui::mouseCallback); 81 | emscripten_set_mousedown_callback(nullptr, nullptr, false, &FunImGui::mouseCallback); 82 | emscripten_set_mouseup_callback(nullptr, nullptr, false, &FunImGui::mouseCallback); 83 | emscripten_set_wheel_callback("canvas", nullptr, false, &FunImGui::wheelCallback); 84 | emscripten_set_wheel_callback("canvas", nullptr, false, &FunImGui::wheelCallback); 85 | //emscripten_set_keypress_callback(nullptr, nullptr, false, &FunImGui::keyboardCallback); 86 | emscripten_set_keydown_callback(nullptr, nullptr, false, &FunImGui::keyboardCallback); 87 | emscripten_set_keyup_callback(nullptr, nullptr, false, &FunImGui::keyboardCallback); 88 | 89 | //emscripten_request_pointerlock(nullptr, true); 90 | //printf("pixel ratio: %f\n", emscripten_get_device_pixel_ratio()); 91 | } 92 | 93 | void FunImGui::BeginFrame() 94 | { 95 | ImGuiIO& io = ImGui::GetIO(); 96 | int width = 0; 97 | int height = 0; 98 | // int display_w = 0; 99 | // int display_h = 0; 100 | // 101 | // 102 | //double ratio = emscripten_get_device_pixel_ratio(); 103 | 104 | emscripten_get_canvas_element_size("canvas", &width, &height); 105 | io.DisplaySize = ImVec2((float)width, (float)height); 106 | io.DisplayFramebufferScale = ImVec2(1.f,1.f); 107 | /*io.DisplayFramebufferScale = ImVec2( 108 | width > 0 ? ((float)display_w/width) : 0, 109 | height > 0 ? ((float)display_h/height): 0 110 | );*/ 111 | 112 | static double last; 113 | double now = emscripten_get_now(); 114 | io.DeltaTime = (float)(now - last)/1000.0; 115 | last = now; 116 | ImGui::NewFrame(); 117 | } 118 | 119 | void FunImGui::RenderDrawLists(ImDrawData* drawData) 120 | { 121 | ImGuiIO& io = ImGui::GetIO(); 122 | int fb_width = io.DisplaySize.x * io.DisplayFramebufferScale.x; 123 | int fb_height = io.DisplaySize.y * io.DisplayFramebufferScale.y; 124 | 125 | //printf("size: %d, %d\n", fb_width, fb_height); 126 | 127 | if(fb_width == 0 || fb_height == 0) 128 | return; 129 | drawData->ScaleClipRects(io.DisplayFramebufferScale); 130 | 131 | GLint lastProgram; glGetIntegerv(GL_CURRENT_PROGRAM, &lastProgram); 132 | GLint lastTexture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture); 133 | GLint lastActiveTexture; glGetIntegerv(GL_ACTIVE_TEXTURE, &lastActiveTexture); 134 | GLint lastArrayBuffer; glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &lastArrayBuffer); 135 | GLint lastElementArrayBuffer; glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &lastElementArrayBuffer); 136 | GLint lastVertexArray; glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &lastVertexArray); 137 | //GLint lastBlendSrc; glGetIntegerv(GL_BLEND_SRC, &lastBlendSrc); 138 | //GLint lastBlendDst; glGetIntegerv(GL_BLEND_DST, &lastBlendDst); 139 | GLint lastBlendEquationRgb; glGetIntegerv(GL_BLEND_EQUATION_RGB, &lastBlendEquationRgb); 140 | GLint lastBlendEquationAlpha; glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &lastBlendEquationAlpha); 141 | GLint lastViewport[4]; glGetIntegerv(GL_VIEWPORT, lastViewport); 142 | GLint lastScissorBox[4]; glGetIntegerv(GL_SCISSOR_BOX, lastScissorBox); 143 | GLboolean lastEnableBlend = glIsEnabled(GL_BLEND); 144 | GLboolean lastEnableCullFace = glIsEnabled(GL_CULL_FACE); 145 | GLboolean lastEnableDepthTest = glIsEnabled(GL_DEPTH_TEST); 146 | GLboolean lastEnableScissorTest = glIsEnabled(GL_SCISSOR_TEST); 147 | 148 | glEnable(GL_BLEND); 149 | glBlendEquation(GL_FUNC_ADD); 150 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 151 | glDisable(GL_CULL_FACE); 152 | glDisable(GL_DEPTH_TEST); 153 | glEnable(GL_SCISSOR_TEST); 154 | glActiveTexture(GL_TEXTURE0); 155 | 156 | glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); 157 | 158 | const float orthProjection[4][4] = 159 | { 160 | { 2.0f/io.DisplaySize.x, 0.0f, 0.0f, 0.0f }, 161 | { 0.0f, 2.0f/-io.DisplaySize.y, 0.0f, 0.0f }, 162 | { 0.0f, 0.0f, -1.0f, 0.0f }, 163 | {-1.0f, 1.0f, 0.0f, 1.0f }, 164 | }; 165 | 166 | glUseProgram(m_shaderHandle); 167 | glUniform1i(m_texture, 0); 168 | glUniformMatrix4fv(m_projectionMatrix, 1, GL_FALSE, &orthProjection[0][0]); 169 | glBindVertexArrayOES(m_vao); 170 | 171 | for(int i=0; i < drawData->CmdListsCount; ++i) 172 | { 173 | const ImDrawList* cmdList = drawData->CmdLists[i]; 174 | const ImDrawIdx* idxBufferOffset = nullptr; 175 | 176 | glBindBuffer(GL_ARRAY_BUFFER, m_vbo); 177 | glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)cmdList->VtxBuffer.Size * sizeof(ImDrawVert), (GLvoid*)cmdList->VtxBuffer.Data, GL_STREAM_DRAW); 178 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elements); 179 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmdList->IdxBuffer.Size * sizeof(ImDrawIdx), (GLvoid*)cmdList->IdxBuffer.Data, GL_STREAM_DRAW); 180 | 181 | for(int j=0; j < cmdList->CmdBuffer.Size; ++j) 182 | { 183 | const ImDrawCmd* drawCommand = &cmdList->CmdBuffer[j]; 184 | if(drawCommand->UserCallback) 185 | { 186 | drawCommand->UserCallback(cmdList, drawCommand); 187 | } 188 | else 189 | { 190 | glBindTexture( 191 | GL_TEXTURE_2D, 192 | (GLuint)(intptr_t)drawCommand->TextureId 193 | ); 194 | glScissor( 195 | (int)drawCommand->ClipRect.x, 196 | (int)(fb_height - drawCommand->ClipRect.w), 197 | (int)(drawCommand->ClipRect.z - drawCommand->ClipRect.x), 198 | (int)(drawCommand->ClipRect.w - drawCommand->ClipRect.y) 199 | ); 200 | glDrawElements( 201 | GL_TRIANGLES, 202 | (GLsizei)drawCommand->ElemCount, 203 | sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, 204 | idxBufferOffset 205 | ); 206 | } 207 | idxBufferOffset += drawCommand->ElemCount; 208 | } 209 | } 210 | glUseProgram(lastProgram); 211 | glActiveTexture(lastActiveTexture); 212 | glBindTexture(GL_TEXTURE_2D, lastTexture); 213 | glBindVertexArrayOES(lastVertexArray); 214 | glBindBuffer(GL_ARRAY_BUFFER, lastArrayBuffer); 215 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lastElementArrayBuffer); 216 | glBlendEquationSeparate(lastBlendEquationRgb, lastBlendEquationAlpha); 217 | //glBlendFunc(lastBlendSrc, lastBlendDst); 218 | if(lastEnableBlend) glEnable(GL_BLEND); else glDisable(GL_BLEND); 219 | if(lastEnableCullFace) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); 220 | if(lastEnableDepthTest) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); 221 | if(lastEnableScissorTest) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); 222 | glViewport(lastViewport[0], lastViewport[1], (GLsizei)lastViewport[2], (GLsizei)lastViewport[3]); 223 | glScissor(lastScissorBox[0], lastScissorBox[1], (GLsizei)lastScissorBox[2], (GLsizei)lastScissorBox[3]); 224 | } 225 | 226 | void FunImGui::initGraphics() 227 | { 228 | GLint lastTexture; 229 | GLint lastArrayBuffer; 230 | GLint lastVertexArray; 231 | 232 | GL_CHECKED( glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture) ); 233 | GL_CHECKED( glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &lastArrayBuffer) ); 234 | GL_CHECKED( glGetIntegerv(GL_VERTEX_ARRAY_BINDING_OES, &lastVertexArray) ); 235 | 236 | m_shaderHandle = glCreateProgram(); 237 | GLint vertexHandle = glCreateShader(GL_VERTEX_SHADER); 238 | GLint fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER); 239 | 240 | GL_CHECKED( glShaderSource(vertexHandle, 1, &vertexShader, 0) ); 241 | GL_CHECKED( glShaderSource(fragmentHandle, 1, &fragmentShader, 0) ); 242 | GL_CHECKED( glCompileShader(vertexHandle) ); 243 | Draw::shaderErrors(vertexHandle); 244 | GL_CHECKED( glCompileShader(fragmentHandle) ); 245 | Draw::shaderErrors(fragmentHandle); 246 | 247 | GL_CHECKED( glAttachShader(m_shaderHandle, vertexHandle) ); 248 | GL_CHECKED( glAttachShader(m_shaderHandle, fragmentHandle) ); 249 | 250 | GL_CHECKED( glLinkProgram(m_shaderHandle) ); 251 | 252 | GL_CHECKED( m_texture = glGetUniformLocation(m_shaderHandle, "Texture") ); 253 | GL_CHECKED( m_projectionMatrix = glGetUniformLocation(m_shaderHandle, "ProjMtx") ); 254 | GL_CHECKED( m_position = glGetAttribLocation(m_shaderHandle, "Position") ); 255 | GL_CHECKED( m_uv = glGetAttribLocation(m_shaderHandle, "UV") ); 256 | GL_CHECKED( m_color = glGetAttribLocation(m_shaderHandle, "Color") ); 257 | 258 | GL_CHECKED( glGenBuffers(1, &m_vbo) ); 259 | GL_CHECKED( glGenBuffers(1, &m_elements) ); 260 | 261 | GL_CHECKED( glGenVertexArraysOES(1, &m_vao) ); 262 | GL_CHECKED( glBindVertexArrayOES(m_vao) ); 263 | GL_CHECKED( glBindBuffer(GL_ARRAY_BUFFER, m_vbo) ); 264 | GL_CHECKED( glEnableVertexAttribArray(m_position) ); 265 | GL_CHECKED( glEnableVertexAttribArray(m_uv) ); 266 | GL_CHECKED( glEnableVertexAttribArray(m_color) ); 267 | #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) 268 | GL_CHECKED( glVertexAttribPointer(m_position, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, pos)) ); 269 | GL_CHECKED( glVertexAttribPointer(m_uv, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, uv)) ); 270 | GL_CHECKED( glVertexAttribPointer(m_color, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)OFFSETOF(ImDrawVert, col)) ); 271 | #undef OFFSETOF 272 | GL_CHECKED( glBindTexture(GL_TEXTURE_2D, lastTexture) ); 273 | GL_CHECKED( glBindBuffer(GL_ARRAY_BUFFER, lastArrayBuffer) ); 274 | GL_CHECKED( glBindVertexArrayOES(lastVertexArray) ); 275 | initFont(); 276 | } 277 | 278 | void FunImGui::initFont() 279 | { 280 | ImGuiIO& io = ImGui::GetIO(); 281 | unsigned char* pixels = nullptr; 282 | int width = 0; 283 | int height = 0; 284 | io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); 285 | 286 | GLint lastTexture; 287 | glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTexture); 288 | glGenTextures(1, &m_fontTexture); 289 | glBindTexture(GL_TEXTURE_2D, m_fontTexture); 290 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 291 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 292 | //glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 293 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); 294 | io.Fonts->TexID = (void*)(intptr_t)m_fontTexture; 295 | glBindTexture(GL_TEXTURE_2D, lastTexture); 296 | 297 | } 298 | 299 | int FunImGui::mouseCallback(int /*eventType*/, const EmscriptenMouseEvent* mouseEvent, void* /*userData*/) 300 | { 301 | ImGuiIO& io = ImGui::GetIO(); 302 | io.MousePos = ImVec2((float)mouseEvent->canvasX, (float)mouseEvent->canvasY); 303 | io.MouseDown[0] = mouseEvent->buttons & 1; 304 | io.MouseDown[1] = mouseEvent->buttons & 2; 305 | io.MouseDown[2] = mouseEvent->buttons & 4; 306 | return true; 307 | } 308 | 309 | int FunImGui::wheelCallback(int /*eventType*/, const EmscriptenWheelEvent* wheelEvent, void* /*userData*/) 310 | { 311 | ImGuiIO& io = ImGui::GetIO(); 312 | if(wheelEvent->deltaY > 0) 313 | io.MouseWheel = -1.f/5.f; 314 | else if(wheelEvent->deltaY < 0) 315 | io.MouseWheel = 1.f/5.f; 316 | 317 | return true; 318 | } 319 | 320 | int FunImGui::keyboardCallback(int eventType, const EmscriptenKeyboardEvent* keyEvent, void* /*userData*/) 321 | { 322 | bool handled = true; 323 | ImGuiIO& io = ImGui::GetIO(); 324 | //let copy paste fall through to browser 325 | if( 326 | (keyEvent->ctrlKey || keyEvent->metaKey) && 327 | ( 328 | 0 == strcmp("KeyC", keyEvent->code) || 329 | 0 == strcmp("KeyV", keyEvent->code) || 330 | 0 == strcmp("KeyX", keyEvent->code) 331 | ) 332 | ) 333 | { 334 | handled = true; 335 | } 336 | 337 | //todo, detect a single unicode codepoint instead of a single character 338 | if( 339 | keyEvent->key[1] == '\0' && 340 | eventType == EMSCRIPTEN_EVENT_KEYDOWN 341 | ) 342 | { 343 | io.AddInputCharactersUTF8(keyEvent->key); 344 | } 345 | #if DEBUGPRINT_KEYBOARD 346 | printf( 347 | "%d key: %s, code: %s, ctrl: %d, shift: %d, alt: %d, meta: %d, repeat: %d, which: %lu\n", 348 | eventType, 349 | keyEvent->key, 350 | keyEvent->code, 351 | keyEvent->ctrlKey, 352 | keyEvent->shiftKey, 353 | keyEvent->altKey, 354 | keyEvent->metaKey, 355 | keyEvent->repeat, 356 | keyEvent->which 357 | ); 358 | #endif // DEBUGPRINT_KEYBOARD 359 | if(keyEvent->repeat) 360 | { 361 | return true; 362 | } 363 | io.KeyCtrl = keyEvent->ctrlKey; 364 | io.KeyShift = keyEvent->shiftKey; 365 | io.KeyAlt = keyEvent->altKey; 366 | io.KeySuper = keyEvent->metaKey; 367 | //io.KeySuper = false; 368 | 369 | #if DEBUGPRINT_KEYBOARD 370 | printf("ctrl: %d, shift: %d, alt: %d, meta: %d\n", 371 | keyEvent->ctrlKey, 372 | keyEvent->shiftKey, 373 | keyEvent->altKey, 374 | keyEvent->metaKey 375 | ); 376 | #endif // DEBUGPRINT_KEYBOARD 377 | 378 | 379 | switch(eventType) 380 | { 381 | case EMSCRIPTEN_EVENT_KEYDOWN: 382 | { 383 | io.KeysDown[keyEvent->which] = 1; 384 | } 385 | break; 386 | case EMSCRIPTEN_EVENT_KEYUP: 387 | { 388 | io.KeysDown[keyEvent->which] = 0; 389 | } 390 | break; 391 | case EMSCRIPTEN_EVENT_KEYPRESS: 392 | { 393 | printf("%s was pressed\n", keyEvent->key); 394 | } 395 | break; 396 | } 397 | return handled; 398 | } 399 | -------------------------------------------------------------------------------- /src/funimgui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class ImDrawData; 3 | class EmscriptenMouseEvent; 4 | class EmscriptenWheelEvent; 5 | class EmscriptenKeyboardEvent; 6 | class FunImGui { 7 | public: 8 | static void init(); 9 | static void BeginFrame(); 10 | private: 11 | static void initGraphics(); 12 | static void initFont(); 13 | static void RenderDrawLists(ImDrawData* drawData); 14 | static int mouseCallback(int eventType, const EmscriptenMouseEvent* mouseEvent, void* userData); 15 | static int wheelCallback(int eventType, const EmscriptenWheelEvent* wheelEvent, void* userData); 16 | static int keyboardCallback(int eventType, const EmscriptenKeyboardEvent* keyEvent, void* userData); 17 | static const char* vertexShader; 18 | static const char* fragmentShader; 19 | static int m_shaderHandle; 20 | static int m_texture; 21 | static int m_projectionMatrix; 22 | static int m_position; 23 | static int m_uv; 24 | static int m_color; 25 | static unsigned int m_vao; 26 | static unsigned int m_vbo; 27 | static unsigned int m_elements; 28 | static unsigned int m_fontTexture; 29 | }; 30 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | #include "draw.h" 3 | #include "funimgui.h" 4 | #include 5 | #include 6 | #include 7 | static Draw GDraw; 8 | 9 | EMSCRIPTEN_KEEPALIVE 10 | bool init() 11 | { 12 | 13 | bool result = GDraw.init(); 14 | FunImGui::init(); 15 | return result; 16 | } 17 | 18 | EMSCRIPTEN_KEEPALIVE 19 | void loop() 20 | { 21 | static bool bShowTestWindow = true; 22 | FunImGui::BeginFrame(); 23 | ImGui::ShowDemoWindow(&bShowTestWindow); 24 | Draw::clear(); 25 | ImGui::Render(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | bool init(); 3 | void loop(); 4 | --------------------------------------------------------------------------------