stb_textedit.h (49161B)
1 // [ImGui] this is a slightly modified version of stb_truetype.h 1.9. Those changes would need to be pushed into nothings/sb 2 // [ImGui] - fixed linestart handler when over last character of multi-line buffer + simplified existing code (#588, #815) 3 // [ImGui] - fixed a state corruption/crash bug in stb_text_redo and stb_textedit_discard_redo (#715) 4 // [ImGui] - fixed a crash bug in stb_textedit_discard_redo (#681) 5 // [ImGui] - fixed some minor warnings 6 7 // stb_textedit.h - v1.9 - 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 // This software is dual-licensed to the public domain and under the following 27 // license: you are granted a perpetual, irrevocable license to copy, modify, 28 // publish, and distribute this file as you see fit. 29 // 30 // 31 // DEPENDENCIES 32 // 33 // Uses the C runtime function 'memmove', which you can override 34 // by defining STB_TEXTEDIT_memmove before the implementation. 35 // Uses no other functions. Performs no runtime allocations. 36 // 37 // 38 // VERSION HISTORY 39 // 40 // 1.9 (2016-08-27) customizable move-by-word 41 // 1.8 (2016-04-02) better keyboard handling when mouse button is down 42 // 1.7 (2015-09-13) change y range handling in case baseline is non-0 43 // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 44 // 1.5 (2014-09-10) add support for secondary keys for OS X 45 // 1.4 (2014-08-17) fix signed/unsigned warnings 46 // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 47 // 1.2 (2014-05-27) fix some RAD types that had crept into the new code 48 // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 49 // 1.0 (2012-07-26) improve documentation, initial public release 50 // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 51 // 0.2 (2011-11-28) fixes to undo/redo 52 // 0.1 (2010-07-08) initial version 53 // 54 // ADDITIONAL CONTRIBUTORS 55 // 56 // Ulf Winklemann: move-by-word in 1.1 57 // Fabian Giesen: secondary key inputs in 1.5 58 // Martins Mozeiko: STB_TEXTEDIT_memmove 59 // 60 // Bugfixes: 61 // Scott Graham 62 // Daniel Keller 63 // Omar Cornut 64 // 65 // USAGE 66 // 67 // This file behaves differently depending on what symbols you define 68 // before including it. 69 // 70 // 71 // Header-file mode: 72 // 73 // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 74 // it will operate in "header file" mode. In this mode, it declares a 75 // single public symbol, STB_TexteditState, which encapsulates the current 76 // state of a text widget (except for the string, which you will store 77 // separately). 78 // 79 // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 80 // primitive type that defines a single character (e.g. char, wchar_t, etc). 81 // 82 // To save space or increase undo-ability, you can optionally define the 83 // following things that are used by the undo system: 84 // 85 // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 86 // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 87 // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 88 // 89 // If you don't define these, they are set to permissive types and 90 // moderate sizes. The undo system does no memory allocations, so 91 // it grows STB_TexteditState by the worst-case storage which is (in bytes): 92 // 93 // [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT 94 // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT 95 // 96 // 97 // Implementation mode: 98 // 99 // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 100 // will compile the implementation of the text edit widget, depending 101 // on a large number of symbols which must be defined before the include. 102 // 103 // The implementation is defined only as static functions. You will then 104 // need to provide your own APIs in the same file which will access the 105 // static functions. 106 // 107 // The basic concept is that you provide a "string" object which 108 // behaves like an array of characters. stb_textedit uses indices to 109 // refer to positions in the string, implicitly representing positions 110 // in the displayed textedit. This is true for both plain text and 111 // rich text; even with rich text stb_truetype interacts with your 112 // code as if there was an array of all the displayed characters. 113 // 114 // Symbols that must be the same in header-file and implementation mode: 115 // 116 // STB_TEXTEDIT_CHARTYPE the character type 117 // STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position 118 // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 119 // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 120 // 121 // Symbols you must define for implementation mode: 122 // 123 // STB_TEXTEDIT_STRING the type of object representing a string being edited, 124 // typically this is a wrapper object with other data you need 125 // 126 // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 127 // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 128 // starting from character #n (see discussion below) 129 // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 130 // to the xpos of the i+1'th char for a line of characters 131 // starting at character #n (i.e. accounts for kerning 132 // with previous char) 133 // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 134 // (return type is int, -1 means not valid to insert) 135 // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 136 // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 137 // as manually wordwrapping for end-of-line positioning 138 // 139 // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 140 // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 141 // 142 // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 143 // 144 // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 145 // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 146 // STB_TEXTEDIT_K_UP keyboard input to move cursor up 147 // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 148 // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 149 // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 150 // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 151 // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 152 // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 153 // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 154 // STB_TEXTEDIT_K_UNDO keyboard input to perform undo 155 // STB_TEXTEDIT_K_REDO keyboard input to perform redo 156 // 157 // Optional: 158 // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 159 // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 160 // required for default WORDLEFT/WORDRIGHT handlers 161 // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 162 // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 163 // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 164 // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 165 // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 166 // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 167 // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 168 // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 169 // 170 // Todo: 171 // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 172 // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 173 // 174 // Keyboard input must be encoded as a single integer value; e.g. a character code 175 // and some bitflags that represent shift states. to simplify the interface, SHIFT must 176 // be a bitflag, so we can test the shifted state of cursor movements to allow selection, 177 // i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 178 // 179 // You can encode other things, such as CONTROL or ALT, in additional bits, and 180 // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 181 // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 182 // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 183 // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 184 // API below. The control keys will only match WM_KEYDOWN events because of the 185 // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 186 // bit so it only decodes WM_CHAR events. 187 // 188 // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 189 // row of characters assuming they start on the i'th character--the width and 190 // the height and the number of characters consumed. This allows this library 191 // to traverse the entire layout incrementally. You need to compute word-wrapping 192 // here. 193 // 194 // Each textfield keeps its own insert mode state, which is not how normal 195 // applications work. To keep an app-wide insert mode, update/copy the 196 // "insert_mode" field of STB_TexteditState before/after calling API functions. 197 // 198 // API 199 // 200 // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 201 // 202 // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 203 // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 204 // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 205 // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 206 // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) 207 // 208 // Each of these functions potentially updates the string and updates the 209 // state. 210 // 211 // initialize_state: 212 // set the textedit state to a known good default state when initially 213 // constructing the textedit. 214 // 215 // click: 216 // call this with the mouse x,y on a mouse down; it will update the cursor 217 // and reset the selection start/end to the cursor point. the x,y must 218 // be relative to the text widget, with (0,0) being the top left. 219 // 220 // drag: 221 // call this with the mouse x,y on a mouse drag/up; it will update the 222 // cursor and the selection end point 223 // 224 // cut: 225 // call this to delete the current selection; returns true if there was 226 // one. you should FIRST copy the current selection to the system paste buffer. 227 // (To copy, just copy the current selection out of the string yourself.) 228 // 229 // paste: 230 // call this to paste text at the current cursor point or over the current 231 // selection if there is one. 232 // 233 // key: 234 // call this for keyboard inputs sent to the textfield. you can use it 235 // for "key down" events or for "translated" key events. if you need to 236 // do both (as in Win32), or distinguish Unicode characters from control 237 // inputs, set a high bit to distinguish the two; then you can define the 238 // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 239 // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 240 // clear. 241 // 242 // When rendering, you can read the cursor position and selection state from 243 // the STB_TexteditState. 244 // 245 // 246 // Notes: 247 // 248 // This is designed to be usable in IMGUI, so it allows for the possibility of 249 // running in an IMGUI that has NOT cached the multi-line layout. For this 250 // reason, it provides an interface that is compatible with computing the 251 // layout incrementally--we try to make sure we make as few passes through 252 // as possible. (For example, to locate the mouse pointer in the text, we 253 // could define functions that return the X and Y positions of characters 254 // and binary search Y and then X, but if we're doing dynamic layout this 255 // will run the layout algorithm many times, so instead we manually search 256 // forward in one pass. Similar logic applies to e.g. up-arrow and 257 // down-arrow movement.) 258 // 259 // If it's run in a widget that *has* cached the layout, then this is less 260 // efficient, but it's not horrible on modern computers. But you wouldn't 261 // want to edit million-line files with it. 262 263 264 //////////////////////////////////////////////////////////////////////////// 265 //////////////////////////////////////////////////////////////////////////// 266 //// 267 //// Header-file mode 268 //// 269 //// 270 271 #ifndef INCLUDE_STB_TEXTEDIT_H 272 #define INCLUDE_STB_TEXTEDIT_H 273 274 //////////////////////////////////////////////////////////////////////// 275 // 276 // STB_TexteditState 277 // 278 // Definition of STB_TexteditState which you should store 279 // per-textfield; it includes cursor position, selection state, 280 // and undo state. 281 // 282 283 #ifndef STB_TEXTEDIT_UNDOSTATECOUNT 284 #define STB_TEXTEDIT_UNDOSTATECOUNT 99 285 #endif 286 #ifndef STB_TEXTEDIT_UNDOCHARCOUNT 287 #define STB_TEXTEDIT_UNDOCHARCOUNT 999 288 #endif 289 #ifndef STB_TEXTEDIT_CHARTYPE 290 #define STB_TEXTEDIT_CHARTYPE int 291 #endif 292 #ifndef STB_TEXTEDIT_POSITIONTYPE 293 #define STB_TEXTEDIT_POSITIONTYPE int 294 #endif 295 296 typedef struct 297 { 298 // private data 299 STB_TEXTEDIT_POSITIONTYPE where; 300 short insert_length; 301 short delete_length; 302 short char_storage; 303 } StbUndoRecord; 304 305 typedef struct 306 { 307 // private data 308 StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; 309 STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; 310 short undo_point, redo_point; 311 short undo_char_point, redo_char_point; 312 } StbUndoState; 313 314 typedef struct 315 { 316 ///////////////////// 317 // 318 // public data 319 // 320 321 int cursor; 322 // position of the text cursor within the string 323 324 int select_start; // selection start point 325 int select_end; 326 // selection start and end point in characters; if equal, no selection. 327 // note that start may be less than or greater than end (e.g. when 328 // dragging the mouse, start is where the initial click was, and you 329 // can drag in either direction) 330 331 unsigned char insert_mode; 332 // each textfield keeps its own insert mode state. to keep an app-wide 333 // insert mode, copy this value in/out of the app state 334 335 ///////////////////// 336 // 337 // private data 338 // 339 unsigned char cursor_at_end_of_line; // not implemented yet 340 unsigned char initialized; 341 unsigned char has_preferred_x; 342 unsigned char single_line; 343 unsigned char padding1, padding2, padding3; 344 float preferred_x; // this determines where the cursor up/down tries to seek to along x 345 StbUndoState undostate; 346 } STB_TexteditState; 347 348 349 //////////////////////////////////////////////////////////////////////// 350 // 351 // StbTexteditRow 352 // 353 // Result of layout query, used by stb_textedit to determine where 354 // the text in each row is. 355 356 // result of layout query 357 typedef struct 358 { 359 float x0,x1; // starting x location, end x location (allows for align=right, etc) 360 float baseline_y_delta; // position of baseline relative to previous row's baseline 361 float ymin,ymax; // height of row above and below baseline 362 int num_chars; 363 } StbTexteditRow; 364 #endif //INCLUDE_STB_TEXTEDIT_H 365 366 367 //////////////////////////////////////////////////////////////////////////// 368 //////////////////////////////////////////////////////////////////////////// 369 //// 370 //// Implementation mode 371 //// 372 //// 373 374 375 // implementation isn't include-guarded, since it might have indirectly 376 // included just the "header" portion 377 #ifdef STB_TEXTEDIT_IMPLEMENTATION 378 379 #ifndef STB_TEXTEDIT_memmove 380 #include <string.h> 381 #define STB_TEXTEDIT_memmove memmove 382 #endif 383 384 385 ///////////////////////////////////////////////////////////////////////////// 386 // 387 // Mouse input handling 388 // 389 390 // traverse the layout to locate the nearest character to a display position 391 static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) 392 { 393 StbTexteditRow r; 394 int n = STB_TEXTEDIT_STRINGLEN(str); 395 float base_y = 0, prev_x; 396 int i=0, k; 397 398 r.x0 = r.x1 = 0; 399 r.ymin = r.ymax = 0; 400 r.num_chars = 0; 401 402 // search rows to find one that straddles 'y' 403 while (i < n) { 404 STB_TEXTEDIT_LAYOUTROW(&r, str, i); 405 if (r.num_chars <= 0) 406 return n; 407 408 if (i==0 && y < base_y + r.ymin) 409 return 0; 410 411 if (y < base_y + r.ymax) 412 break; 413 414 i += r.num_chars; 415 base_y += r.baseline_y_delta; 416 } 417 418 // below all text, return 'after' last character 419 if (i >= n) 420 return n; 421 422 // check if it's before the beginning of the line 423 if (x < r.x0) 424 return i; 425 426 // check if it's before the end of the line 427 if (x < r.x1) { 428 // search characters in row for one that straddles 'x' 429 prev_x = r.x0; 430 for (k=0; k < r.num_chars; ++k) { 431 float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 432 if (x < prev_x+w) { 433 if (x < prev_x+w/2) 434 return k+i; 435 else 436 return k+i+1; 437 } 438 prev_x += w; 439 } 440 // shouldn't happen, but if it does, fall through to end-of-line case 441 } 442 443 // if the last character is a newline, return that. otherwise return 'after' the last character 444 if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 445 return i+r.num_chars-1; 446 else 447 return i+r.num_chars; 448 } 449 450 // API click: on mouse down, move the cursor to the clicked location, and reset the selection 451 static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 452 { 453 state->cursor = stb_text_locate_coord(str, x, y); 454 state->select_start = state->cursor; 455 state->select_end = state->cursor; 456 state->has_preferred_x = 0; 457 } 458 459 // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 460 static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 461 { 462 int p = stb_text_locate_coord(str, x, y); 463 if (state->select_start == state->select_end) 464 state->select_start = state->cursor; 465 state->cursor = state->select_end = p; 466 } 467 468 ///////////////////////////////////////////////////////////////////////////// 469 // 470 // Keyboard input handling 471 // 472 473 // forward declarations 474 static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 475 static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 476 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 477 static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 478 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 479 480 typedef struct 481 { 482 float x,y; // position of n'th character 483 float height; // height of line 484 int first_char, length; // first char of row, and length 485 int prev_first; // first char of previous row 486 } StbFindState; 487 488 // find the x/y location of a character, and remember info about the previous row in 489 // case we get a move-up event (for page up, we'll have to rescan) 490 static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) 491 { 492 StbTexteditRow r; 493 int prev_start = 0; 494 int z = STB_TEXTEDIT_STRINGLEN(str); 495 int i=0, first; 496 497 if (n == z) { 498 // if it's at the end, then find the last line -- simpler than trying to 499 // explicitly handle this case in the regular code 500 if (single_line) { 501 STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 502 find->y = 0; 503 find->first_char = 0; 504 find->length = z; 505 find->height = r.ymax - r.ymin; 506 find->x = r.x1; 507 } else { 508 find->y = 0; 509 find->x = 0; 510 find->height = 1; 511 while (i < z) { 512 STB_TEXTEDIT_LAYOUTROW(&r, str, i); 513 prev_start = i; 514 i += r.num_chars; 515 } 516 find->first_char = i; 517 find->length = 0; 518 find->prev_first = prev_start; 519 } 520 return; 521 } 522 523 // search rows to find the one that straddles character n 524 find->y = 0; 525 526 for(;;) { 527 STB_TEXTEDIT_LAYOUTROW(&r, str, i); 528 if (n < i + r.num_chars) 529 break; 530 prev_start = i; 531 i += r.num_chars; 532 find->y += r.baseline_y_delta; 533 } 534 535 find->first_char = first = i; 536 find->length = r.num_chars; 537 find->height = r.ymax - r.ymin; 538 find->prev_first = prev_start; 539 540 // now scan to find xpos 541 find->x = r.x0; 542 i = 0; 543 for (i=0; first+i < n; ++i) 544 find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 545 } 546 547 #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 548 549 // make the selection/cursor state valid if client altered the string 550 static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 551 { 552 int n = STB_TEXTEDIT_STRINGLEN(str); 553 if (STB_TEXT_HAS_SELECTION(state)) { 554 if (state->select_start > n) state->select_start = n; 555 if (state->select_end > n) state->select_end = n; 556 // if clamping forced them to be equal, move the cursor to match 557 if (state->select_start == state->select_end) 558 state->cursor = state->select_start; 559 } 560 if (state->cursor > n) state->cursor = n; 561 } 562 563 // delete characters while updating undo 564 static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 565 { 566 stb_text_makeundo_delete(str, state, where, len); 567 STB_TEXTEDIT_DELETECHARS(str, where, len); 568 state->has_preferred_x = 0; 569 } 570 571 // delete the section 572 static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 573 { 574 stb_textedit_clamp(str, state); 575 if (STB_TEXT_HAS_SELECTION(state)) { 576 if (state->select_start < state->select_end) { 577 stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 578 state->select_end = state->cursor = state->select_start; 579 } else { 580 stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 581 state->select_start = state->cursor = state->select_end; 582 } 583 state->has_preferred_x = 0; 584 } 585 } 586 587 // canoncialize the selection so start <= end 588 static void stb_textedit_sortselection(STB_TexteditState *state) 589 { 590 if (state->select_end < state->select_start) { 591 int temp = state->select_end; 592 state->select_end = state->select_start; 593 state->select_start = temp; 594 } 595 } 596 597 // move cursor to first character of selection 598 static void stb_textedit_move_to_first(STB_TexteditState *state) 599 { 600 if (STB_TEXT_HAS_SELECTION(state)) { 601 stb_textedit_sortselection(state); 602 state->cursor = state->select_start; 603 state->select_end = state->select_start; 604 state->has_preferred_x = 0; 605 } 606 } 607 608 // move cursor to last character of selection 609 static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 610 { 611 if (STB_TEXT_HAS_SELECTION(state)) { 612 stb_textedit_sortselection(state); 613 stb_textedit_clamp(str, state); 614 state->cursor = state->select_end; 615 state->select_start = state->select_end; 616 state->has_preferred_x = 0; 617 } 618 } 619 620 #ifdef STB_TEXTEDIT_IS_SPACE 621 static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) 622 { 623 return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 624 } 625 626 #ifndef STB_TEXTEDIT_MOVEWORDLEFT 627 static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) 628 { 629 --c; // always move at least one character 630 while( c >= 0 && !is_word_boundary( str, c ) ) 631 --c; 632 633 if( c < 0 ) 634 c = 0; 635 636 return c; 637 } 638 #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 639 #endif 640 641 #ifndef STB_TEXTEDIT_MOVEWORDRIGHT 642 static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) 643 { 644 const int len = STB_TEXTEDIT_STRINGLEN(str); 645 ++c; // always move at least one character 646 while( c < len && !is_word_boundary( str, c ) ) 647 ++c; 648 649 if( c > len ) 650 c = len; 651 652 return c; 653 } 654 #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 655 #endif 656 657 #endif 658 659 // update selection and cursor to match each other 660 static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 661 { 662 if (!STB_TEXT_HAS_SELECTION(state)) 663 state->select_start = state->select_end = state->cursor; 664 else 665 state->cursor = state->select_end; 666 } 667 668 // API cut: delete selection 669 static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 670 { 671 if (STB_TEXT_HAS_SELECTION(state)) { 672 stb_textedit_delete_selection(str,state); // implicity clamps 673 state->has_preferred_x = 0; 674 return 1; 675 } 676 return 0; 677 } 678 679 // API paste: replace existing selection with passed-in text 680 static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) 681 { 682 STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext; 683 // if there's a selection, the paste should delete it 684 stb_textedit_clamp(str, state); 685 stb_textedit_delete_selection(str,state); 686 // try to insert the characters 687 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { 688 stb_text_makeundo_insert(state, state->cursor, len); 689 state->cursor += len; 690 state->has_preferred_x = 0; 691 return 1; 692 } 693 // remove the undo since we didn't actually insert the characters 694 if (state->undostate.undo_point) 695 --state->undostate.undo_point; 696 return 0; 697 } 698 699 // API key: process a keyboard input 700 static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) 701 { 702 retry: 703 switch (key) { 704 default: { 705 int c = STB_TEXTEDIT_KEYTOTEXT(key); 706 if (c > 0) { 707 STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; 708 709 // can't add newline in single-line mode 710 if (c == '\n' && state->single_line) 711 break; 712 713 if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 714 stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 715 STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 716 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 717 ++state->cursor; 718 state->has_preferred_x = 0; 719 } 720 } else { 721 stb_textedit_delete_selection(str,state); // implicity clamps 722 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 723 stb_text_makeundo_insert(state, state->cursor, 1); 724 ++state->cursor; 725 state->has_preferred_x = 0; 726 } 727 } 728 } 729 break; 730 } 731 732 #ifdef STB_TEXTEDIT_K_INSERT 733 case STB_TEXTEDIT_K_INSERT: 734 state->insert_mode = !state->insert_mode; 735 break; 736 #endif 737 738 case STB_TEXTEDIT_K_UNDO: 739 stb_text_undo(str, state); 740 state->has_preferred_x = 0; 741 break; 742 743 case STB_TEXTEDIT_K_REDO: 744 stb_text_redo(str, state); 745 state->has_preferred_x = 0; 746 break; 747 748 case STB_TEXTEDIT_K_LEFT: 749 // if currently there's a selection, move cursor to start of selection 750 if (STB_TEXT_HAS_SELECTION(state)) 751 stb_textedit_move_to_first(state); 752 else 753 if (state->cursor > 0) 754 --state->cursor; 755 state->has_preferred_x = 0; 756 break; 757 758 case STB_TEXTEDIT_K_RIGHT: 759 // if currently there's a selection, move cursor to end of selection 760 if (STB_TEXT_HAS_SELECTION(state)) 761 stb_textedit_move_to_last(str, state); 762 else 763 ++state->cursor; 764 stb_textedit_clamp(str, state); 765 state->has_preferred_x = 0; 766 break; 767 768 case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 769 stb_textedit_clamp(str, state); 770 stb_textedit_prep_selection_at_cursor(state); 771 // move selection left 772 if (state->select_end > 0) 773 --state->select_end; 774 state->cursor = state->select_end; 775 state->has_preferred_x = 0; 776 break; 777 778 #ifdef STB_TEXTEDIT_MOVEWORDLEFT 779 case STB_TEXTEDIT_K_WORDLEFT: 780 if (STB_TEXT_HAS_SELECTION(state)) 781 stb_textedit_move_to_first(state); 782 else { 783 state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 784 stb_textedit_clamp( str, state ); 785 } 786 break; 787 788 case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 789 if( !STB_TEXT_HAS_SELECTION( state ) ) 790 stb_textedit_prep_selection_at_cursor(state); 791 792 state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 793 state->select_end = state->cursor; 794 795 stb_textedit_clamp( str, state ); 796 break; 797 #endif 798 799 #ifdef STB_TEXTEDIT_MOVEWORDRIGHT 800 case STB_TEXTEDIT_K_WORDRIGHT: 801 if (STB_TEXT_HAS_SELECTION(state)) 802 stb_textedit_move_to_last(str, state); 803 else { 804 state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 805 stb_textedit_clamp( str, state ); 806 } 807 break; 808 809 case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 810 if( !STB_TEXT_HAS_SELECTION( state ) ) 811 stb_textedit_prep_selection_at_cursor(state); 812 813 state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 814 state->select_end = state->cursor; 815 816 stb_textedit_clamp( str, state ); 817 break; 818 #endif 819 820 case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 821 stb_textedit_prep_selection_at_cursor(state); 822 // move selection right 823 ++state->select_end; 824 stb_textedit_clamp(str, state); 825 state->cursor = state->select_end; 826 state->has_preferred_x = 0; 827 break; 828 829 case STB_TEXTEDIT_K_DOWN: 830 case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { 831 StbFindState find; 832 StbTexteditRow row; 833 int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 834 835 if (state->single_line) { 836 // on windows, up&down in single-line behave like left&right 837 key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 838 goto retry; 839 } 840 841 if (sel) 842 stb_textedit_prep_selection_at_cursor(state); 843 else if (STB_TEXT_HAS_SELECTION(state)) 844 stb_textedit_move_to_last(str,state); 845 846 // compute current position of cursor point 847 stb_textedit_clamp(str, state); 848 stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 849 850 // now find character position down a row 851 if (find.length) { 852 float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 853 float x; 854 int start = find.first_char + find.length; 855 state->cursor = start; 856 STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 857 x = row.x0; 858 for (i=0; i < row.num_chars; ++i) { 859 float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 860 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 861 if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 862 break; 863 #endif 864 x += dx; 865 if (x > goal_x) 866 break; 867 ++state->cursor; 868 } 869 stb_textedit_clamp(str, state); 870 871 state->has_preferred_x = 1; 872 state->preferred_x = goal_x; 873 874 if (sel) 875 state->select_end = state->cursor; 876 } 877 break; 878 } 879 880 case STB_TEXTEDIT_K_UP: 881 case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { 882 StbFindState find; 883 StbTexteditRow row; 884 int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 885 886 if (state->single_line) { 887 // on windows, up&down become left&right 888 key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 889 goto retry; 890 } 891 892 if (sel) 893 stb_textedit_prep_selection_at_cursor(state); 894 else if (STB_TEXT_HAS_SELECTION(state)) 895 stb_textedit_move_to_first(state); 896 897 // compute current position of cursor point 898 stb_textedit_clamp(str, state); 899 stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 900 901 // can only go up if there's a previous row 902 if (find.prev_first != find.first_char) { 903 // now find character position up a row 904 float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 905 float x; 906 state->cursor = find.prev_first; 907 STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 908 x = row.x0; 909 for (i=0; i < row.num_chars; ++i) { 910 float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 911 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 912 if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 913 break; 914 #endif 915 x += dx; 916 if (x > goal_x) 917 break; 918 ++state->cursor; 919 } 920 stb_textedit_clamp(str, state); 921 922 state->has_preferred_x = 1; 923 state->preferred_x = goal_x; 924 925 if (sel) 926 state->select_end = state->cursor; 927 } 928 break; 929 } 930 931 case STB_TEXTEDIT_K_DELETE: 932 case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 933 if (STB_TEXT_HAS_SELECTION(state)) 934 stb_textedit_delete_selection(str, state); 935 else { 936 int n = STB_TEXTEDIT_STRINGLEN(str); 937 if (state->cursor < n) 938 stb_textedit_delete(str, state, state->cursor, 1); 939 } 940 state->has_preferred_x = 0; 941 break; 942 943 case STB_TEXTEDIT_K_BACKSPACE: 944 case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 945 if (STB_TEXT_HAS_SELECTION(state)) 946 stb_textedit_delete_selection(str, state); 947 else { 948 stb_textedit_clamp(str, state); 949 if (state->cursor > 0) { 950 stb_textedit_delete(str, state, state->cursor-1, 1); 951 --state->cursor; 952 } 953 } 954 state->has_preferred_x = 0; 955 break; 956 957 #ifdef STB_TEXTEDIT_K_TEXTSTART2 958 case STB_TEXTEDIT_K_TEXTSTART2: 959 #endif 960 case STB_TEXTEDIT_K_TEXTSTART: 961 state->cursor = state->select_start = state->select_end = 0; 962 state->has_preferred_x = 0; 963 break; 964 965 #ifdef STB_TEXTEDIT_K_TEXTEND2 966 case STB_TEXTEDIT_K_TEXTEND2: 967 #endif 968 case STB_TEXTEDIT_K_TEXTEND: 969 state->cursor = STB_TEXTEDIT_STRINGLEN(str); 970 state->select_start = state->select_end = 0; 971 state->has_preferred_x = 0; 972 break; 973 974 #ifdef STB_TEXTEDIT_K_TEXTSTART2 975 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 976 #endif 977 case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 978 stb_textedit_prep_selection_at_cursor(state); 979 state->cursor = state->select_end = 0; 980 state->has_preferred_x = 0; 981 break; 982 983 #ifdef STB_TEXTEDIT_K_TEXTEND2 984 case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 985 #endif 986 case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 987 stb_textedit_prep_selection_at_cursor(state); 988 state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 989 state->has_preferred_x = 0; 990 break; 991 992 993 #ifdef STB_TEXTEDIT_K_LINESTART2 994 case STB_TEXTEDIT_K_LINESTART2: 995 #endif 996 case STB_TEXTEDIT_K_LINESTART: 997 stb_textedit_clamp(str, state); 998 stb_textedit_move_to_first(state); 999 if (state->single_line) 1000 state->cursor = 0; 1001 else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 1002 --state->cursor; 1003 state->has_preferred_x = 0; 1004 break; 1005 1006 #ifdef STB_TEXTEDIT_K_LINEEND2 1007 case STB_TEXTEDIT_K_LINEEND2: 1008 #endif 1009 case STB_TEXTEDIT_K_LINEEND: { 1010 int n = STB_TEXTEDIT_STRINGLEN(str); 1011 stb_textedit_clamp(str, state); 1012 stb_textedit_move_to_first(state); 1013 if (state->single_line) 1014 state->cursor = n; 1015 else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1016 ++state->cursor; 1017 state->has_preferred_x = 0; 1018 break; 1019 } 1020 1021 #ifdef STB_TEXTEDIT_K_LINESTART2 1022 case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 1023 #endif 1024 case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 1025 stb_textedit_clamp(str, state); 1026 stb_textedit_prep_selection_at_cursor(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->select_end = state->cursor; 1032 state->has_preferred_x = 0; 1033 break; 1034 1035 #ifdef STB_TEXTEDIT_K_LINEEND2 1036 case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 1037 #endif 1038 case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 1039 int n = STB_TEXTEDIT_STRINGLEN(str); 1040 stb_textedit_clamp(str, state); 1041 stb_textedit_prep_selection_at_cursor(state); 1042 if (state->single_line) 1043 state->cursor = n; 1044 else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 1045 ++state->cursor; 1046 state->select_end = state->cursor; 1047 state->has_preferred_x = 0; 1048 break; 1049 } 1050 1051 // @TODO: 1052 // STB_TEXTEDIT_K_PGUP - move cursor up a page 1053 // STB_TEXTEDIT_K_PGDOWN - move cursor down a page 1054 } 1055 } 1056 1057 ///////////////////////////////////////////////////////////////////////////// 1058 // 1059 // Undo processing 1060 // 1061 // @OPTIMIZE: the undo/redo buffer should be circular 1062 1063 static void stb_textedit_flush_redo(StbUndoState *state) 1064 { 1065 state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1066 state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1067 } 1068 1069 // discard the oldest entry in the undo list 1070 static void stb_textedit_discard_undo(StbUndoState *state) 1071 { 1072 if (state->undo_point > 0) { 1073 // if the 0th undo state has characters, clean those up 1074 if (state->undo_rec[0].char_storage >= 0) { 1075 int n = state->undo_rec[0].insert_length, i; 1076 // delete n characters from all other records 1077 state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 1078 STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); 1079 for (i=0; i < state->undo_point; ++i) 1080 if (state->undo_rec[i].char_storage >= 0) 1081 state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it 1082 } 1083 --state->undo_point; 1084 STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0]))); 1085 } 1086 } 1087 1088 // discard the oldest entry in the redo list--it's bad if this 1089 // ever happens, but because undo & redo have to store the actual 1090 // characters in different cases, the redo character buffer can 1091 // fill up even though the undo buffer didn't 1092 static void stb_textedit_discard_redo(StbUndoState *state) 1093 { 1094 int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; 1095 1096 if (state->redo_point <= k) { 1097 // if the k'th undo state has characters, clean those up 1098 if (state->undo_rec[k].char_storage >= 0) { 1099 int n = state->undo_rec[k].insert_length, i; 1100 // delete n characters from all other records 1101 state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 1102 STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((size_t)(STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); 1103 for (i=state->redo_point; i < k; ++i) 1104 if (state->undo_rec[i].char_storage >= 0) 1105 state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 1106 } 1107 STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); 1108 ++state->redo_point; 1109 } 1110 } 1111 1112 static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 1113 { 1114 // any time we create a new undo record, we discard redo 1115 stb_textedit_flush_redo(state); 1116 1117 // if we have no free records, we have to make room, by sliding the 1118 // existing records down 1119 if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1120 stb_textedit_discard_undo(state); 1121 1122 // if the characters to store won't possibly fit in the buffer, we can't undo 1123 if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { 1124 state->undo_point = 0; 1125 state->undo_char_point = 0; 1126 return NULL; 1127 } 1128 1129 // if we don't have enough free characters in the buffer, we have to make room 1130 while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) 1131 stb_textedit_discard_undo(state); 1132 1133 return &state->undo_rec[state->undo_point++]; 1134 } 1135 1136 static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 1137 { 1138 StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 1139 if (r == NULL) 1140 return NULL; 1141 1142 r->where = pos; 1143 r->insert_length = (short) insert_len; 1144 r->delete_length = (short) delete_len; 1145 1146 if (insert_len == 0) { 1147 r->char_storage = -1; 1148 return NULL; 1149 } else { 1150 r->char_storage = state->undo_char_point; 1151 state->undo_char_point = state->undo_char_point + (short) insert_len; 1152 return &state->undo_char[r->char_storage]; 1153 } 1154 } 1155 1156 static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1157 { 1158 StbUndoState *s = &state->undostate; 1159 StbUndoRecord u, *r; 1160 if (s->undo_point == 0) 1161 return; 1162 1163 // we need to do two things: apply the undo record, and create a redo record 1164 u = s->undo_rec[s->undo_point-1]; 1165 r = &s->undo_rec[s->redo_point-1]; 1166 r->char_storage = -1; 1167 1168 r->insert_length = u.delete_length; 1169 r->delete_length = u.insert_length; 1170 r->where = u.where; 1171 1172 if (u.delete_length) { 1173 // if the undo record says to delete characters, then the redo record will 1174 // need to re-insert the characters that get deleted, so we need to store 1175 // them. 1176 1177 // there are three cases: 1178 // there's enough room to store the characters 1179 // characters stored for *redoing* don't leave room for redo 1180 // characters stored for *undoing* don't leave room for redo 1181 // if the last is true, we have to bail 1182 1183 if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { 1184 // the undo records take up too much character space; there's no space to store the redo characters 1185 r->insert_length = 0; 1186 } else { 1187 int i; 1188 1189 // there's definitely room to store the characters eventually 1190 while (s->undo_char_point + u.delete_length > s->redo_char_point) { 1191 // there's currently not enough room, so discard a redo record 1192 stb_textedit_discard_redo(s); 1193 // should never happen: 1194 if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1195 return; 1196 } 1197 r = &s->undo_rec[s->redo_point-1]; 1198 1199 r->char_storage = s->redo_char_point - u.delete_length; 1200 s->redo_char_point = s->redo_char_point - (short) u.delete_length; 1201 1202 // now save the characters 1203 for (i=0; i < u.delete_length; ++i) 1204 s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 1205 } 1206 1207 // now we can carry out the deletion 1208 STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 1209 } 1210 1211 // check type of recorded action: 1212 if (u.insert_length) { 1213 // easy case: was a deletion, so we need to insert n characters 1214 STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 1215 s->undo_char_point -= u.insert_length; 1216 } 1217 1218 state->cursor = u.where + u.insert_length; 1219 1220 s->undo_point--; 1221 s->redo_point--; 1222 } 1223 1224 static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 1225 { 1226 StbUndoState *s = &state->undostate; 1227 StbUndoRecord *u, r; 1228 if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 1229 return; 1230 1231 // we need to do two things: apply the redo record, and create an undo record 1232 u = &s->undo_rec[s->undo_point]; 1233 r = s->undo_rec[s->redo_point]; 1234 1235 // we KNOW there must be room for the undo record, because the redo record 1236 // was derived from an undo record 1237 1238 u->delete_length = r.insert_length; 1239 u->insert_length = r.delete_length; 1240 u->where = r.where; 1241 u->char_storage = -1; 1242 1243 if (r.delete_length) { 1244 // the redo record requires us to delete characters, so the undo record 1245 // needs to store the characters 1246 1247 if (s->undo_char_point + u->insert_length > s->redo_char_point) { 1248 u->insert_length = 0; 1249 u->delete_length = 0; 1250 } else { 1251 int i; 1252 u->char_storage = s->undo_char_point; 1253 s->undo_char_point = s->undo_char_point + u->insert_length; 1254 1255 // now save the characters 1256 for (i=0; i < u->insert_length; ++i) 1257 s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 1258 } 1259 1260 STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 1261 } 1262 1263 if (r.insert_length) { 1264 // easy case: need to insert n characters 1265 STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 1266 s->redo_char_point += r.insert_length; 1267 } 1268 1269 state->cursor = r.where + r.insert_length; 1270 1271 s->undo_point++; 1272 s->redo_point++; 1273 } 1274 1275 static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 1276 { 1277 stb_text_createundo(&state->undostate, where, 0, length); 1278 } 1279 1280 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 1281 { 1282 int i; 1283 STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 1284 if (p) { 1285 for (i=0; i < length; ++i) 1286 p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1287 } 1288 } 1289 1290 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 1291 { 1292 int i; 1293 STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 1294 if (p) { 1295 for (i=0; i < old_length; ++i) 1296 p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 1297 } 1298 } 1299 1300 // reset the state to default 1301 static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 1302 { 1303 state->undostate.undo_point = 0; 1304 state->undostate.undo_char_point = 0; 1305 state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 1306 state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 1307 state->select_end = state->select_start = 0; 1308 state->cursor = 0; 1309 state->has_preferred_x = 0; 1310 state->preferred_x = 0; 1311 state->cursor_at_end_of_line = 0; 1312 state->initialized = 1; 1313 state->single_line = (unsigned char) is_single_line; 1314 state->insert_mode = 0; 1315 } 1316 1317 // API initialize 1318 static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 1319 { 1320 stb_textedit_clear_state(state, is_single_line); 1321 } 1322 #endif//STB_TEXTEDIT_IMPLEMENTATION