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