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