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