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