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