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