/ deps / AImGUI / third_party / imgui / imstb_textedit.h
imstb_textedit.h
   1  // [DEAR IMGUI]
   2  // This is a slightly modified version of stb_textedit.h 1.14.
   3  // Those changes would need to be pushed into nothings/stb:
   4  // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
   5  // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
   6  // - Added name to struct or it may be forward declared in our code.
   7  // - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)
   8  // - Changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
   9  // Grep for [DEAR IMGUI] to find some changes.
  10  // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
  11  
  12  // stb_textedit.h - v1.14  - public domain - Sean Barrett
  13  // Development of this library was sponsored by RAD Game Tools
  14  //
  15  // This C header file implements the guts of a multi-line text-editing
  16  // widget; you implement display, word-wrapping, and low-level string
  17  // insertion/deletion, and stb_textedit will map user inputs into
  18  // insertions & deletions, plus updates to the cursor position,
  19  // selection state, and undo state.
  20  //
  21  // It is intended for use in games and other systems that need to build
  22  // their own custom widgets and which do not have heavy text-editing
  23  // requirements (this library is not recommended for use for editing large
  24  // texts, as its performance does not scale and it has limited undo).
  25  //
  26  // Non-trivial behaviors are modelled after Windows text controls.
  27  //
  28  //
  29  // LICENSE
  30  //
  31  // See end of file for license information.
  32  //
  33  //
  34  // DEPENDENCIES
  35  //
  36  // Uses the C runtime function 'memmove', which you can override
  37  // by defining IMSTB_TEXTEDIT_memmove before the implementation.
  38  // Uses no other functions. Performs no runtime allocations.
  39  //
  40  //
  41  // VERSION HISTORY
  42  //
  43  //   !!!! (2025-10-23) changed STB_TEXTEDIT_INSERTCHARS() to return inserted count (instead of 0/1 bool), allowing partial insertion.
  44  //   1.14 (2021-07-11) page up/down, various fixes
  45  //   1.13 (2019-02-07) fix bug in undo size management
  46  //   1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
  47  //   1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
  48  //   1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual
  49  //   1.9  (2016-08-27) customizable move-by-word
  50  //   1.8  (2016-04-02) better keyboard handling when mouse button is down
  51  //   1.7  (2015-09-13) change y range handling in case baseline is non-0
  52  //   1.6  (2015-04-15) allow STB_TEXTEDIT_memmove
  53  //   1.5  (2014-09-10) add support for secondary keys for OS X
  54  //   1.4  (2014-08-17) fix signed/unsigned warnings
  55  //   1.3  (2014-06-19) fix mouse clicking to round to nearest char boundary
  56  //   1.2  (2014-05-27) fix some RAD types that had crept into the new code
  57  //   1.1  (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
  58  //   1.0  (2012-07-26) improve documentation, initial public release
  59  //   0.3  (2012-02-24) bugfixes, single-line mode; insert mode
  60  //   0.2  (2011-11-28) fixes to undo/redo
  61  //   0.1  (2010-07-08) initial version
  62  //
  63  // ADDITIONAL CONTRIBUTORS
  64  //
  65  //   Ulf Winklemann: move-by-word in 1.1
  66  //   Fabian Giesen: secondary key inputs in 1.5
  67  //   Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
  68  //   Louis Schnellbach: page up/down in 1.14
  69  //
  70  //   Bugfixes:
  71  //      Scott Graham
  72  //      Daniel Keller
  73  //      Omar Cornut
  74  //      Dan Thompson
  75  //
  76  // USAGE
  77  //
  78  // This file behaves differently depending on what symbols you define
  79  // before including it.
  80  //
  81  //
  82  // Header-file mode:
  83  //
  84  //   If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
  85  //   it will operate in "header file" mode. In this mode, it declares a
  86  //   single public symbol, STB_TexteditState, which encapsulates the current
  87  //   state of a text widget (except for the string, which you will store
  88  //   separately).
  89  //
  90  //   To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
  91  //   primitive type that defines a single character (e.g. char, wchar_t, etc).
  92  //
  93  //   To save space or increase undo-ability, you can optionally define the
  94  //   following things that are used by the undo system:
  95  //
  96  //      STB_TEXTEDIT_POSITIONTYPE         small int type encoding a valid cursor position
  97  //      STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
  98  //      STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
  99  //
 100  //   If you don't define these, they are set to permissive types and
 101  //   moderate sizes. The undo system does no memory allocations, so
 102  //   it grows STB_TexteditState by the worst-case storage which is (in bytes):
 103  //
 104  //        [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT
 105  //      +          sizeof(STB_TEXTEDIT_CHARTYPE)      * STB_TEXTEDIT_UNDOCHARCOUNT
 106  //
 107  //
 108  // Implementation mode:
 109  //
 110  //   If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
 111  //   will compile the implementation of the text edit widget, depending
 112  //   on a large number of symbols which must be defined before the include.
 113  //
 114  //   The implementation is defined only as static functions. You will then
 115  //   need to provide your own APIs in the same file which will access the
 116  //   static functions.
 117  //
 118  //   The basic concept is that you provide a "string" object which
 119  //   behaves like an array of characters. stb_textedit uses indices to
 120  //   refer to positions in the string, implicitly representing positions
 121  //   in the displayed textedit. This is true for both plain text and
 122  //   rich text; even with rich text stb_truetype interacts with your
 123  //   code as if there was an array of all the displayed characters.
 124  //
 125  // Symbols that must be the same in header-file and implementation mode:
 126  //
 127  //     STB_TEXTEDIT_CHARTYPE             the character type
 128  //     STB_TEXTEDIT_POSITIONTYPE         small type that is a valid cursor position
 129  //     STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
 130  //     STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
 131  //
 132  // Symbols you must define for implementation mode:
 133  //
 134  //    STB_TEXTEDIT_STRING               the type of object representing a string being edited,
 135  //                                      typically this is a wrapper object with other data you need
 136  //
 137  //    STB_TEXTEDIT_STRINGLEN(obj)       the length of the string (ideally O(1))
 138  //    STB_TEXTEDIT_LAYOUTROW(&r,obj,n)  returns the results of laying out a line of characters
 139  //                                        starting from character #n (see discussion below)
 140  //    STB_TEXTEDIT_GETWIDTH(obj,n,i)    returns the pixel delta from the xpos of the i'th character
 141  //                                        to the xpos of the i+1'th char for a line of characters
 142  //                                        starting at character #n (i.e. accounts for kerning
 143  //                                        with previous char)
 144  //    STB_TEXTEDIT_KEYTOTEXT(k)         maps a keyboard input to an insertable character
 145  //                                        (return type is int, -1 means not valid to insert)
 146  //                                        (not supported if you want to use UTF-8, see below)
 147  //    STB_TEXTEDIT_GETCHAR(obj,i)       returns the i'th character of obj, 0-based
 148  //    STB_TEXTEDIT_NEWLINE              the character returned by _GETCHAR() we recognize
 149  //                                        as manually wordwrapping for end-of-line positioning
 150  //
 151  //    STB_TEXTEDIT_DELETECHARS(obj,i,n)      delete n characters starting at i
 152  //    STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n)   try to insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
 153  //                                           returns number of characters actually inserted. [DEAR IMGUI]
 154  //
 155  //    STB_TEXTEDIT_K_SHIFT       a power of two that is or'd in to a keyboard input to represent the shift key
 156  //
 157  //    STB_TEXTEDIT_K_LEFT        keyboard input to move cursor left
 158  //    STB_TEXTEDIT_K_RIGHT       keyboard input to move cursor right
 159  //    STB_TEXTEDIT_K_UP          keyboard input to move cursor up
 160  //    STB_TEXTEDIT_K_DOWN        keyboard input to move cursor down
 161  //    STB_TEXTEDIT_K_PGUP        keyboard input to move cursor up a page
 162  //    STB_TEXTEDIT_K_PGDOWN      keyboard input to move cursor down a page
 163  //    STB_TEXTEDIT_K_LINESTART   keyboard input to move cursor to start of line  // e.g. HOME
 164  //    STB_TEXTEDIT_K_LINEEND     keyboard input to move cursor to end of line    // e.g. END
 165  //    STB_TEXTEDIT_K_TEXTSTART   keyboard input to move cursor to start of text  // e.g. ctrl-HOME
 166  //    STB_TEXTEDIT_K_TEXTEND     keyboard input to move cursor to end of text    // e.g. ctrl-END
 167  //    STB_TEXTEDIT_K_DELETE      keyboard input to delete selection or character under cursor
 168  //    STB_TEXTEDIT_K_BACKSPACE   keyboard input to delete selection or character left of cursor
 169  //    STB_TEXTEDIT_K_UNDO        keyboard input to perform undo
 170  //    STB_TEXTEDIT_K_REDO        keyboard input to perform redo
 171  //
 172  // Optional:
 173  //    STB_TEXTEDIT_K_INSERT              keyboard input to toggle insert mode
 174  //    STB_TEXTEDIT_IS_SPACE(ch)          true if character is whitespace (e.g. 'isspace'),
 175  //                                          required for default WORDLEFT/WORDRIGHT handlers
 176  //    STB_TEXTEDIT_MOVEWORDLEFT(obj,i)   custom handler for WORDLEFT, returns index to move cursor to
 177  //    STB_TEXTEDIT_MOVEWORDRIGHT(obj,i)  custom handler for WORDRIGHT, returns index to move cursor to
 178  //    STB_TEXTEDIT_K_WORDLEFT            keyboard input to move cursor left one word // e.g. ctrl-LEFT
 179  //    STB_TEXTEDIT_K_WORDRIGHT           keyboard input to move cursor right one word // e.g. ctrl-RIGHT
 180  //    STB_TEXTEDIT_K_LINESTART2          secondary keyboard input to move cursor to start of line
 181  //    STB_TEXTEDIT_K_LINEEND2            secondary keyboard input to move cursor to end of line
 182  //    STB_TEXTEDIT_K_TEXTSTART2          secondary keyboard input to move cursor to start of text
 183  //    STB_TEXTEDIT_K_TEXTEND2            secondary keyboard input to move cursor to end of text
 184  //
 185  // To support UTF-8:
 186  //
 187  //    STB_TEXTEDIT_GETPREVCHARINDEX      returns index of previous character
 188  //    STB_TEXTEDIT_GETNEXTCHARINDEX      returns index of next character
 189  //    Do NOT define STB_TEXTEDIT_KEYTOTEXT.
 190  //    Instead, call stb_textedit_text() directly for text contents.
 191  //
 192  // Keyboard input must be encoded as a single integer value; e.g. a character code
 193  // and some bitflags that represent shift states. to simplify the interface, SHIFT must
 194  // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
 195  // i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
 196  //
 197  // You can encode other things, such as CONTROL or ALT, in additional bits, and
 198  // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
 199  // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
 200  // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
 201  // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
 202  // API below. The control keys will only match WM_KEYDOWN events because of the
 203  // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
 204  // bit so it only decodes WM_CHAR events.
 205  //
 206  // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
 207  // row of characters assuming they start on the i'th character--the width and
 208  // the height and the number of characters consumed. This allows this library
 209  // to traverse the entire layout incrementally. You need to compute word-wrapping
 210  // here.
 211  //
 212  // Each textfield keeps its own insert mode state, which is not how normal
 213  // applications work. To keep an app-wide insert mode, update/copy the
 214  // "insert_mode" field of STB_TexteditState before/after calling API functions.
 215  //
 216  // API
 217  //
 218  //    void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
 219  //
 220  //    void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
 221  //    void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
 222  //    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 223  //    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
 224  //    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
 225  //    void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)
 226  //
 227  //    Each of these functions potentially updates the string and updates the
 228  //    state.
 229  //
 230  //      initialize_state:
 231  //          set the textedit state to a known good default state when initially
 232  //          constructing the textedit.
 233  //
 234  //      click:
 235  //          call this with the mouse x,y on a mouse down; it will update the cursor
 236  //          and reset the selection start/end to the cursor point. the x,y must
 237  //          be relative to the text widget, with (0,0) being the top left.
 238  //
 239  //      drag:
 240  //          call this with the mouse x,y on a mouse drag/up; it will update the
 241  //          cursor and the selection end point
 242  //
 243  //      cut:
 244  //          call this to delete the current selection; returns true if there was
 245  //          one. you should FIRST copy the current selection to the system paste buffer.
 246  //          (To copy, just copy the current selection out of the string yourself.)
 247  //
 248  //      paste:
 249  //          call this to paste text at the current cursor point or over the current
 250  //          selection if there is one.
 251  //
 252  //      key:
 253  //          call this for keyboard inputs sent to the textfield. you can use it
 254  //          for "key down" events or for "translated" key events. if you need to
 255  //          do both (as in Win32), or distinguish Unicode characters from control
 256  //          inputs, set a high bit to distinguish the two; then you can define the
 257  //          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
 258  //          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
 259  //          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
 260  //          anything other type you want before including.
 261  //          if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
 262  //          transformed into text and stb_textedit_text() is automatically called.
 263  //
 264  //      text: (added 2025)
 265  //          call this to directly send text input the textfield, which is required
 266  //          for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
 267  //          cannot infer text length.
 268  //
 269  //
 270  //   When rendering, you can read the cursor position and selection state from
 271  //   the STB_TexteditState.
 272  //
 273  //
 274  // Notes:
 275  //
 276  // This is designed to be usable in IMGUI, so it allows for the possibility of
 277  // running in an IMGUI that has NOT cached the multi-line layout. For this
 278  // reason, it provides an interface that is compatible with computing the
 279  // layout incrementally--we try to make sure we make as few passes through
 280  // as possible. (For example, to locate the mouse pointer in the text, we
 281  // could define functions that return the X and Y positions of characters
 282  // and binary search Y and then X, but if we're doing dynamic layout this
 283  // will run the layout algorithm many times, so instead we manually search
 284  // forward in one pass. Similar logic applies to e.g. up-arrow and
 285  // down-arrow movement.)
 286  //
 287  // If it's run in a widget that *has* cached the layout, then this is less
 288  // efficient, but it's not horrible on modern computers. But you wouldn't
 289  // want to edit million-line files with it.
 290  
 291  
 292  ////////////////////////////////////////////////////////////////////////////
 293  ////////////////////////////////////////////////////////////////////////////
 294  ////
 295  ////   Header-file mode
 296  ////
 297  ////
 298  
 299  #ifndef INCLUDE_IMSTB_TEXTEDIT_H
 300  #define INCLUDE_IMSTB_TEXTEDIT_H
 301  
 302  ////////////////////////////////////////////////////////////////////////
 303  //
 304  //     STB_TexteditState
 305  //
 306  // Definition of STB_TexteditState which you should store
 307  // per-textfield; it includes cursor position, selection state,
 308  // and undo state.
 309  //
 310  
 311  #ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT
 312  #define IMSTB_TEXTEDIT_UNDOSTATECOUNT   99
 313  #endif
 314  #ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT
 315  #define IMSTB_TEXTEDIT_UNDOCHARCOUNT   999
 316  #endif
 317  #ifndef IMSTB_TEXTEDIT_CHARTYPE
 318  #define IMSTB_TEXTEDIT_CHARTYPE        int
 319  #endif
 320  #ifndef IMSTB_TEXTEDIT_POSITIONTYPE
 321  #define IMSTB_TEXTEDIT_POSITIONTYPE    int
 322  #endif
 323  
 324  typedef struct
 325  {
 326     // private data
 327     IMSTB_TEXTEDIT_POSITIONTYPE  where;
 328     IMSTB_TEXTEDIT_POSITIONTYPE  insert_length;
 329     IMSTB_TEXTEDIT_POSITIONTYPE  delete_length;
 330     int                        char_storage;
 331  } StbUndoRecord;
 332  
 333  typedef struct
 334  {
 335     // private data
 336     StbUndoRecord          undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT];
 337     IMSTB_TEXTEDIT_CHARTYPE  undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT];
 338     short undo_point, redo_point;
 339     int undo_char_point, redo_char_point;
 340  } StbUndoState;
 341  
 342  typedef struct STB_TexteditState
 343  {
 344     /////////////////////
 345     //
 346     // public data
 347     //
 348  
 349     int cursor;
 350     // position of the text cursor within the string
 351  
 352     int select_start;          // selection start point
 353     int select_end;
 354     // selection start and end point in characters; if equal, no selection.
 355     // note that start may be less than or greater than end (e.g. when
 356     // dragging the mouse, start is where the initial click was, and you
 357     // can drag in either direction)
 358  
 359     unsigned char insert_mode;
 360     // each textfield keeps its own insert mode state. to keep an app-wide
 361     // insert mode, copy this value in/out of the app state
 362  
 363     int row_count_per_page;
 364     // page size in number of row.
 365     // this value MUST be set to >0 for pageup or pagedown in multilines documents.
 366  
 367     /////////////////////
 368     //
 369     // private data
 370     //
 371     unsigned char cursor_at_end_of_line; // not implemented yet
 372     unsigned char initialized;
 373     unsigned char has_preferred_x;
 374     unsigned char single_line;
 375     unsigned char padding1, padding2, padding3;
 376     float preferred_x; // this determines where the cursor up/down tries to seek to along x
 377     StbUndoState undostate;
 378  } STB_TexteditState;
 379  
 380  
 381  ////////////////////////////////////////////////////////////////////////
 382  //
 383  //     StbTexteditRow
 384  //
 385  // Result of layout query, used by stb_textedit to determine where
 386  // the text in each row is.
 387  
 388  // result of layout query
 389  typedef struct
 390  {
 391     float x0,x1;             // starting x location, end x location (allows for align=right, etc)
 392     float baseline_y_delta;  // position of baseline relative to previous row's baseline
 393     float ymin,ymax;         // height of row above and below baseline
 394     int num_chars;
 395  } StbTexteditRow;
 396  #endif //INCLUDE_IMSTB_TEXTEDIT_H
 397  
 398  
 399  ////////////////////////////////////////////////////////////////////////////
 400  ////////////////////////////////////////////////////////////////////////////
 401  ////
 402  ////   Implementation mode
 403  ////
 404  ////
 405  
 406  
 407  // implementation isn't include-guarded, since it might have indirectly
 408  // included just the "header" portion
 409  #ifdef IMSTB_TEXTEDIT_IMPLEMENTATION
 410  
 411  #ifndef IMSTB_TEXTEDIT_memmove
 412  #include <string.h>
 413  #define IMSTB_TEXTEDIT_memmove memmove
 414  #endif
 415  
 416  // [DEAR IMGUI]
 417  // Functions must be implemented for UTF8 support
 418  // Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
 419  // There is not necessarily a '[DEAR IMGUI]' at the usage sites.
 420  #ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
 421  #define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
 422  #endif
 423  #ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
 424  #define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
 425  #endif
 426  
 427  /////////////////////////////////////////////////////////////////////////////
 428  //
 429  //      Mouse input handling
 430  //
 431  
 432  // traverse the layout to locate the nearest character to a display position
 433  static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y, int* out_side_on_line)
 434  {
 435     StbTexteditRow r;
 436     int n = STB_TEXTEDIT_STRINGLEN(str);
 437     float base_y = 0, prev_x;
 438     int i=0, k;
 439  
 440     r.x0 = r.x1 = 0;
 441     r.ymin = r.ymax = 0;
 442     r.num_chars = 0;
 443     *out_side_on_line = 0;
 444  
 445     // search rows to find one that straddles 'y'
 446     while (i < n) {
 447        STB_TEXTEDIT_LAYOUTROW(&r, str, i);
 448        if (r.num_chars <= 0)
 449           return n;
 450  
 451        if (i==0 && y < base_y + r.ymin)
 452           return 0;
 453  
 454        if (y < base_y + r.ymax)
 455           break;
 456  
 457        i += r.num_chars;
 458        base_y += r.baseline_y_delta;
 459     }
 460  
 461     // below all text, return 'after' last character
 462     if (i >= n)
 463     {
 464        *out_side_on_line = 1;
 465        return n;
 466     }
 467  
 468     // check if it's before the beginning of the line
 469     if (x < r.x0)
 470        return i;
 471  
 472     // check if it's before the end of the line
 473     if (x < r.x1) {
 474        // search characters in row for one that straddles 'x'
 475        prev_x = r.x0;
 476        for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
 477           float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
 478           if (x < prev_x+w) {
 479              *out_side_on_line = (k == 0) ? 0 : 1;
 480              if (x < prev_x+w/2)
 481                 return k+i;
 482              else
 483                 return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);
 484           }
 485           prev_x += w;
 486        }
 487        // shouldn't happen, but if it does, fall through to end-of-line case
 488     }
 489  
 490     // if the last character is a newline, return that. otherwise return 'after' the last character
 491     *out_side_on_line = 1;
 492     if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
 493        return i+r.num_chars-1;
 494     else
 495        return i+r.num_chars;
 496  }
 497  
 498  // API click: on mouse down, move the cursor to the clicked location, and reset the selection
 499  static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
 500  {
 501     // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
 502     // goes off the top or bottom of the text
 503     int side_on_line;
 504     if( state->single_line )
 505     {
 506        StbTexteditRow r;
 507        STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
 508        y = r.ymin;
 509     }
 510  
 511     state->cursor = stb_text_locate_coord(str, x, y, &side_on_line);
 512     state->select_start = state->cursor;
 513     state->select_end = state->cursor;
 514     state->has_preferred_x = 0;
 515     str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
 516  }
 517  
 518  // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
 519  static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
 520  {
 521     int p = 0;
 522     int side_on_line;
 523  
 524     // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
 525     // goes off the top or bottom of the text
 526     if( state->single_line )
 527     {
 528        StbTexteditRow r;
 529        STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
 530        y = r.ymin;
 531     }
 532  
 533     if (state->select_start == state->select_end)
 534        state->select_start = state->cursor;
 535  
 536     p = stb_text_locate_coord(str, x, y, &side_on_line);
 537     state->cursor = state->select_end = p;
 538     str->LastMoveDirectionLR = (ImS8)(side_on_line ? ImGuiDir_Right : ImGuiDir_Left);
 539  }
 540  
 541  /////////////////////////////////////////////////////////////////////////////
 542  //
 543  //      Keyboard input handling
 544  //
 545  
 546  // forward declarations
 547  static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
 548  static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
 549  static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
 550  static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
 551  static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
 552  
 553  typedef struct
 554  {
 555     float x,y;    // position of n'th character
 556     float height; // height of line
 557     int first_char, length; // first char of row, and length
 558     int prev_first;  // first char of previous row
 559  } StbFindState;
 560  
 561  // find the x/y location of a character, and remember info about the previous row in
 562  // case we get a move-up event (for page up, we'll have to rescan)
 563  static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line)
 564  {
 565     StbTexteditRow r;
 566     int prev_start = 0;
 567     int z = STB_TEXTEDIT_STRINGLEN(str);
 568     int i=0, first;
 569  
 570     if (n == z && single_line) {
 571        // special case if it's at the end (may not be needed?)
 572        STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
 573        find->y = 0;
 574        find->first_char = 0;
 575        find->length = z;
 576        find->height = r.ymax - r.ymin;
 577        find->x = r.x1;
 578        return;
 579     }
 580  
 581     // search rows to find the one that straddles character n
 582     find->y = 0;
 583  
 584     for(;;) {
 585        STB_TEXTEDIT_LAYOUTROW(&r, str, i);
 586        if (n < i + r.num_chars)
 587           break;
 588        if (str->LastMoveDirectionLR == ImGuiDir_Right && str->Stb->cursor > 0 && str->Stb->cursor == i + r.num_chars && STB_TEXTEDIT_GETCHAR(str, i + r.num_chars - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] Wrapping point handling
 589           break;
 590        if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE)  // [DEAR IMGUI] special handling for last line
 591           break;   // [DEAR IMGUI]
 592        prev_start = i;
 593        i += r.num_chars;
 594        find->y += r.baseline_y_delta;
 595        if (i == z) // [DEAR IMGUI]
 596        {
 597           r.num_chars = 0; // [DEAR IMGUI]
 598           break;   // [DEAR IMGUI]
 599        }
 600     }
 601  
 602     find->first_char = first = i;
 603     find->length = r.num_chars;
 604     find->height = r.ymax - r.ymin;
 605     find->prev_first = prev_start;
 606  
 607     // now scan to find xpos
 608     find->x = r.x0;
 609     for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)
 610        find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
 611  }
 612  
 613  #define STB_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
 614  
 615  // make the selection/cursor state valid if client altered the string
 616  static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 617  {
 618     int n = STB_TEXTEDIT_STRINGLEN(str);
 619     if (STB_TEXT_HAS_SELECTION(state)) {
 620        if (state->select_start > n) state->select_start = n;
 621        if (state->select_end   > n) state->select_end = n;
 622        // if clamping forced them to be equal, move the cursor to match
 623        if (state->select_start == state->select_end)
 624           state->cursor = state->select_start;
 625     }
 626     if (state->cursor > n) state->cursor = n;
 627  }
 628  
 629  // delete characters while updating undo
 630  static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
 631  {
 632     stb_text_makeundo_delete(str, state, where, len);
 633     STB_TEXTEDIT_DELETECHARS(str, where, len);
 634     state->has_preferred_x = 0;
 635  }
 636  
 637  // delete the section
 638  static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 639  {
 640     stb_textedit_clamp(str, state);
 641     if (STB_TEXT_HAS_SELECTION(state)) {
 642        if (state->select_start < state->select_end) {
 643           stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
 644           state->select_end = state->cursor = state->select_start;
 645        } else {
 646           stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
 647           state->select_start = state->cursor = state->select_end;
 648        }
 649        state->has_preferred_x = 0;
 650     }
 651  }
 652  
 653  // canoncialize the selection so start <= end
 654  static void stb_textedit_sortselection(STB_TexteditState *state)
 655  {
 656     if (state->select_end < state->select_start) {
 657        int temp = state->select_end;
 658        state->select_end = state->select_start;
 659        state->select_start = temp;
 660     }
 661  }
 662  
 663  // move cursor to first character of selection
 664  static void stb_textedit_move_to_first(STB_TexteditState *state)
 665  {
 666     if (STB_TEXT_HAS_SELECTION(state)) {
 667        stb_textedit_sortselection(state);
 668        state->cursor = state->select_start;
 669        state->select_end = state->select_start;
 670        state->has_preferred_x = 0;
 671     }
 672  }
 673  
 674  // move cursor to last character of selection
 675  static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 676  {
 677     if (STB_TEXT_HAS_SELECTION(state)) {
 678        stb_textedit_sortselection(state);
 679        stb_textedit_clamp(str, state);
 680        state->cursor = state->select_end;
 681        state->select_start = state->select_end;
 682        state->has_preferred_x = 0;
 683     }
 684  }
 685  
 686  // [DEAR IMGUI] Extracted this function so we can more easily add support for word-wrapping.
 687  #ifndef STB_TEXTEDIT_MOVELINESTART
 688  static int stb_textedit_move_line_start(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
 689  {
 690     if (state->single_line)
 691        return 0;
 692     while (cursor > 0) {
 693        int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, cursor);
 694        if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
 695           break;
 696        cursor = prev;
 697     }
 698     return cursor;
 699  }
 700  #define STB_TEXTEDIT_MOVELINESTART stb_textedit_move_line_start
 701  #endif
 702  #ifndef STB_TEXTEDIT_MOVELINEEND
 703  static int stb_textedit_move_line_end(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int cursor)
 704  {
 705     int n = STB_TEXTEDIT_STRINGLEN(str);
 706     if (state->single_line)
 707        return n;
 708     while (cursor < n && STB_TEXTEDIT_GETCHAR(str, cursor) != STB_TEXTEDIT_NEWLINE)
 709        cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, cursor);
 710     return cursor;
 711  }
 712  #define STB_TEXTEDIT_MOVELINEEND stb_textedit_move_line_end
 713  #endif
 714  
 715  #ifdef STB_TEXTEDIT_IS_SPACE
 716  static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
 717  {
 718     return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
 719  }
 720  
 721  #ifndef STB_TEXTEDIT_MOVEWORDLEFT
 722  static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
 723  {
 724     c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
 725     while (c >= 0 && !is_word_boundary(str, c))
 726        c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
 727  
 728     if( c < 0 )
 729        c = 0;
 730  
 731     return c;
 732  }
 733  #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
 734  #endif
 735  
 736  #ifndef STB_TEXTEDIT_MOVEWORDRIGHT
 737  static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
 738  {
 739     const int len = STB_TEXTEDIT_STRINGLEN(str);
 740     c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
 741     while( c < len && !is_word_boundary( str, c ) )
 742        c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
 743  
 744     if( c > len )
 745        c = len;
 746  
 747     return c;
 748  }
 749  #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
 750  #endif
 751  
 752  #endif
 753  
 754  // update selection and cursor to match each other
 755  static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
 756  {
 757     if (!STB_TEXT_HAS_SELECTION(state))
 758        state->select_start = state->select_end = state->cursor;
 759     else
 760        state->cursor = state->select_end;
 761  }
 762  
 763  // API cut: delete selection
 764  static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 765  {
 766     if (STB_TEXT_HAS_SELECTION(state)) {
 767        stb_textedit_delete_selection(str,state); // implicitly clamps
 768        state->has_preferred_x = 0;
 769        return 1;
 770     }
 771     return 0;
 772  }
 773  
 774  // API paste: replace existing selection with passed-in text
 775  static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len)
 776  {
 777     // if there's a selection, the paste should delete it
 778     stb_textedit_clamp(str, state);
 779     stb_textedit_delete_selection(str,state);
 780     // try to insert the characters
 781     len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len);
 782     if (len) {
 783        stb_text_makeundo_insert(state, state->cursor, len);
 784        state->cursor += len;
 785        state->has_preferred_x = 0;
 786        return 1;
 787     }
 788     // note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details)
 789     return 0;
 790  }
 791  
 792  #ifndef STB_TEXTEDIT_KEYTYPE
 793  #define STB_TEXTEDIT_KEYTYPE int
 794  #endif
 795  
 796  // API key: process text input
 797  // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
 798  static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
 799  {
 800     // can't add newline in single-line mode
 801     if (text[0] == '\n' && state->single_line)
 802        return;
 803  
 804     if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
 805        stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
 806        STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
 807        text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
 808        if (text_len) {
 809           state->cursor += text_len;
 810           state->has_preferred_x = 0;
 811        }
 812     } else {
 813        stb_textedit_delete_selection(str, state); // implicitly clamps
 814        text_len = STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len);
 815        if (text_len) {
 816           stb_text_makeundo_insert(state, state->cursor, text_len);
 817           state->cursor += text_len;
 818           state->has_preferred_x = 0;
 819        }
 820     }
 821  }
 822  
 823  // API key: process a keyboard input
 824  static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
 825  {
 826  retry:
 827     switch (key) {
 828        default: {
 829  #ifdef STB_TEXTEDIT_KEYTOTEXT
 830           // This is not suitable for UTF-8 support.
 831           int c = STB_TEXTEDIT_KEYTOTEXT(key);
 832           if (c > 0) {
 833              IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
 834              stb_textedit_text(str, state, &ch, 1);
 835           }
 836  #endif
 837           break;
 838        }
 839  
 840  #ifdef STB_TEXTEDIT_K_INSERT
 841        case STB_TEXTEDIT_K_INSERT:
 842           state->insert_mode = !state->insert_mode;
 843           break;
 844  #endif
 845  
 846        case STB_TEXTEDIT_K_UNDO:
 847           stb_text_undo(str, state);
 848           state->has_preferred_x = 0;
 849           break;
 850  
 851        case STB_TEXTEDIT_K_REDO:
 852           stb_text_redo(str, state);
 853           state->has_preferred_x = 0;
 854           break;
 855  
 856        case STB_TEXTEDIT_K_LEFT:
 857           // if currently there's a selection, move cursor to start of selection
 858           if (STB_TEXT_HAS_SELECTION(state))
 859              stb_textedit_move_to_first(state);
 860           else
 861              if (state->cursor > 0)
 862                 state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
 863           state->has_preferred_x = 0;
 864           break;
 865  
 866        case STB_TEXTEDIT_K_RIGHT:
 867           // if currently there's a selection, move cursor to end of selection
 868           if (STB_TEXT_HAS_SELECTION(state))
 869              stb_textedit_move_to_last(str, state);
 870           else
 871              state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
 872           stb_textedit_clamp(str, state);
 873           state->has_preferred_x = 0;
 874           break;
 875  
 876        case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
 877           stb_textedit_clamp(str, state);
 878           stb_textedit_prep_selection_at_cursor(state);
 879           // move selection left
 880           if (state->select_end > 0)
 881              state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);
 882           state->cursor = state->select_end;
 883           state->has_preferred_x = 0;
 884           break;
 885  
 886  #ifdef STB_TEXTEDIT_MOVEWORDLEFT
 887        case STB_TEXTEDIT_K_WORDLEFT:
 888           if (STB_TEXT_HAS_SELECTION(state))
 889              stb_textedit_move_to_first(state);
 890           else {
 891              state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
 892              stb_textedit_clamp( str, state );
 893           }
 894           break;
 895  
 896        case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
 897           if( !STB_TEXT_HAS_SELECTION( state ) )
 898              stb_textedit_prep_selection_at_cursor(state);
 899  
 900           state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
 901           state->select_end = state->cursor;
 902  
 903           stb_textedit_clamp( str, state );
 904           break;
 905  #endif
 906  
 907  #ifdef STB_TEXTEDIT_MOVEWORDRIGHT
 908        case STB_TEXTEDIT_K_WORDRIGHT:
 909           if (STB_TEXT_HAS_SELECTION(state))
 910              stb_textedit_move_to_last(str, state);
 911           else {
 912              state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
 913              stb_textedit_clamp( str, state );
 914           }
 915           break;
 916  
 917        case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
 918           if( !STB_TEXT_HAS_SELECTION( state ) )
 919              stb_textedit_prep_selection_at_cursor(state);
 920  
 921           state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
 922           state->select_end = state->cursor;
 923  
 924           stb_textedit_clamp( str, state );
 925           break;
 926  #endif
 927  
 928        case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
 929           stb_textedit_prep_selection_at_cursor(state);
 930           // move selection right
 931           state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);
 932           stb_textedit_clamp(str, state);
 933           state->cursor = state->select_end;
 934           state->has_preferred_x = 0;
 935           break;
 936  
 937        case STB_TEXTEDIT_K_DOWN:
 938        case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:
 939        case STB_TEXTEDIT_K_PGDOWN:
 940        case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {
 941           StbFindState find;
 942           StbTexteditRow row;
 943           int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
 944           int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;
 945           int row_count = is_page ? state->row_count_per_page : 1;
 946  
 947           if (!is_page && state->single_line) {
 948              // on windows, up&down in single-line behave like left&right
 949              key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
 950              goto retry;
 951           }
 952  
 953           if (sel)
 954              stb_textedit_prep_selection_at_cursor(state);
 955           else if (STB_TEXT_HAS_SELECTION(state))
 956              stb_textedit_move_to_last(str, state);
 957  
 958           // compute current position of cursor point
 959           stb_textedit_clamp(str, state);
 960           stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
 961  
 962           for (j = 0; j < row_count; ++j) {
 963              float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
 964              int start = find.first_char + find.length;
 965  
 966              if (find.length == 0)
 967                 break;
 968  
 969              // [DEAR IMGUI]
 970              // going down while being on the last line shouldn't bring us to that line end
 971              //if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
 972              //   break;
 973  
 974              // now find character position down a row
 975              state->cursor = start;
 976              STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
 977              x = row.x0;
 978              for (i=0; i < row.num_chars; ) {
 979                 float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
 980                 int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
 981                 #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
 982                 if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
 983                    break;
 984                 #endif
 985                 x += dx;
 986                 if (x > goal_x)
 987                    break;
 988                 i += next - state->cursor;
 989                 state->cursor = next;
 990              }
 991              stb_textedit_clamp(str, state);
 992  
 993              if (state->cursor == find.first_char + find.length)
 994                 str->LastMoveDirectionLR = ImGuiDir_Left;
 995              state->has_preferred_x = 1;
 996              state->preferred_x = goal_x;
 997  
 998              if (sel)
 999                 state->select_end = state->cursor;
1000  
1001              // go to next line
1002              find.first_char = find.first_char + find.length;
1003              find.length = row.num_chars;
1004           }
1005           break;
1006        }
1007  
1008        case STB_TEXTEDIT_K_UP:
1009        case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:
1010        case STB_TEXTEDIT_K_PGUP:
1011        case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {
1012           StbFindState find;
1013           StbTexteditRow row;
1014           int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
1015           int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;
1016           int row_count = is_page ? state->row_count_per_page : 1;
1017  
1018           if (!is_page && state->single_line) {
1019              // on windows, up&down become left&right
1020              key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
1021              goto retry;
1022           }
1023  
1024           if (sel)
1025              stb_textedit_prep_selection_at_cursor(state);
1026           else if (STB_TEXT_HAS_SELECTION(state))
1027              stb_textedit_move_to_first(state);
1028  
1029           // compute current position of cursor point
1030           stb_textedit_clamp(str, state);
1031           stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
1032  
1033           for (j = 0; j < row_count; ++j) {
1034              float  x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
1035  
1036              // can only go up if there's a previous row
1037              if (find.prev_first == find.first_char)
1038                 break;
1039  
1040              // now find character position up a row
1041              state->cursor = find.prev_first;
1042              STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
1043              x = row.x0;
1044              for (i=0; i < row.num_chars; ) {
1045                 float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
1046                 int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
1047                 #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
1048                 if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
1049                    break;
1050                 #endif
1051                 x += dx;
1052                 if (x > goal_x)
1053                    break;
1054                 i += next - state->cursor;
1055                 state->cursor = next;
1056              }
1057              stb_textedit_clamp(str, state);
1058  
1059              if (state->cursor == find.first_char)
1060                 str->LastMoveDirectionLR = ImGuiDir_Right;
1061              else if (state->cursor == find.prev_first)
1062                 str->LastMoveDirectionLR = ImGuiDir_Left;
1063              state->has_preferred_x = 1;
1064              state->preferred_x = goal_x;
1065  
1066              if (sel)
1067                 state->select_end = state->cursor;
1068  
1069              // go to previous line
1070              // (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
1071              prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
1072              while (prev_scan > 0)
1073              {
1074                 int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
1075                 if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
1076                    break;
1077                 prev_scan = prev;
1078              }
1079              find.first_char = find.prev_first;
1080              find.prev_first = STB_TEXTEDIT_MOVELINESTART(str, state, prev_scan);
1081           }
1082           break;
1083        }
1084  
1085        case STB_TEXTEDIT_K_DELETE:
1086        case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
1087           if (STB_TEXT_HAS_SELECTION(state))
1088              stb_textedit_delete_selection(str, state);
1089           else {
1090              int n = STB_TEXTEDIT_STRINGLEN(str);
1091              if (state->cursor < n)
1092                 stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);
1093           }
1094           state->has_preferred_x = 0;
1095           break;
1096  
1097        case STB_TEXTEDIT_K_BACKSPACE:
1098        case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
1099           if (STB_TEXT_HAS_SELECTION(state))
1100              stb_textedit_delete_selection(str, state);
1101           else {
1102              stb_textedit_clamp(str, state);
1103              if (state->cursor > 0) {
1104                 int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
1105                 stb_textedit_delete(str, state, prev, state->cursor - prev);
1106                 state->cursor = prev;
1107              }
1108           }
1109           state->has_preferred_x = 0;
1110           break;
1111  
1112  #ifdef STB_TEXTEDIT_K_TEXTSTART2
1113        case STB_TEXTEDIT_K_TEXTSTART2:
1114  #endif
1115        case STB_TEXTEDIT_K_TEXTSTART:
1116           state->cursor = state->select_start = state->select_end = 0;
1117           state->has_preferred_x = 0;
1118           break;
1119  
1120  #ifdef STB_TEXTEDIT_K_TEXTEND2
1121        case STB_TEXTEDIT_K_TEXTEND2:
1122  #endif
1123        case STB_TEXTEDIT_K_TEXTEND:
1124           state->cursor = STB_TEXTEDIT_STRINGLEN(str);
1125           state->select_start = state->select_end = 0;
1126           state->has_preferred_x = 0;
1127           break;
1128  
1129  #ifdef STB_TEXTEDIT_K_TEXTSTART2
1130        case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:
1131  #endif
1132        case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
1133           stb_textedit_prep_selection_at_cursor(state);
1134           state->cursor = state->select_end = 0;
1135           state->has_preferred_x = 0;
1136           break;
1137  
1138  #ifdef STB_TEXTEDIT_K_TEXTEND2
1139        case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:
1140  #endif
1141        case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
1142           stb_textedit_prep_selection_at_cursor(state);
1143           state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
1144           state->has_preferred_x = 0;
1145           break;
1146  
1147  
1148  #ifdef STB_TEXTEDIT_K_LINESTART2
1149        case STB_TEXTEDIT_K_LINESTART2:
1150  #endif
1151        case STB_TEXTEDIT_K_LINESTART:
1152           stb_textedit_clamp(str, state);
1153           stb_textedit_move_to_first(state);
1154           state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
1155           state->has_preferred_x = 0;
1156           break;
1157  
1158  #ifdef STB_TEXTEDIT_K_LINEEND2
1159        case STB_TEXTEDIT_K_LINEEND2:
1160  #endif
1161        case STB_TEXTEDIT_K_LINEEND: {
1162           stb_textedit_clamp(str, state);
1163           stb_textedit_move_to_last(str, state);
1164           state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
1165           state->has_preferred_x = 0;
1166           break;
1167        }
1168  
1169  #ifdef STB_TEXTEDIT_K_LINESTART2
1170        case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:
1171  #endif
1172        case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
1173           stb_textedit_clamp(str, state);
1174           stb_textedit_prep_selection_at_cursor(state);
1175           state->cursor = STB_TEXTEDIT_MOVELINESTART(str, state, state->cursor);
1176           state->select_end = state->cursor;
1177           state->has_preferred_x = 0;
1178           break;
1179  
1180  #ifdef STB_TEXTEDIT_K_LINEEND2
1181        case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
1182  #endif
1183        case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
1184           stb_textedit_clamp(str, state);
1185           stb_textedit_prep_selection_at_cursor(state);
1186           state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
1187           state->select_end = state->cursor;
1188           state->has_preferred_x = 0;
1189           break;
1190        }
1191     }
1192  }
1193  
1194  /////////////////////////////////////////////////////////////////////////////
1195  //
1196  //      Undo processing
1197  //
1198  // @OPTIMIZE: the undo/redo buffer should be circular
1199  
1200  static void stb_textedit_flush_redo(StbUndoState *state)
1201  {
1202     state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;
1203     state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;
1204  }
1205  
1206  // discard the oldest entry in the undo list
1207  static void stb_textedit_discard_undo(StbUndoState *state)
1208  {
1209     if (state->undo_point > 0) {
1210        // if the 0th undo state has characters, clean those up
1211        if (state->undo_rec[0].char_storage >= 0) {
1212           int n = state->undo_rec[0].insert_length, i;
1213           // delete n characters from all other records
1214           state->undo_char_point -= n;
1215           IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));
1216           for (i=0; i < state->undo_point; ++i)
1217              if (state->undo_rec[i].char_storage >= 0)
1218                 state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it
1219        }
1220        --state->undo_point;
1221        IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
1222     }
1223  }
1224  
1225  // discard the oldest entry in the redo list--it's bad if this
1226  // ever happens, but because undo & redo have to store the actual
1227  // characters in different cases, the redo character buffer can
1228  // fill up even though the undo buffer didn't
1229  static void stb_textedit_discard_redo(StbUndoState *state)
1230  {
1231     int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1;
1232  
1233     if (state->redo_point <= k) {
1234        // if the k'th undo state has characters, clean those up
1235        if (state->undo_rec[k].char_storage >= 0) {
1236           int n = state->undo_rec[k].insert_length, i;
1237           // move the remaining redo character data to the end of the buffer
1238           state->redo_char_point += n;
1239           IMSTB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((IMSTB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));
1240           // adjust the position of all the other records to account for above memmove
1241           for (i=state->redo_point; i < k; ++i)
1242              if (state->undo_rec[i].char_storage >= 0)
1243                 state->undo_rec[i].char_storage += n;
1244        }
1245        // now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
1246        // [DEAR IMGUI]
1247        size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
1248        const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;
1249        const char* buf_end   = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;
1250        IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);
1251        IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);
1252        IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);
1253  
1254        // now move redo_point to point to the new one
1255        ++state->redo_point;
1256     }
1257  }
1258  
1259  static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
1260  {
1261     // any time we create a new undo record, we discard redo
1262     stb_textedit_flush_redo(state);
1263  
1264     // if we have no free records, we have to make room, by sliding the
1265     // existing records down
1266     if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
1267        stb_textedit_discard_undo(state);
1268  
1269     // if the characters to store won't possibly fit in the buffer, we can't undo
1270     if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) {
1271        state->undo_point = 0;
1272        state->undo_char_point = 0;
1273        return NULL;
1274     }
1275  
1276     // if we don't have enough free characters in the buffer, we have to make room
1277     while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT)
1278        stb_textedit_discard_undo(state);
1279  
1280     return &state->undo_rec[state->undo_point++];
1281  }
1282  
1283  static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
1284  {
1285     StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
1286     if (r == NULL)
1287        return NULL;
1288  
1289     r->where = pos;
1290     r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len;
1291     r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len;
1292  
1293     if (insert_len == 0) {
1294        r->char_storage = -1;
1295        return NULL;
1296     } else {
1297        r->char_storage = state->undo_char_point;
1298        state->undo_char_point += insert_len;
1299        return &state->undo_char[r->char_storage];
1300     }
1301  }
1302  
1303  static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1304  {
1305     StbUndoState *s = &state->undostate;
1306     StbUndoRecord u, *r;
1307     if (s->undo_point == 0)
1308        return;
1309  
1310     // we need to do two things: apply the undo record, and create a redo record
1311     u = s->undo_rec[s->undo_point-1];
1312     r = &s->undo_rec[s->redo_point-1];
1313     r->char_storage = -1;
1314  
1315     r->insert_length = u.delete_length;
1316     r->delete_length = u.insert_length;
1317     r->where = u.where;
1318  
1319     if (u.delete_length) {
1320        // if the undo record says to delete characters, then the redo record will
1321        // need to re-insert the characters that get deleted, so we need to store
1322        // them.
1323  
1324        // there are three cases:
1325        //    there's enough room to store the characters
1326        //    characters stored for *redoing* don't leave room for redo
1327        //    characters stored for *undoing* don't leave room for redo
1328        // if the last is true, we have to bail
1329  
1330        if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) {
1331           // the undo records take up too much character space; there's no space to store the redo characters
1332           r->insert_length = 0;
1333        } else {
1334           int i;
1335  
1336           // there's definitely room to store the characters eventually
1337           while (s->undo_char_point + u.delete_length > s->redo_char_point) {
1338              // should never happen:
1339              if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
1340                 return;
1341              // there's currently not enough room, so discard a redo record
1342              stb_textedit_discard_redo(s);
1343           }
1344           r = &s->undo_rec[s->redo_point-1];
1345  
1346           r->char_storage = s->redo_char_point - u.delete_length;
1347           s->redo_char_point = s->redo_char_point - u.delete_length;
1348  
1349           // now save the characters
1350           for (i=0; i < u.delete_length; ++i)
1351              s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
1352        }
1353  
1354        // now we can carry out the deletion
1355        STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
1356     }
1357  
1358     // check type of recorded action:
1359     if (u.insert_length) {
1360        // easy case: was a deletion, so we need to insert n characters
1361        u.insert_length = STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
1362        s->undo_char_point -= u.insert_length;
1363     }
1364  
1365     state->cursor = u.where + u.insert_length;
1366  
1367     s->undo_point--;
1368     s->redo_point--;
1369  }
1370  
1371  static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1372  {
1373     StbUndoState *s = &state->undostate;
1374     StbUndoRecord *u, r;
1375     if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)
1376        return;
1377  
1378     // we need to do two things: apply the redo record, and create an undo record
1379     u = &s->undo_rec[s->undo_point];
1380     r = s->undo_rec[s->redo_point];
1381  
1382     // we KNOW there must be room for the undo record, because the redo record
1383     // was derived from an undo record
1384  
1385     u->delete_length = r.insert_length;
1386     u->insert_length = r.delete_length;
1387     u->where = r.where;
1388     u->char_storage = -1;
1389  
1390     if (r.delete_length) {
1391        // the redo record requires us to delete characters, so the undo record
1392        // needs to store the characters
1393  
1394        if (s->undo_char_point + u->insert_length > s->redo_char_point) {
1395           u->insert_length = 0;
1396           u->delete_length = 0;
1397        } else {
1398           int i;
1399           u->char_storage = s->undo_char_point;
1400           s->undo_char_point = s->undo_char_point + u->insert_length;
1401  
1402           // now save the characters
1403           for (i=0; i < u->insert_length; ++i)
1404              s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
1405        }
1406  
1407        STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
1408     }
1409  
1410     if (r.insert_length) {
1411        // easy case: need to insert n characters
1412        r.insert_length = STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
1413        s->redo_char_point += r.insert_length;
1414     }
1415  
1416     state->cursor = r.where + r.insert_length;
1417  
1418     s->undo_point++;
1419     s->redo_point++;
1420  }
1421  
1422  static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
1423  {
1424     stb_text_createundo(&state->undostate, where, 0, length);
1425  }
1426  
1427  static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
1428  {
1429     int i;
1430     IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
1431     if (p) {
1432        for (i=0; i < length; ++i)
1433           p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1434     }
1435  }
1436  
1437  static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
1438  {
1439     int i;
1440     IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
1441     if (p) {
1442        for (i=0; i < old_length; ++i)
1443           p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1444     }
1445  }
1446  
1447  // reset the state to default
1448  static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
1449  {
1450     state->undostate.undo_point = 0;
1451     state->undostate.undo_char_point = 0;
1452     state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;
1453     state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;
1454     state->select_end = state->select_start = 0;
1455     state->cursor = 0;
1456     state->has_preferred_x = 0;
1457     state->preferred_x = 0;
1458     state->cursor_at_end_of_line = 0;
1459     state->initialized = 1;
1460     state->single_line = (unsigned char) is_single_line;
1461     state->insert_mode = 0;
1462     state->row_count_per_page = 0;
1463  }
1464  
1465  // API initialize
1466  static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
1467  {
1468     stb_textedit_clear_state(state, is_single_line);
1469  }
1470  
1471  #if defined(__GNUC__) || defined(__clang__)
1472  #pragma GCC diagnostic push
1473  #pragma GCC diagnostic ignored "-Wcast-qual"
1474  #endif
1475  
1476  static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len)
1477  {
1478     return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len);
1479  }
1480  
1481  #if defined(__GNUC__) || defined(__clang__)
1482  #pragma GCC diagnostic pop
1483  #endif
1484  
1485  #endif//IMSTB_TEXTEDIT_IMPLEMENTATION
1486  
1487  /*
1488  ------------------------------------------------------------------------------
1489  This software is available under 2 licenses -- choose whichever you prefer.
1490  ------------------------------------------------------------------------------
1491  ALTERNATIVE A - MIT License
1492  Copyright (c) 2017 Sean Barrett
1493  Permission is hereby granted, free of charge, to any person obtaining a copy of
1494  this software and associated documentation files (the "Software"), to deal in
1495  the Software without restriction, including without limitation the rights to
1496  use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1497  of the Software, and to permit persons to whom the Software is furnished to do
1498  so, subject to the following conditions:
1499  The above copyright notice and this permission notice shall be included in all
1500  copies or substantial portions of the Software.
1501  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1502  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1503  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1504  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1505  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1506  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1507  SOFTWARE.
1508  ------------------------------------------------------------------------------
1509  ALTERNATIVE B - Public Domain (www.unlicense.org)
1510  This is free and unencumbered software released into the public domain.
1511  Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
1512  software, either in source code form or as a compiled binary, for any purpose,
1513  commercial or non-commercial, and by any means.
1514  In jurisdictions that recognize copyright laws, the author or authors of this
1515  software dedicate any and all copyright interest in the software to the public
1516  domain. We make this dedication for the benefit of the public at large and to
1517  the detriment of our heirs and successors. We intend this dedication to be an
1518  overt act of relinquishment in perpetuity of all present and future rights to
1519  this software under copyright law.
1520  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1521  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1522  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1523  AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1524  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1525  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1526  ------------------------------------------------------------------------------
1527  */