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 ////
267 //// Header-file mode
268 ////
269 ////
270
271 #ifndef INCLUDE_STB_TEXTEDIT_H
272 #define INCLUDE_STB_TEXTEDIT_H
273
274 ////////////////////////////////////////////////////////////////////////
275 //
276 // STB_TexteditState
277 //
278 // Definition of STB_TexteditState which you should store
279 // per-textfield; it includes cursor position, selection state,
280 // and undo state.
281 //
282
283 #ifndef STB_TEXTEDIT_UNDOSTATECOUNT
284 #define STB_TEXTEDIT_UNDOSTATECOUNT 99
285 #endif
286 #ifndef STB_TEXTEDIT_UNDOCHARCOUNT
287 #define STB_TEXTEDIT_UNDOCHARCOUNT 999
288 #endif
289 #ifndef STB_TEXTEDIT_CHARTYPE
290 #define STB_TEXTEDIT_CHARTYPE int
291 #endif
292 #ifndef STB_TEXTEDIT_POSITIONTYPE
293 #define STB_TEXTEDIT_POSITIONTYPE int
294 #endif
295
296 typedef struct
297 {
298 // private data
299 STB_TEXTEDIT_POSITIONTYPE where;
300 short insert_length;
301 short delete_length;
302 short char_storage;
303 } StbUndoRecord;
304
305 typedef struct
306 {
307 // private data
308 StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
309 STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
310 short undo_point, redo_point;
311 short undo_char_point, redo_char_point;
312 } StbUndoState;
313
314 typedef struct
315 {
316 /////////////////////
317 //
318 // public data
319 //
320
321 int cursor;
322 // position of the text cursor within the string
323
324 int select_start; // selection start point
325 int select_end;
326 // selection start and end point in characters; if equal, no selection.
327 // note that start may be less than or greater than end (e.g. when
328 // dragging the mouse, start is where the initial click was, and you
329 // can drag in either direction)
330
331 unsigned char insert_mode;
332 // each textfield keeps its own insert mode state. to keep an app-wide
333 // insert mode, copy this value in/out of the app state
334
335 /////////////////////
336 //
337 // private data
338 //
339 unsigned char cursor_at_end_of_line; // not implemented yet
340 unsigned char initialized;
341 unsigned char has_preferred_x;
342 unsigned char single_line;
343 unsigned char padding1, padding2, padding3;
344 float preferred_x; // this determines where the cursor up/down tries to seek to along x
345 StbUndoState undostate;
346 } STB_TexteditState;
347
348
349 ////////////////////////////////////////////////////////////////////////
350 //
351 // StbTexteditRow
352 //
353 // Result of layout query, used by stb_textedit to determine where
354 // the text in each row is.
355
356 // result of layout query
357 typedef struct
358 {
359 float x0,x1; // starting x location, end x location (allows for align=right, etc)
360 float baseline_y_delta; // position of baseline relative to previous row's baseline
361 float ymin,ymax; // height of row above and below baseline
362 int num_chars;
363 } StbTexteditRow;
364 #endif //INCLUDE_STB_TEXTEDIT_H
365
366
367 ////////////////////////////////////////////////////////////////////////////
368 ////////////////////////////////////////////////////////////////////////////
369 ////
370 //// Implementation mode
371 ////
372 ////
373
374
375 // implementation isn't include-guarded, since it might have indirectly
376 // included just the "header" portion
377 #ifdef STB_TEXTEDIT_IMPLEMENTATION
378
379 #ifndef STB_TEXTEDIT_memmove
380 #include <string.h>
381 #define STB_TEXTEDIT_memmove memmove
382 #endif
383
384
385 /////////////////////////////////////////////////////////////////////////////
386 //
387 // Mouse input handling
388 //
389
390 // traverse the layout to locate the nearest character to a display position
stb_text_locate_coord(STB_TEXTEDIT_STRING * str,float x,float y)391 static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
392 {
393 StbTexteditRow r;
394 int n = STB_TEXTEDIT_STRINGLEN(str);
395 float base_y = 0, prev_x;
396 int i=0, k;
397
398 r.x0 = r.x1 = 0;
399 r.ymin = r.ymax = 0;
400 r.num_chars = 0;
401
402 // search rows to find one that straddles 'y'
403 while (i < n) {
404 STB_TEXTEDIT_LAYOUTROW(&r, str, i);
405 if (r.num_chars <= 0)
406 return n;
407
408 if (i==0 && y < base_y + r.ymin)
409 return 0;
410
411 if (y < base_y + r.ymax)
412 break;
413
414 i += r.num_chars;
415 base_y += r.baseline_y_delta;
416 }
417
418 // below all text, return 'after' last character
419 if (i >= n)
420 return n;
421
422 // check if it's before the beginning of the line
423 if (x < r.x0)
424 return i;
425
426 // check if it's before the end of the line
427 if (x < r.x1) {
428 // search characters in row for one that straddles 'x'
429 prev_x = r.x0;
430 for (k=0; k < r.num_chars; ++k) {
431 float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
432 if (x < prev_x+w) {
433 if (x < prev_x+w/2)
434 return k+i;
435 else
436 return k+i+1;
437 }
438 prev_x += w;
439 }
440 // shouldn't happen, but if it does, fall through to end-of-line case
441 }
442
443 // if the last character is a newline, return that. otherwise return 'after' the last character
444 if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
445 return i+r.num_chars-1;
446 else
447 return i+r.num_chars;
448 }
449
450 // 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)451 static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
452 {
453 state->cursor = stb_text_locate_coord(str, x, y);
454 state->select_start = state->cursor;
455 state->select_end = state->cursor;
456 state->has_preferred_x = 0;
457 }
458
459 // 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)460 static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
461 {
462 int p = stb_text_locate_coord(str, x, y);
463 if (state->select_start == state->select_end)
464 state->select_start = state->cursor;
465 state->cursor = state->select_end = p;
466 }
467
468 /////////////////////////////////////////////////////////////////////////////
469 //
470 // Keyboard input handling
471 //
472
473 // forward declarations
474 static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
475 static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
476 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
477 static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
478 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
479
480 typedef struct
481 {
482 float x,y; // position of n'th character
483 float height; // height of line
484 int first_char, length; // first char of row, and length
485 int prev_first; // first char of previous row
486 } StbFindState;
487
488 // find the x/y location of a character, and remember info about the previous row in
489 // 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)490 static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
491 {
492 StbTexteditRow r;
493 int prev_start = 0;
494 int z = STB_TEXTEDIT_STRINGLEN(str);
495 int i=0, first;
496
497 if (n == z) {
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 STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
502 find->y = 0;
503 find->first_char = 0;
504 find->length = z;
505 find->height = r.ymax - r.ymin;
506 find->x = r.x1;
507 } else {
508 find->y = 0;
509 find->x = 0;
510 find->height = 1;
511 while (i < z) {
512 STB_TEXTEDIT_LAYOUTROW(&r, str, i);
513 prev_start = i;
514 i += r.num_chars;
515 }
516 find->first_char = i;
517 find->length = 0;
518 find->prev_first = prev_start;
519 }
520 return;
521 }
522
523 // search rows to find the one that straddles character n
524 find->y = 0;
525
526 for(;;) {
527 STB_TEXTEDIT_LAYOUTROW(&r, str, i);
528 if (n < i + r.num_chars)
529 break;
530 prev_start = i;
531 i += r.num_chars;
532 find->y += r.baseline_y_delta;
533 }
534
535 find->first_char = first = i;
536 find->length = r.num_chars;
537 find->height = r.ymax - r.ymin;
538 find->prev_first = prev_start;
539
540 // now scan to find xpos
541 find->x = r.x0;
542 i = 0;
543 for (i=0; first+i < n; ++i)
544 find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
545 }
546
547 #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)
548
549 // make the selection/cursor state valid if client altered the string
stb_textedit_clamp(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)550 static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
551 {
552 int n = STB_TEXTEDIT_STRINGLEN(str);
553 if (STB_TEXT_HAS_SELECTION(state)) {
554 if (state->select_start > n) state->select_start = n;
555 if (state->select_end > n) state->select_end = n;
556 // if clamping forced them to be equal, move the cursor to match
557 if (state->select_start == state->select_end)
558 state->cursor = state->select_start;
559 }
560 if (state->cursor > n) state->cursor = n;
561 }
562
563 // delete characters while updating undo
stb_textedit_delete(STB_TEXTEDIT_STRING * str,STB_TexteditState * state,int where,int len)564 static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
565 {
566 stb_text_makeundo_delete(str, state, where, len);
567 STB_TEXTEDIT_DELETECHARS(str, where, len);
568 state->has_preferred_x = 0;
569 }
570
571 // delete the section
stb_textedit_delete_selection(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)572 static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
573 {
574 stb_textedit_clamp(str, state);
575 if (STB_TEXT_HAS_SELECTION(state)) {
576 if (state->select_start < state->select_end) {
577 stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
578 state->select_end = state->cursor = state->select_start;
579 } else {
580 stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
581 state->select_start = state->cursor = state->select_end;
582 }
583 state->has_preferred_x = 0;
584 }
585 }
586
587 // canoncialize the selection so start <= end
stb_textedit_sortselection(STB_TexteditState * state)588 static void stb_textedit_sortselection(STB_TexteditState *state)
589 {
590 if (state->select_end < state->select_start) {
591 int temp = state->select_end;
592 state->select_end = state->select_start;
593 state->select_start = temp;
594 }
595 }
596
597 // move cursor to first character of selection
stb_textedit_move_to_first(STB_TexteditState * state)598 static void stb_textedit_move_to_first(STB_TexteditState *state)
599 {
600 if (STB_TEXT_HAS_SELECTION(state)) {
601 stb_textedit_sortselection(state);
602 state->cursor = state->select_start;
603 state->select_end = state->select_start;
604 state->has_preferred_x = 0;
605 }
606 }
607
608 // move cursor to last character of selection
stb_textedit_move_to_last(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)609 static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
610 {
611 if (STB_TEXT_HAS_SELECTION(state)) {
612 stb_textedit_sortselection(state);
613 stb_textedit_clamp(str, state);
614 state->cursor = state->select_end;
615 state->select_start = state->select_end;
616 state->has_preferred_x = 0;
617 }
618 }
619
620 #ifdef STB_TEXTEDIT_IS_SPACE
is_word_boundary(STB_TEXTEDIT_STRING * str,int idx)621 static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx )
622 {
623 return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
624 }
625
626 #ifndef STB_TEXTEDIT_MOVEWORDLEFT
stb_textedit_move_to_word_previous(STB_TEXTEDIT_STRING * str,int c)627 static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
628 {
629 --c; // always move at least one character
630 while( c >= 0 && !is_word_boundary( str, c ) )
631 --c;
632
633 if( c < 0 )
634 c = 0;
635
636 return c;
637 }
638 #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
639 #endif
640
641 #ifndef STB_TEXTEDIT_MOVEWORDRIGHT
stb_textedit_move_to_word_next(STB_TEXTEDIT_STRING * str,int c)642 static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c )
643 {
644 const int len = STB_TEXTEDIT_STRINGLEN(str);
645 ++c; // always move at least one character
646 while( c < len && !is_word_boundary( str, c ) )
647 ++c;
648
649 if( c > len )
650 c = len;
651
652 return c;
653 }
654 #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
655 #endif
656
657 #endif
658
659 // update selection and cursor to match each other
stb_textedit_prep_selection_at_cursor(STB_TexteditState * state)660 static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
661 {
662 if (!STB_TEXT_HAS_SELECTION(state))
663 state->select_start = state->select_end = state->cursor;
664 else
665 state->cursor = state->select_end;
666 }
667
668 // API cut: delete selection
stb_textedit_cut(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)669 static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
670 {
671 if (STB_TEXT_HAS_SELECTION(state)) {
672 stb_textedit_delete_selection(str,state); // implicity clamps
673 state->has_preferred_x = 0;
674 return 1;
675 }
676 return 0;
677 }
678
679 // API paste: replace existing selection with passed-in text
stb_textedit_paste(STB_TEXTEDIT_STRING * str,STB_TexteditState * state,STB_TEXTEDIT_CHARTYPE const * text,int len)680 static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *text, int len)
681 {
682 // if there's a selection, the paste should delete it
683 stb_textedit_clamp(str, state);
684 stb_textedit_delete_selection(str,state);
685 // try to insert the characters
686 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
687 stb_text_makeundo_insert(state, state->cursor, len);
688 state->cursor += len;
689 state->has_preferred_x = 0;
690 return 1;
691 }
692 // remove the undo since we didn't actually insert the characters
693 if (state->undostate.undo_point)
694 --state->undostate.undo_point;
695 return 0;
696 }
697
698 // API key: process a keyboard input
stb_textedit_key(STB_TEXTEDIT_STRING * str,STB_TexteditState * state,int key)699 static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key)
700 {
701 retry:
702 switch (key) {
703 default: {
704 int c = STB_TEXTEDIT_KEYTOTEXT(key);
705 if (c > 0) {
706 STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
707
708 // can't add newline in single-line mode
709 if (c == '\n' && state->single_line)
710 break;
711
712 if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
713 stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
714 STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
715 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
716 ++state->cursor;
717 state->has_preferred_x = 0;
718 }
719 } else {
720 stb_textedit_delete_selection(str,state); // implicity clamps
721 if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
722 stb_text_makeundo_insert(state, state->cursor, 1);
723 ++state->cursor;
724 state->has_preferred_x = 0;
725 }
726 }
727 }
728 break;
729 }
730
731 #ifdef STB_TEXTEDIT_K_INSERT
732 case STB_TEXTEDIT_K_INSERT:
733 state->insert_mode = !state->insert_mode;
734 break;
735 #endif
736
737 case STB_TEXTEDIT_K_UNDO:
738 stb_text_undo(str, state);
739 state->has_preferred_x = 0;
740 break;
741
742 case STB_TEXTEDIT_K_REDO:
743 stb_text_redo(str, state);
744 state->has_preferred_x = 0;
745 break;
746
747 case STB_TEXTEDIT_K_LEFT:
748 // if currently there's a selection, move cursor to start of selection
749 if (STB_TEXT_HAS_SELECTION(state))
750 stb_textedit_move_to_first(state);
751 else
752 if (state->cursor > 0)
753 --state->cursor;
754 state->has_preferred_x = 0;
755 break;
756
757 case STB_TEXTEDIT_K_RIGHT:
758 // if currently there's a selection, move cursor to end of selection
759 if (STB_TEXT_HAS_SELECTION(state))
760 stb_textedit_move_to_last(str, state);
761 else
762 ++state->cursor;
763 stb_textedit_clamp(str, state);
764 state->has_preferred_x = 0;
765 break;
766
767 case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
768 stb_textedit_clamp(str, state);
769 stb_textedit_prep_selection_at_cursor(state);
770 // move selection left
771 if (state->select_end > 0)
772 --state->select_end;
773 state->cursor = state->select_end;
774 state->has_preferred_x = 0;
775 break;
776
777 #ifdef STB_TEXTEDIT_MOVEWORDLEFT
778 case STB_TEXTEDIT_K_WORDLEFT:
779 if (STB_TEXT_HAS_SELECTION(state))
780 stb_textedit_move_to_first(state);
781 else {
782 state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
783 stb_textedit_clamp( str, state );
784 }
785 break;
786
787 case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
788 if( !STB_TEXT_HAS_SELECTION( state ) )
789 stb_textedit_prep_selection_at_cursor(state);
790
791 state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
792 state->select_end = state->cursor;
793
794 stb_textedit_clamp( str, state );
795 break;
796 #endif
797
798 #ifdef STB_TEXTEDIT_MOVEWORDRIGHT
799 case STB_TEXTEDIT_K_WORDRIGHT:
800 if (STB_TEXT_HAS_SELECTION(state))
801 stb_textedit_move_to_last(str, state);
802 else {
803 state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
804 stb_textedit_clamp( str, state );
805 }
806 break;
807
808 case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
809 if( !STB_TEXT_HAS_SELECTION( state ) )
810 stb_textedit_prep_selection_at_cursor(state);
811
812 state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
813 state->select_end = state->cursor;
814
815 stb_textedit_clamp( str, state );
816 break;
817 #endif
818
819 case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
820 stb_textedit_prep_selection_at_cursor(state);
821 // move selection right
822 ++state->select_end;
823 stb_textedit_clamp(str, state);
824 state->cursor = state->select_end;
825 state->has_preferred_x = 0;
826 break;
827
828 case STB_TEXTEDIT_K_DOWN:
829 case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
830 StbFindState find;
831 StbTexteditRow row;
832 int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
833
834 if (state->single_line) {
835 // on windows, up&down in single-line behave like left&right
836 key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
837 goto retry;
838 }
839
840 if (sel)
841 stb_textedit_prep_selection_at_cursor(state);
842 else if (STB_TEXT_HAS_SELECTION(state))
843 stb_textedit_move_to_last(str,state);
844
845 // compute current position of cursor point
846 stb_textedit_clamp(str, state);
847 stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
848
849 // now find character position down a row
850 if (find.length) {
851 float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
852 float x;
853 int start = find.first_char + find.length;
854 state->cursor = start;
855 STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
856 x = row.x0;
857 for (i=0; i < row.num_chars; ++i) {
858 float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
859 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
860 if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
861 break;
862 #endif
863 x += dx;
864 if (x > goal_x)
865 break;
866 ++state->cursor;
867 }
868 stb_textedit_clamp(str, state);
869
870 state->has_preferred_x = 1;
871 state->preferred_x = goal_x;
872
873 if (sel)
874 state->select_end = state->cursor;
875 }
876 break;
877 }
878
879 case STB_TEXTEDIT_K_UP:
880 case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
881 StbFindState find;
882 StbTexteditRow row;
883 int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
884
885 if (state->single_line) {
886 // on windows, up&down become left&right
887 key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
888 goto retry;
889 }
890
891 if (sel)
892 stb_textedit_prep_selection_at_cursor(state);
893 else if (STB_TEXT_HAS_SELECTION(state))
894 stb_textedit_move_to_first(state);
895
896 // compute current position of cursor point
897 stb_textedit_clamp(str, state);
898 stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
899
900 // can only go up if there's a previous row
901 if (find.prev_first != find.first_char) {
902 // now find character position up a row
903 float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
904 float x;
905 state->cursor = find.prev_first;
906 STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
907 x = row.x0;
908 for (i=0; i < row.num_chars; ++i) {
909 float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
910 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
911 if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
912 break;
913 #endif
914 x += dx;
915 if (x > goal_x)
916 break;
917 ++state->cursor;
918 }
919 stb_textedit_clamp(str, state);
920
921 state->has_preferred_x = 1;
922 state->preferred_x = goal_x;
923
924 if (sel)
925 state->select_end = state->cursor;
926 }
927 break;
928 }
929
930 case STB_TEXTEDIT_K_DELETE:
931 case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
932 if (STB_TEXT_HAS_SELECTION(state))
933 stb_textedit_delete_selection(str, state);
934 else {
935 int n = STB_TEXTEDIT_STRINGLEN(str);
936 if (state->cursor < n)
937 stb_textedit_delete(str, state, state->cursor, 1);
938 }
939 state->has_preferred_x = 0;
940 break;
941
942 case STB_TEXTEDIT_K_BACKSPACE:
943 case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
944 if (STB_TEXT_HAS_SELECTION(state))
945 stb_textedit_delete_selection(str, state);
946 else {
947 stb_textedit_clamp(str, state);
948 if (state->cursor > 0) {
949 stb_textedit_delete(str, state, state->cursor-1, 1);
950 --state->cursor;
951 }
952 }
953 state->has_preferred_x = 0;
954 break;
955
956 #ifdef STB_TEXTEDIT_K_TEXTSTART2
957 case STB_TEXTEDIT_K_TEXTSTART2:
958 #endif
959 case STB_TEXTEDIT_K_TEXTSTART:
960 state->cursor = state->select_start = state->select_end = 0;
961 state->has_preferred_x = 0;
962 break;
963
964 #ifdef STB_TEXTEDIT_K_TEXTEND2
965 case STB_TEXTEDIT_K_TEXTEND2:
966 #endif
967 case STB_TEXTEDIT_K_TEXTEND:
968 state->cursor = STB_TEXTEDIT_STRINGLEN(str);
969 state->select_start = state->select_end = 0;
970 state->has_preferred_x = 0;
971 break;
972
973 #ifdef STB_TEXTEDIT_K_TEXTSTART2
974 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:
975 #endif
976 case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
977 stb_textedit_prep_selection_at_cursor(state);
978 state->cursor = state->select_end = 0;
979 state->has_preferred_x = 0;
980 break;
981
982 #ifdef STB_TEXTEDIT_K_TEXTEND2
983 case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:
984 #endif
985 case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
986 stb_textedit_prep_selection_at_cursor(state);
987 state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
988 state->has_preferred_x = 0;
989 break;
990
991
992 #ifdef STB_TEXTEDIT_K_LINESTART2
993 case STB_TEXTEDIT_K_LINESTART2:
994 #endif
995 case STB_TEXTEDIT_K_LINESTART:
996 stb_textedit_clamp(str, state);
997 stb_textedit_move_to_first(state);
998 if (state->single_line)
999 state->cursor = 0;
1000 else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
1001 --state->cursor;
1002 state->has_preferred_x = 0;
1003 break;
1004
1005 #ifdef STB_TEXTEDIT_K_LINEEND2
1006 case STB_TEXTEDIT_K_LINEEND2:
1007 #endif
1008 case STB_TEXTEDIT_K_LINEEND: {
1009 int n = STB_TEXTEDIT_STRINGLEN(str);
1010 stb_textedit_clamp(str, state);
1011 stb_textedit_move_to_first(state);
1012 if (state->single_line)
1013 state->cursor = n;
1014 else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
1015 ++state->cursor;
1016 state->has_preferred_x = 0;
1017 break;
1018 }
1019
1020 #ifdef STB_TEXTEDIT_K_LINESTART2
1021 case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:
1022 #endif
1023 case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
1024 stb_textedit_clamp(str, state);
1025 stb_textedit_prep_selection_at_cursor(state);
1026 if (state->single_line)
1027 state->cursor = 0;
1028 else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
1029 --state->cursor;
1030 state->select_end = state->cursor;
1031 state->has_preferred_x = 0;
1032 break;
1033
1034 #ifdef STB_TEXTEDIT_K_LINEEND2
1035 case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
1036 #endif
1037 case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
1038 int n = STB_TEXTEDIT_STRINGLEN(str);
1039 stb_textedit_clamp(str, state);
1040 stb_textedit_prep_selection_at_cursor(state);
1041 if (state->single_line)
1042 state->cursor = n;
1043 else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
1044 ++state->cursor;
1045 state->select_end = state->cursor;
1046 state->has_preferred_x = 0;
1047 break;
1048 }
1049
1050 // @TODO:
1051 // STB_TEXTEDIT_K_PGUP - move cursor up a page
1052 // STB_TEXTEDIT_K_PGDOWN - move cursor down a page
1053 }
1054 }
1055
1056 /////////////////////////////////////////////////////////////////////////////
1057 //
1058 // Undo processing
1059 //
1060 // @OPTIMIZE: the undo/redo buffer should be circular
1061
stb_textedit_flush_redo(StbUndoState * state)1062 static void stb_textedit_flush_redo(StbUndoState *state)
1063 {
1064 state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
1065 state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
1066 }
1067
1068 // discard the oldest entry in the undo list
stb_textedit_discard_undo(StbUndoState * state)1069 static void stb_textedit_discard_undo(StbUndoState *state)
1070 {
1071 if (state->undo_point > 0) {
1072 // if the 0th undo state has characters, clean those up
1073 if (state->undo_rec[0].char_storage >= 0) {
1074 int n = state->undo_rec[0].insert_length, i;
1075 // delete n characters from all other records
1076 state->undo_char_point = state->undo_char_point - (short) n; // vsnet05
1077 STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) ((size_t)state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
1078 for (i=0; i < state->undo_point; ++i)
1079 if (state->undo_rec[i].char_storage >= 0)
1080 state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it
1081 }
1082 --state->undo_point;
1083 STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) ((size_t)state->undo_point*sizeof(state->undo_rec[0])));
1084 }
1085 }
1086
1087 // discard the oldest entry in the redo list--it's bad if this
1088 // ever happens, but because undo & redo have to store the actual
1089 // characters in different cases, the redo character buffer can
1090 // fill up even though the undo buffer didn't
stb_textedit_discard_redo(StbUndoState * state)1091 static void stb_textedit_discard_redo(StbUndoState *state)
1092 {
1093 int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
1094
1095 if (state->redo_point <= k) {
1096 // if the k'th undo state has characters, clean those up
1097 if (state->undo_rec[k].char_storage >= 0) {
1098 int n = state->undo_rec[k].insert_length, i;
1099 // delete n characters from all other records
1100 state->redo_char_point = state->redo_char_point + (short) n; // vsnet05
1101 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)));
1102 for (i=state->redo_point; i < k; ++i)
1103 if (state->undo_rec[i].char_storage >= 0)
1104 state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05
1105 }
1106 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])));
1107 ++state->redo_point;
1108 }
1109 }
1110
stb_text_create_undo_record(StbUndoState * state,int numchars)1111 static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
1112 {
1113 // any time we create a new undo record, we discard redo
1114 stb_textedit_flush_redo(state);
1115
1116 // if we have no free records, we have to make room, by sliding the
1117 // existing records down
1118 if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1119 stb_textedit_discard_undo(state);
1120
1121 // if the characters to store won't possibly fit in the buffer, we can't undo
1122 if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
1123 state->undo_point = 0;
1124 state->undo_char_point = 0;
1125 return NULL;
1126 }
1127
1128 // if we don't have enough free characters in the buffer, we have to make room
1129 while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
1130 stb_textedit_discard_undo(state);
1131
1132 return &state->undo_rec[state->undo_point++];
1133 }
1134
stb_text_createundo(StbUndoState * state,int pos,int insert_len,int delete_len)1135 static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
1136 {
1137 StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
1138 if (r == NULL)
1139 return NULL;
1140
1141 r->where = pos;
1142 r->insert_length = (short) insert_len;
1143 r->delete_length = (short) delete_len;
1144
1145 if (insert_len == 0) {
1146 r->char_storage = -1;
1147 return NULL;
1148 } else {
1149 r->char_storage = state->undo_char_point;
1150 state->undo_char_point = state->undo_char_point + (short) insert_len;
1151 return &state->undo_char[r->char_storage];
1152 }
1153 }
1154
stb_text_undo(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)1155 static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1156 {
1157 StbUndoState *s = &state->undostate;
1158 StbUndoRecord u, *r;
1159 if (s->undo_point == 0)
1160 return;
1161
1162 // we need to do two things: apply the undo record, and create a redo record
1163 u = s->undo_rec[s->undo_point-1];
1164 r = &s->undo_rec[s->redo_point-1];
1165 r->char_storage = -1;
1166
1167 r->insert_length = u.delete_length;
1168 r->delete_length = u.insert_length;
1169 r->where = u.where;
1170
1171 if (u.delete_length) {
1172 // if the undo record says to delete characters, then the redo record will
1173 // need to re-insert the characters that get deleted, so we need to store
1174 // them.
1175
1176 // there are three cases:
1177 // there's enough room to store the characters
1178 // characters stored for *redoing* don't leave room for redo
1179 // characters stored for *undoing* don't leave room for redo
1180 // if the last is true, we have to bail
1181
1182 if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
1183 // the undo records take up too much character space; there's no space to store the redo characters
1184 r->insert_length = 0;
1185 } else {
1186 int i;
1187
1188 // there's definitely room to store the characters eventually
1189 while (s->undo_char_point + u.delete_length > s->redo_char_point) {
1190 // there's currently not enough room, so discard a redo record
1191 stb_textedit_discard_redo(s);
1192 // should never happen:
1193 if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1194 return;
1195 }
1196 r = &s->undo_rec[s->redo_point-1];
1197
1198 r->char_storage = s->redo_char_point - u.delete_length;
1199 s->redo_char_point = s->redo_char_point - (short) u.delete_length;
1200
1201 // now save the characters
1202 for (i=0; i < u.delete_length; ++i)
1203 s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
1204 }
1205
1206 // now we can carry out the deletion
1207 STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
1208 }
1209
1210 // check type of recorded action:
1211 if (u.insert_length) {
1212 // easy case: was a deletion, so we need to insert n characters
1213 STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
1214 s->undo_char_point -= u.insert_length;
1215 }
1216
1217 state->cursor = u.where + u.insert_length;
1218
1219 s->undo_point--;
1220 s->redo_point--;
1221 }
1222
stb_text_redo(STB_TEXTEDIT_STRING * str,STB_TexteditState * state)1223 static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
1224 {
1225 StbUndoState *s = &state->undostate;
1226 StbUndoRecord *u, r;
1227 if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
1228 return;
1229
1230 // we need to do two things: apply the redo record, and create an undo record
1231 u = &s->undo_rec[s->undo_point];
1232 r = s->undo_rec[s->redo_point];
1233
1234 // we KNOW there must be room for the undo record, because the redo record
1235 // was derived from an undo record
1236
1237 u->delete_length = r.insert_length;
1238 u->insert_length = r.delete_length;
1239 u->where = r.where;
1240 u->char_storage = -1;
1241
1242 if (r.delete_length) {
1243 // the redo record requires us to delete characters, so the undo record
1244 // needs to store the characters
1245
1246 if (s->undo_char_point + u->insert_length > s->redo_char_point) {
1247 u->insert_length = 0;
1248 u->delete_length = 0;
1249 } else {
1250 int i;
1251 u->char_storage = s->undo_char_point;
1252 s->undo_char_point = s->undo_char_point + u->insert_length;
1253
1254 // now save the characters
1255 for (i=0; i < u->insert_length; ++i)
1256 s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
1257 }
1258
1259 STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
1260 }
1261
1262 if (r.insert_length) {
1263 // easy case: need to insert n characters
1264 STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
1265 s->redo_char_point += r.insert_length;
1266 }
1267
1268 state->cursor = r.where + r.insert_length;
1269
1270 s->undo_point++;
1271 s->redo_point++;
1272 }
1273
stb_text_makeundo_insert(STB_TexteditState * state,int where,int length)1274 static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
1275 {
1276 stb_text_createundo(&state->undostate, where, 0, length);
1277 }
1278
stb_text_makeundo_delete(STB_TEXTEDIT_STRING * str,STB_TexteditState * state,int where,int length)1279 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
1280 {
1281 int i;
1282 STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
1283 if (p) {
1284 for (i=0; i < length; ++i)
1285 p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1286 }
1287 }
1288
stb_text_makeundo_replace(STB_TEXTEDIT_STRING * str,STB_TexteditState * state,int where,int old_length,int new_length)1289 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
1290 {
1291 int i;
1292 STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
1293 if (p) {
1294 for (i=0; i < old_length; ++i)
1295 p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
1296 }
1297 }
1298
1299 // reset the state to default
stb_textedit_clear_state(STB_TexteditState * state,int is_single_line)1300 static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
1301 {
1302 state->undostate.undo_point = 0;
1303 state->undostate.undo_char_point = 0;
1304 state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
1305 state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
1306 state->select_end = state->select_start = 0;
1307 state->cursor = 0;
1308 state->has_preferred_x = 0;
1309 state->preferred_x = 0;
1310 state->cursor_at_end_of_line = 0;
1311 state->initialized = 1;
1312 state->single_line = (unsigned char) is_single_line;
1313 state->insert_mode = 0;
1314 }
1315
1316 // API initialize
stb_textedit_initialize_state(STB_TexteditState * state,int is_single_line)1317 static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
1318 {
1319 stb_textedit_clear_state(state, is_single_line);
1320 }
1321 #endif//STB_TEXTEDIT_IMPLEMENTATION
1322