1 // stb_textedit.h - v1.10 - public domain - Sean Barrett
2 // Development of this library was sponsored by RAD Game Tools
4 // This C header file implements the guts of a multi-line text-editing
5 // widget; you implement display, word-wrapping, and low-level string
6 // insertion/deletion, and stb_textedit will map user inputs into
7 // insertions & deletions, plus updates to the cursor position,
8 // selection state, and undo state.
10 // It is intended for use in games and other systems that need to build
11 // their own custom widgets and which do not have heavy text-editing
12 // requirements (this library is not recommended for use for editing large
13 // texts, as its performance does not scale and it has limited undo).
15 // Non-trivial behaviors are modelled after Windows text controls.
20 // This software is dual-licensed to the public domain and under the following
21 // license: you are granted a perpetual, irrevocable license to copy, modify,
22 // publish, and distribute this file as you see fit.
27 // Uses the C runtime function 'memmove', which you can override
28 // by defining STB_TEXTEDIT_memmove before the implementation.
29 // Uses no other functions. Performs no runtime allocations.
34 // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual
35 // 1.9 (2016-08-27) customizable move-by-word
36 // 1.8 (2016-04-02) better keyboard handling when mouse button is down
37 // 1.7 (2015-09-13) change y range handling in case baseline is non-0
38 // 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove
39 // 1.5 (2014-09-10) add support for secondary keys for OS X
40 // 1.4 (2014-08-17) fix signed/unsigned warnings
41 // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary
42 // 1.2 (2014-05-27) fix some RAD types that had crept into the new code
43 // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
44 // 1.0 (2012-07-26) improve documentation, initial public release
45 // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode
46 // 0.2 (2011-11-28) fixes to undo/redo
47 // 0.1 (2010-07-08) initial version
49 // ADDITIONAL CONTRIBUTORS
51 // Ulf Winklemann: move-by-word in 1.1
52 // Fabian Giesen: secondary key inputs in 1.5
53 // Martins Mozeiko: STB_TEXTEDIT_memmove
62 // This file behaves differently depending on what symbols you define
63 // before including it.
68 // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
69 // it will operate in "header file" mode. In this mode, it declares a
70 // single public symbol, STB_TexteditState, which encapsulates the current
71 // state of a text widget (except for the string, which you will store
74 // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
75 // primitive type that defines a single character (e.g. char, wchar_t, etc).
77 // To save space or increase undo-ability, you can optionally define the
78 // following things that are used by the undo system:
80 // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position
81 // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow
82 // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer
84 // If you don't define these, they are set to permissive types and
85 // moderate sizes. The undo system does no memory allocations, so
86 // it grows STB_TexteditState by the worst-case storage which is (in bytes):
88 // [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT
89 // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT
92 // Implementation mode:
94 // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
95 // will compile the implementation of the text edit widget, depending
96 // on a large number of symbols which must be defined before the include.
98 // The implementation is defined only as static functions. You will then
99 // need to provide your own APIs in the same file which will access the
102 // The basic concept is that you provide a "string" object which
103 // behaves like an array of characters. stb_textedit uses indices to
104 // refer to positions in the string, implicitly representing positions
105 // in the displayed textedit. This is true for both plain text and
106 // rich text; even with rich text stb_truetype interacts with your
107 // code as if there was an array of all the displayed characters.
109 // Symbols that must be the same in header-file and implementation mode:
111 // STB_TEXTEDIT_CHARTYPE the character type
112 // STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position
113 // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow
114 // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer
116 // Symbols you must define for implementation mode:
118 // STB_TEXTEDIT_STRING the type of object representing a string being edited,
119 // typically this is a wrapper object with other data you need
121 // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))
122 // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters
123 // starting from character #n (see discussion below)
124 // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character
125 // to the xpos of the i+1'th char for a line of characters
126 // starting at character #n (i.e. accounts for kerning
127 // with previous char)
128 // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
129 // (return type is int, -1 means not valid to insert)
130 // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
131 // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
132 // as manually wordwrapping for end-of-line positioning
134 // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
135 // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
137 // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
139 // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left
140 // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
141 // STB_TEXTEDIT_K_UP keyboard input to move cursor up
142 // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
143 // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME
144 // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END
145 // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME
146 // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END
147 // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor
148 // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor
149 // STB_TEXTEDIT_K_UNDO keyboard input to perform undo
150 // STB_TEXTEDIT_K_REDO keyboard input to perform redo
153 // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode
154 // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'),
155 // required for default WORDLEFT/WORDRIGHT handlers
156 // STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to
157 // STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to
158 // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT
159 // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT
160 // STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line
161 // STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line
162 // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
163 // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
166 // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
167 // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
169 // Keyboard input must be encoded as a single integer value; e.g. a character code
170 // and some bitflags that represent shift states. to simplify the interface, SHIFT must
171 // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
172 // i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
174 // You can encode other things, such as CONTROL or ALT, in additional bits, and
175 // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
176 // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
177 // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
178 // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
179 // API below. The control keys will only match WM_KEYDOWN events because of the
180 // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
181 // bit so it only decodes WM_CHAR events.
183 // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
184 // row of characters assuming they start on the i'th character--the width and
185 // the height and the number of characters consumed. This allows this library
186 // to traverse the entire layout incrementally. You need to compute word-wrapping
189 // Each textfield keeps its own insert mode state, which is not how normal
190 // applications work. To keep an app-wide insert mode, update/copy the
191 // "insert_mode" field of STB_TexteditState before/after calling API functions.
195 // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
197 // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
198 // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
199 // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
200 // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
201 // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key)
203 // Each of these functions potentially updates the string and updates the
207 // set the textedit state to a known good default state when initially
208 // constructing the textedit.
211 // call this with the mouse x,y on a mouse down; it will update the cursor
212 // and reset the selection start/end to the cursor point. the x,y must
213 // be relative to the text widget, with (0,0) being the top left.
216 // call this with the mouse x,y on a mouse drag/up; it will update the
217 // cursor and the selection end point
220 // call this to delete the current selection; returns true if there was
221 // one. you should FIRST copy the current selection to the system paste buffer.
222 // (To copy, just copy the current selection out of the string yourself.)
225 // call this to paste text at the current cursor point or over the current
226 // selection if there is one.
229 // call this for keyboard inputs sent to the textfield. you can use it
230 // for "key down" events or for "translated" key events. if you need to
231 // do both (as in Win32), or distinguish Unicode characters from control
232 // inputs, set a high bit to distinguish the two; then you can define the
233 // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
234 // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
237 // When rendering, you can read the cursor position and selection state from
238 // the STB_TexteditState.
243 // This is designed to be usable in IMGUI, so it allows for the possibility of
244 // running in an IMGUI that has NOT cached the multi-line layout. For this
245 // reason, it provides an interface that is compatible with computing the
246 // layout incrementally--we try to make sure we make as few passes through
247 // as possible. (For example, to locate the mouse pointer in the text, we
248 // could define functions that return the X and Y positions of characters
249 // and binary search Y and then X, but if we're doing dynamic layout this
250 // will run the layout algorithm many times, so instead we manually search
251 // forward in one pass. Similar logic applies to e.g. up-arrow and
252 // down-arrow movement.)
254 // If it's run in a widget that *has* cached the layout, then this is less
255 // efficient, but it's not horrible on modern computers. But you wouldn't
256 // want to edit million-line files with it.
259 ////////////////////////////////////////////////////////////////////////////
260 ////////////////////////////////////////////////////////////////////////////
262 //// Header-file mode
266 #ifndef INCLUDE_STB_TEXTEDIT_H
267 #define INCLUDE_STB_TEXTEDIT_H
269 ////////////////////////////////////////////////////////////////////////
273 // Definition of STB_TexteditState which you should store
274 // per-textfield; it includes cursor position, selection state,
278 #ifndef STB_TEXTEDIT_UNDOSTATECOUNT
279 #define STB_TEXTEDIT_UNDOSTATECOUNT 99
281 #ifndef STB_TEXTEDIT_UNDOCHARCOUNT
282 #define STB_TEXTEDIT_UNDOCHARCOUNT 999
284 #ifndef STB_TEXTEDIT_CHARTYPE
285 #define STB_TEXTEDIT_CHARTYPE int
287 #ifndef STB_TEXTEDIT_POSITIONTYPE
288 #define STB_TEXTEDIT_POSITIONTYPE int
294 STB_TEXTEDIT_POSITIONTYPE where
;
303 StbUndoRecord undo_rec
[STB_TEXTEDIT_UNDOSTATECOUNT
];
304 STB_TEXTEDIT_CHARTYPE undo_char
[STB_TEXTEDIT_UNDOCHARCOUNT
];
305 short undo_point
, redo_point
;
306 short undo_char_point
, redo_char_point
;
311 /////////////////////
317 // position of the text cursor within the string
319 int select_start
; // selection start point
321 // selection start and end point in characters; if equal, no selection.
322 // note that start may be less than or greater than end (e.g. when
323 // dragging the mouse, start is where the initial click was, and you
324 // can drag in either direction)
326 unsigned char insert_mode
;
327 // each textfield keeps its own insert mode state. to keep an app-wide
328 // insert mode, copy this value in/out of the app state
330 /////////////////////
334 unsigned char cursor_at_end_of_line
; // not implemented yet
335 unsigned char initialized
;
336 unsigned char has_preferred_x
;
337 unsigned char single_line
;
338 unsigned char padding1
, padding2
, padding3
;
339 float preferred_x
; // this determines where the cursor up/down tries to seek to along x
340 StbUndoState undostate
;
344 ////////////////////////////////////////////////////////////////////////
348 // Result of layout query, used by stb_textedit to determine where
349 // the text in each row is.
351 // result of layout query
354 float x0
,x1
; // starting x location, end x location (allows for align=right, etc)
355 float baseline_y_delta
; // position of baseline relative to previous row's baseline
356 float ymin
,ymax
; // height of row above and below baseline
359 #endif //INCLUDE_STB_TEXTEDIT_H
362 ////////////////////////////////////////////////////////////////////////////
363 ////////////////////////////////////////////////////////////////////////////
365 //// Implementation mode
370 // implementation isn't include-guarded, since it might have indirectly
371 // included just the "header" portion
372 #ifdef STB_TEXTEDIT_IMPLEMENTATION
374 #ifndef STB_TEXTEDIT_memmove
376 #define STB_TEXTEDIT_memmove memmove
380 /////////////////////////////////////////////////////////////////////////////
382 // Mouse input handling
385 // traverse the layout to locate the nearest character to a display position
386 static int stb_text_locate_coord(STB_TEXTEDIT_STRING
*str
, float x
, float y
)
389 int n
= STB_TEXTEDIT_STRINGLEN(str
);
390 float base_y
= 0, prev_x
;
397 // search rows to find one that straddles 'y'
399 STB_TEXTEDIT_LAYOUTROW(&r
, str
, i
);
400 if (r
.num_chars
<= 0)
403 if (i
==0 && y
< base_y
+ r
.ymin
)
406 if (y
< base_y
+ r
.ymax
)
410 base_y
+= r
.baseline_y_delta
;
413 // below all text, return 'after' last character
417 // check if it's before the beginning of the line
421 // check if it's before the end of the line
423 // search characters in row for one that straddles 'x'
425 for (k
=0; k
< r
.num_chars
; ++k
) {
426 float w
= STB_TEXTEDIT_GETWIDTH(str
, i
, k
);
435 // shouldn't happen, but if it does, fall through to end-of-line case
438 // if the last character is a newline, return that. otherwise return 'after' the last character
439 if (STB_TEXTEDIT_GETCHAR(str
, i
+r
.num_chars
-1) == STB_TEXTEDIT_NEWLINE
)
440 return i
+r
.num_chars
-1;
442 return i
+r
.num_chars
;
445 // API click: on mouse down, move the cursor to the clicked location, and reset the selection
446 static void stb_textedit_click(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, float x
, float y
)
448 state
->cursor
= stb_text_locate_coord(str
, x
, y
);
449 state
->select_start
= state
->cursor
;
450 state
->select_end
= state
->cursor
;
451 state
->has_preferred_x
= 0;
454 // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
455 static void stb_textedit_drag(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, float x
, float y
)
457 int p
= stb_text_locate_coord(str
, x
, y
);
458 if (state
->select_start
== state
->select_end
)
459 state
->select_start
= state
->cursor
;
460 state
->cursor
= state
->select_end
= p
;
463 /////////////////////////////////////////////////////////////////////////////
465 // Keyboard input handling
468 // forward declarations
469 static void stb_text_undo(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
);
470 static void stb_text_redo(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
);
471 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int where
, int length
);
472 static void stb_text_makeundo_insert(STB_TexteditState
*state
, int where
, int length
);
473 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int where
, int old_length
, int new_length
);
477 float x
,y
; // position of n'th character
478 float height
; // height of line
479 int first_char
, length
; // first char of row, and length
480 int prev_first
; // first char of previous row
483 // find the x/y location of a character, and remember info about the previous row in
484 // case we get a move-up event (for page up, we'll have to rescan)
485 static void stb_textedit_find_charpos(StbFindState
*find
, STB_TEXTEDIT_STRING
*str
, int n
, int single_line
)
489 int z
= STB_TEXTEDIT_STRINGLEN(str
);
493 // if it's at the end, then find the last line -- simpler than trying to
494 // explicitly handle this case in the regular code
496 STB_TEXTEDIT_LAYOUTROW(&r
, str
, 0);
498 find
->first_char
= 0;
500 find
->height
= r
.ymax
- r
.ymin
;
507 STB_TEXTEDIT_LAYOUTROW(&r
, str
, i
);
511 find
->first_char
= i
;
513 find
->prev_first
= prev_start
;
518 // search rows to find the one that straddles character n
522 STB_TEXTEDIT_LAYOUTROW(&r
, str
, i
);
523 if (n
< i
+ r
.num_chars
)
527 find
->y
+= r
.baseline_y_delta
;
530 find
->first_char
= first
= i
;
531 find
->length
= r
.num_chars
;
532 find
->height
= r
.ymax
- r
.ymin
;
533 find
->prev_first
= prev_start
;
535 // now scan to find xpos
538 for (i
=0; first
+i
< n
; ++i
)
539 find
->x
+= STB_TEXTEDIT_GETWIDTH(str
, first
, i
);
542 #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)
544 // make the selection/cursor state valid if client altered the string
545 static void stb_textedit_clamp(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
547 int n
= STB_TEXTEDIT_STRINGLEN(str
);
548 if (STB_TEXT_HAS_SELECTION(state
)) {
549 if (state
->select_start
> n
) state
->select_start
= n
;
550 if (state
->select_end
> n
) state
->select_end
= n
;
551 // if clamping forced them to be equal, move the cursor to match
552 if (state
->select_start
== state
->select_end
)
553 state
->cursor
= state
->select_start
;
555 if (state
->cursor
> n
) state
->cursor
= n
;
558 // delete characters while updating undo
559 static void stb_textedit_delete(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int where
, int len
)
561 stb_text_makeundo_delete(str
, state
, where
, len
);
562 STB_TEXTEDIT_DELETECHARS(str
, where
, len
);
563 state
->has_preferred_x
= 0;
566 // delete the section
567 static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
569 stb_textedit_clamp(str
, state
);
570 if (STB_TEXT_HAS_SELECTION(state
)) {
571 if (state
->select_start
< state
->select_end
) {
572 stb_textedit_delete(str
, state
, state
->select_start
, state
->select_end
- state
->select_start
);
573 state
->select_end
= state
->cursor
= state
->select_start
;
575 stb_textedit_delete(str
, state
, state
->select_end
, state
->select_start
- state
->select_end
);
576 state
->select_start
= state
->cursor
= state
->select_end
;
578 state
->has_preferred_x
= 0;
582 // canoncialize the selection so start <= end
583 static void stb_textedit_sortselection(STB_TexteditState
*state
)
585 if (state
->select_end
< state
->select_start
) {
586 int temp
= state
->select_end
;
587 state
->select_end
= state
->select_start
;
588 state
->select_start
= temp
;
592 // move cursor to first character of selection
593 static void stb_textedit_move_to_first(STB_TexteditState
*state
)
595 if (STB_TEXT_HAS_SELECTION(state
)) {
596 stb_textedit_sortselection(state
);
597 state
->cursor
= state
->select_start
;
598 state
->select_end
= state
->select_start
;
599 state
->has_preferred_x
= 0;
603 // move cursor to last character of selection
604 static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
606 if (STB_TEXT_HAS_SELECTION(state
)) {
607 stb_textedit_sortselection(state
);
608 stb_textedit_clamp(str
, state
);
609 state
->cursor
= state
->select_end
;
610 state
->select_start
= state
->select_end
;
611 state
->has_preferred_x
= 0;
615 #ifdef STB_TEXTEDIT_IS_SPACE
616 static int is_word_boundary( STB_TEXTEDIT_STRING
*str
, int idx
)
618 return idx
> 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str
,idx
-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str
, idx
) ) ) : 1;
621 #ifndef STB_TEXTEDIT_MOVEWORDLEFT
622 static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING
*str
, int c
)
624 --c
; // always move at least one character
625 while( c
>= 0 && !is_word_boundary( str
, c
) )
633 #define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
636 #ifndef STB_TEXTEDIT_MOVEWORDRIGHT
637 static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING
*str
, int c
)
639 const int len
= STB_TEXTEDIT_STRINGLEN(str
);
640 ++c
; // always move at least one character
641 while( c
< len
&& !is_word_boundary( str
, c
) )
649 #define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
654 // update selection and cursor to match each other
655 static void stb_textedit_prep_selection_at_cursor(STB_TexteditState
*state
)
657 if (!STB_TEXT_HAS_SELECTION(state
))
658 state
->select_start
= state
->select_end
= state
->cursor
;
660 state
->cursor
= state
->select_end
;
663 // API cut: delete selection
664 static int stb_textedit_cut(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
666 if (STB_TEXT_HAS_SELECTION(state
)) {
667 stb_textedit_delete_selection(str
,state
); // implicity clamps
668 state
->has_preferred_x
= 0;
674 // API paste: replace existing selection with passed-in text
675 static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, STB_TEXTEDIT_CHARTYPE
*text
, int len
)
677 // if there's a selection, the paste should delete it
678 stb_textedit_clamp(str
, state
);
679 stb_textedit_delete_selection(str
,state
);
680 // try to insert the characters
681 if (STB_TEXTEDIT_INSERTCHARS(str
, state
->cursor
, text
, len
)) {
682 stb_text_makeundo_insert(state
, state
->cursor
, len
);
683 state
->cursor
+= len
;
684 state
->has_preferred_x
= 0;
687 // remove the undo since we didn't actually insert the characters
688 if (state
->undostate
.undo_point
)
689 --state
->undostate
.undo_point
;
693 // API key: process a keyboard input
694 static void stb_textedit_key(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int key
)
699 int c
= STB_TEXTEDIT_KEYTOTEXT(key
);
701 STB_TEXTEDIT_CHARTYPE ch
= (STB_TEXTEDIT_CHARTYPE
) c
;
703 // can't add newline in single-line mode
704 if (c
== '\n' && state
->single_line
)
707 if (state
->insert_mode
&& !STB_TEXT_HAS_SELECTION(state
) && state
->cursor
< STB_TEXTEDIT_STRINGLEN(str
)) {
708 stb_text_makeundo_replace(str
, state
, state
->cursor
, 1, 1);
709 STB_TEXTEDIT_DELETECHARS(str
, state
->cursor
, 1);
710 if (STB_TEXTEDIT_INSERTCHARS(str
, state
->cursor
, &ch
, 1)) {
712 state
->has_preferred_x
= 0;
715 stb_textedit_delete_selection(str
,state
); // implicity clamps
716 if (STB_TEXTEDIT_INSERTCHARS(str
, state
->cursor
, &ch
, 1)) {
717 stb_text_makeundo_insert(state
, state
->cursor
, 1);
719 state
->has_preferred_x
= 0;
726 #ifdef STB_TEXTEDIT_K_INSERT
727 case STB_TEXTEDIT_K_INSERT
:
728 state
->insert_mode
= !state
->insert_mode
;
732 case STB_TEXTEDIT_K_UNDO
:
733 stb_text_undo(str
, state
);
734 state
->has_preferred_x
= 0;
737 case STB_TEXTEDIT_K_REDO
:
738 stb_text_redo(str
, state
);
739 state
->has_preferred_x
= 0;
742 case STB_TEXTEDIT_K_LEFT
:
743 // if currently there's a selection, move cursor to start of selection
744 if (STB_TEXT_HAS_SELECTION(state
))
745 stb_textedit_move_to_first(state
);
747 if (state
->cursor
> 0)
749 state
->has_preferred_x
= 0;
752 case STB_TEXTEDIT_K_RIGHT
:
753 // if currently there's a selection, move cursor to end of selection
754 if (STB_TEXT_HAS_SELECTION(state
))
755 stb_textedit_move_to_last(str
, state
);
758 stb_textedit_clamp(str
, state
);
759 state
->has_preferred_x
= 0;
762 case STB_TEXTEDIT_K_LEFT
| STB_TEXTEDIT_K_SHIFT
:
763 stb_textedit_clamp(str
, state
);
764 stb_textedit_prep_selection_at_cursor(state
);
765 // move selection left
766 if (state
->select_end
> 0)
768 state
->cursor
= state
->select_end
;
769 state
->has_preferred_x
= 0;
772 #ifdef STB_TEXTEDIT_MOVEWORDLEFT
773 case STB_TEXTEDIT_K_WORDLEFT
:
774 if (STB_TEXT_HAS_SELECTION(state
))
775 stb_textedit_move_to_first(state
);
777 state
->cursor
= STB_TEXTEDIT_MOVEWORDLEFT(str
, state
->cursor
);
778 stb_textedit_clamp( str
, state
);
782 case STB_TEXTEDIT_K_WORDLEFT
| STB_TEXTEDIT_K_SHIFT
:
783 if( !STB_TEXT_HAS_SELECTION( state
) )
784 stb_textedit_prep_selection_at_cursor(state
);
786 state
->cursor
= STB_TEXTEDIT_MOVEWORDLEFT(str
, state
->cursor
);
787 state
->select_end
= state
->cursor
;
789 stb_textedit_clamp( str
, state
);
793 #ifdef STB_TEXTEDIT_MOVEWORDRIGHT
794 case STB_TEXTEDIT_K_WORDRIGHT
:
795 if (STB_TEXT_HAS_SELECTION(state
))
796 stb_textedit_move_to_last(str
, state
);
798 state
->cursor
= STB_TEXTEDIT_MOVEWORDRIGHT(str
, state
->cursor
);
799 stb_textedit_clamp( str
, state
);
803 case STB_TEXTEDIT_K_WORDRIGHT
| STB_TEXTEDIT_K_SHIFT
:
804 if( !STB_TEXT_HAS_SELECTION( state
) )
805 stb_textedit_prep_selection_at_cursor(state
);
807 state
->cursor
= STB_TEXTEDIT_MOVEWORDRIGHT(str
, state
->cursor
);
808 state
->select_end
= state
->cursor
;
810 stb_textedit_clamp( str
, state
);
814 case STB_TEXTEDIT_K_RIGHT
| STB_TEXTEDIT_K_SHIFT
:
815 stb_textedit_prep_selection_at_cursor(state
);
816 // move selection right
818 stb_textedit_clamp(str
, state
);
819 state
->cursor
= state
->select_end
;
820 state
->has_preferred_x
= 0;
823 case STB_TEXTEDIT_K_DOWN
:
824 case STB_TEXTEDIT_K_DOWN
| STB_TEXTEDIT_K_SHIFT
: {
827 int i
, sel
= (key
& STB_TEXTEDIT_K_SHIFT
) != 0;
829 if (state
->single_line
) {
830 // on windows, up&down in single-line behave like left&right
831 key
= STB_TEXTEDIT_K_RIGHT
| (key
& STB_TEXTEDIT_K_SHIFT
);
836 stb_textedit_prep_selection_at_cursor(state
);
837 else if (STB_TEXT_HAS_SELECTION(state
))
838 stb_textedit_move_to_last(str
,state
);
840 // compute current position of cursor point
841 stb_textedit_clamp(str
, state
);
842 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
844 // now find character position down a row
846 float goal_x
= state
->has_preferred_x
? state
->preferred_x
: find
.x
;
848 int start
= find
.first_char
+ find
.length
;
849 state
->cursor
= start
;
850 STB_TEXTEDIT_LAYOUTROW(&row
, str
, state
->cursor
);
852 for (i
=0; i
< row
.num_chars
; ++i
) {
853 float dx
= STB_TEXTEDIT_GETWIDTH(str
, start
, i
);
854 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
855 if (dx
== STB_TEXTEDIT_GETWIDTH_NEWLINE
)
863 stb_textedit_clamp(str
, state
);
865 state
->has_preferred_x
= 1;
866 state
->preferred_x
= goal_x
;
869 state
->select_end
= state
->cursor
;
874 case STB_TEXTEDIT_K_UP
:
875 case STB_TEXTEDIT_K_UP
| STB_TEXTEDIT_K_SHIFT
: {
878 int i
, sel
= (key
& STB_TEXTEDIT_K_SHIFT
) != 0;
880 if (state
->single_line
) {
881 // on windows, up&down become left&right
882 key
= STB_TEXTEDIT_K_LEFT
| (key
& STB_TEXTEDIT_K_SHIFT
);
887 stb_textedit_prep_selection_at_cursor(state
);
888 else if (STB_TEXT_HAS_SELECTION(state
))
889 stb_textedit_move_to_first(state
);
891 // compute current position of cursor point
892 stb_textedit_clamp(str
, state
);
893 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
895 // can only go up if there's a previous row
896 if (find
.prev_first
!= find
.first_char
) {
897 // now find character position up a row
898 float goal_x
= state
->has_preferred_x
? state
->preferred_x
: find
.x
;
900 state
->cursor
= find
.prev_first
;
901 STB_TEXTEDIT_LAYOUTROW(&row
, str
, state
->cursor
);
903 for (i
=0; i
< row
.num_chars
; ++i
) {
904 float dx
= STB_TEXTEDIT_GETWIDTH(str
, find
.prev_first
, i
);
905 #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
906 if (dx
== STB_TEXTEDIT_GETWIDTH_NEWLINE
)
914 stb_textedit_clamp(str
, state
);
916 state
->has_preferred_x
= 1;
917 state
->preferred_x
= goal_x
;
920 state
->select_end
= state
->cursor
;
925 case STB_TEXTEDIT_K_DELETE
:
926 case STB_TEXTEDIT_K_DELETE
| STB_TEXTEDIT_K_SHIFT
:
927 if (STB_TEXT_HAS_SELECTION(state
))
928 stb_textedit_delete_selection(str
, state
);
930 int n
= STB_TEXTEDIT_STRINGLEN(str
);
931 if (state
->cursor
< n
)
932 stb_textedit_delete(str
, state
, state
->cursor
, 1);
934 state
->has_preferred_x
= 0;
937 case STB_TEXTEDIT_K_BACKSPACE
:
938 case STB_TEXTEDIT_K_BACKSPACE
| STB_TEXTEDIT_K_SHIFT
:
939 if (STB_TEXT_HAS_SELECTION(state
))
940 stb_textedit_delete_selection(str
, state
);
942 stb_textedit_clamp(str
, state
);
943 if (state
->cursor
> 0) {
944 stb_textedit_delete(str
, state
, state
->cursor
-1, 1);
948 state
->has_preferred_x
= 0;
951 #ifdef STB_TEXTEDIT_K_TEXTSTART2
952 case STB_TEXTEDIT_K_TEXTSTART2
:
954 case STB_TEXTEDIT_K_TEXTSTART
:
955 state
->cursor
= state
->select_start
= state
->select_end
= 0;
956 state
->has_preferred_x
= 0;
959 #ifdef STB_TEXTEDIT_K_TEXTEND2
960 case STB_TEXTEDIT_K_TEXTEND2
:
962 case STB_TEXTEDIT_K_TEXTEND
:
963 state
->cursor
= STB_TEXTEDIT_STRINGLEN(str
);
964 state
->select_start
= state
->select_end
= 0;
965 state
->has_preferred_x
= 0;
968 #ifdef STB_TEXTEDIT_K_TEXTSTART2
969 case STB_TEXTEDIT_K_TEXTSTART2
| STB_TEXTEDIT_K_SHIFT
:
971 case STB_TEXTEDIT_K_TEXTSTART
| STB_TEXTEDIT_K_SHIFT
:
972 stb_textedit_prep_selection_at_cursor(state
);
973 state
->cursor
= state
->select_end
= 0;
974 state
->has_preferred_x
= 0;
977 #ifdef STB_TEXTEDIT_K_TEXTEND2
978 case STB_TEXTEDIT_K_TEXTEND2
| STB_TEXTEDIT_K_SHIFT
:
980 case STB_TEXTEDIT_K_TEXTEND
| STB_TEXTEDIT_K_SHIFT
:
981 stb_textedit_prep_selection_at_cursor(state
);
982 state
->cursor
= state
->select_end
= STB_TEXTEDIT_STRINGLEN(str
);
983 state
->has_preferred_x
= 0;
987 #ifdef STB_TEXTEDIT_K_LINESTART2
988 case STB_TEXTEDIT_K_LINESTART2
:
990 case STB_TEXTEDIT_K_LINESTART
: {
992 stb_textedit_clamp(str
, state
);
993 stb_textedit_move_to_first(state
);
994 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
995 state
->cursor
= find
.first_char
;
996 state
->has_preferred_x
= 0;
1000 #ifdef STB_TEXTEDIT_K_LINEEND2
1001 case STB_TEXTEDIT_K_LINEEND2
:
1003 case STB_TEXTEDIT_K_LINEEND
: {
1005 stb_textedit_clamp(str
, state
);
1006 stb_textedit_move_to_first(state
);
1007 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
1009 state
->has_preferred_x
= 0;
1010 state
->cursor
= find
.first_char
+ find
.length
;
1011 if (find
.length
> 0 && STB_TEXTEDIT_GETCHAR(str
, state
->cursor
-1) == STB_TEXTEDIT_NEWLINE
)
1016 #ifdef STB_TEXTEDIT_K_LINESTART2
1017 case STB_TEXTEDIT_K_LINESTART2
| STB_TEXTEDIT_K_SHIFT
:
1019 case STB_TEXTEDIT_K_LINESTART
| STB_TEXTEDIT_K_SHIFT
: {
1021 stb_textedit_clamp(str
, state
);
1022 stb_textedit_prep_selection_at_cursor(state
);
1023 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
1024 state
->cursor
= state
->select_end
= find
.first_char
;
1025 state
->has_preferred_x
= 0;
1029 #ifdef STB_TEXTEDIT_K_LINEEND2
1030 case STB_TEXTEDIT_K_LINEEND2
| STB_TEXTEDIT_K_SHIFT
:
1032 case STB_TEXTEDIT_K_LINEEND
| STB_TEXTEDIT_K_SHIFT
: {
1034 stb_textedit_clamp(str
, state
);
1035 stb_textedit_prep_selection_at_cursor(state
);
1036 stb_textedit_find_charpos(&find
, str
, state
->cursor
, state
->single_line
);
1037 state
->has_preferred_x
= 0;
1038 state
->cursor
= find
.first_char
+ find
.length
;
1039 if (find
.length
> 0 && STB_TEXTEDIT_GETCHAR(str
, state
->cursor
-1) == STB_TEXTEDIT_NEWLINE
)
1041 state
->select_end
= state
->cursor
;
1046 // STB_TEXTEDIT_K_PGUP - move cursor up a page
1047 // STB_TEXTEDIT_K_PGDOWN - move cursor down a page
1051 /////////////////////////////////////////////////////////////////////////////
1055 // @OPTIMIZE: the undo/redo buffer should be circular
1057 static void stb_textedit_flush_redo(StbUndoState
*state
)
1059 state
->redo_point
= STB_TEXTEDIT_UNDOSTATECOUNT
;
1060 state
->redo_char_point
= STB_TEXTEDIT_UNDOCHARCOUNT
;
1063 // discard the oldest entry in the undo list
1064 static void stb_textedit_discard_undo(StbUndoState
*state
)
1066 if (state
->undo_point
> 0) {
1067 // if the 0th undo state has characters, clean those up
1068 if (state
->undo_rec
[0].char_storage
>= 0) {
1069 int n
= state
->undo_rec
[0].insert_length
, i
;
1070 // delete n characters from all other records
1071 state
->undo_char_point
= state
->undo_char_point
- (short) n
; // vsnet05
1072 STB_TEXTEDIT_memmove(state
->undo_char
, state
->undo_char
+ n
, (size_t) (state
->undo_char_point
*sizeof(STB_TEXTEDIT_CHARTYPE
)));
1073 for (i
=0; i
< state
->undo_point
; ++i
)
1074 if (state
->undo_rec
[i
].char_storage
>= 0)
1075 state
->undo_rec
[i
].char_storage
= state
->undo_rec
[i
].char_storage
- (short) n
; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it
1077 --state
->undo_point
;
1078 STB_TEXTEDIT_memmove(state
->undo_rec
, state
->undo_rec
+1, (size_t) (state
->undo_point
*sizeof(state
->undo_rec
[0])));
1082 // discard the oldest entry in the redo list--it's bad if this
1083 // ever happens, but because undo & redo have to store the actual
1084 // characters in different cases, the redo character buffer can
1085 // fill up even though the undo buffer didn't
1086 static void stb_textedit_discard_redo(StbUndoState
*state
)
1088 int k
= STB_TEXTEDIT_UNDOSTATECOUNT
-1;
1090 if (state
->redo_point
<= k
) {
1091 // if the k'th undo state has characters, clean those up
1092 if (state
->undo_rec
[k
].char_storage
>= 0) {
1093 int n
= state
->undo_rec
[k
].insert_length
, i
;
1094 // delete n characters from all other records
1095 state
->redo_char_point
= state
->redo_char_point
+ (short) n
; // vsnet05
1096 STB_TEXTEDIT_memmove(state
->undo_char
+ state
->redo_char_point
, state
->undo_char
+ state
->redo_char_point
-n
, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT
- state
->redo_char_point
)*sizeof(STB_TEXTEDIT_CHARTYPE
)));
1097 for (i
=state
->redo_point
; i
< k
; ++i
)
1098 if (state
->undo_rec
[i
].char_storage
>= 0)
1099 state
->undo_rec
[i
].char_storage
= state
->undo_rec
[i
].char_storage
+ (short) n
; // vsnet05
1101 ++state
->redo_point
;
1102 STB_TEXTEDIT_memmove(state
->undo_rec
+ state
->redo_point
-1, state
->undo_rec
+ state
->redo_point
, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT
- state
->redo_point
)*sizeof(state
->undo_rec
[0])));
1106 static StbUndoRecord
*stb_text_create_undo_record(StbUndoState
*state
, int numchars
)
1108 // any time we create a new undo record, we discard redo
1109 stb_textedit_flush_redo(state
);
1111 // if we have no free records, we have to make room, by sliding the
1112 // existing records down
1113 if (state
->undo_point
== STB_TEXTEDIT_UNDOSTATECOUNT
)
1114 stb_textedit_discard_undo(state
);
1116 // if the characters to store won't possibly fit in the buffer, we can't undo
1117 if (numchars
> STB_TEXTEDIT_UNDOCHARCOUNT
) {
1118 state
->undo_point
= 0;
1119 state
->undo_char_point
= 0;
1123 // if we don't have enough free characters in the buffer, we have to make room
1124 while (state
->undo_char_point
+ numchars
> STB_TEXTEDIT_UNDOCHARCOUNT
)
1125 stb_textedit_discard_undo(state
);
1127 return &state
->undo_rec
[state
->undo_point
++];
1130 static STB_TEXTEDIT_CHARTYPE
*stb_text_createundo(StbUndoState
*state
, int pos
, int insert_len
, int delete_len
)
1132 StbUndoRecord
*r
= stb_text_create_undo_record(state
, insert_len
);
1137 r
->insert_length
= (short) insert_len
;
1138 r
->delete_length
= (short) delete_len
;
1140 if (insert_len
== 0) {
1141 r
->char_storage
= -1;
1144 r
->char_storage
= state
->undo_char_point
;
1145 state
->undo_char_point
= state
->undo_char_point
+ (short) insert_len
;
1146 return &state
->undo_char
[r
->char_storage
];
1150 static void stb_text_undo(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
1152 StbUndoState
*s
= &state
->undostate
;
1153 StbUndoRecord u
, *r
;
1154 if (s
->undo_point
== 0)
1157 // we need to do two things: apply the undo record, and create a redo record
1158 u
= s
->undo_rec
[s
->undo_point
-1];
1159 r
= &s
->undo_rec
[s
->redo_point
-1];
1160 r
->char_storage
= -1;
1162 r
->insert_length
= u
.delete_length
;
1163 r
->delete_length
= u
.insert_length
;
1166 if (u
.delete_length
) {
1167 // if the undo record says to delete characters, then the redo record will
1168 // need to re-insert the characters that get deleted, so we need to store
1171 // there are three cases:
1172 // there's enough room to store the characters
1173 // characters stored for *redoing* don't leave room for redo
1174 // characters stored for *undoing* don't leave room for redo
1175 // if the last is true, we have to bail
1177 if (s
->undo_char_point
+ u
.delete_length
>= STB_TEXTEDIT_UNDOCHARCOUNT
) {
1178 // the undo records take up too much character space; there's no space to store the redo characters
1179 r
->insert_length
= 0;
1183 // there's definitely room to store the characters eventually
1184 while (s
->undo_char_point
+ u
.delete_length
> s
->redo_char_point
) {
1185 // there's currently not enough room, so discard a redo record
1186 stb_textedit_discard_redo(s
);
1187 // should never happen:
1188 if (s
->redo_point
== STB_TEXTEDIT_UNDOSTATECOUNT
)
1191 r
= &s
->undo_rec
[s
->redo_point
-1];
1193 r
->char_storage
= s
->redo_char_point
- u
.delete_length
;
1194 s
->redo_char_point
= s
->redo_char_point
- (short) u
.delete_length
;
1196 // now save the characters
1197 for (i
=0; i
< u
.delete_length
; ++i
)
1198 s
->undo_char
[r
->char_storage
+ i
] = STB_TEXTEDIT_GETCHAR(str
, u
.where
+ i
);
1201 // now we can carry out the deletion
1202 STB_TEXTEDIT_DELETECHARS(str
, u
.where
, u
.delete_length
);
1205 // check type of recorded action:
1206 if (u
.insert_length
) {
1207 // easy case: was a deletion, so we need to insert n characters
1208 STB_TEXTEDIT_INSERTCHARS(str
, u
.where
, &s
->undo_char
[u
.char_storage
], u
.insert_length
);
1209 s
->undo_char_point
-= u
.insert_length
;
1212 state
->cursor
= u
.where
+ u
.insert_length
;
1218 static void stb_text_redo(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
)
1220 StbUndoState
*s
= &state
->undostate
;
1221 StbUndoRecord
*u
, r
;
1222 if (s
->redo_point
== STB_TEXTEDIT_UNDOSTATECOUNT
)
1225 // we need to do two things: apply the redo record, and create an undo record
1226 u
= &s
->undo_rec
[s
->undo_point
];
1227 r
= s
->undo_rec
[s
->redo_point
];
1229 // we KNOW there must be room for the undo record, because the redo record
1230 // was derived from an undo record
1232 u
->delete_length
= r
.insert_length
;
1233 u
->insert_length
= r
.delete_length
;
1235 u
->char_storage
= -1;
1237 if (r
.delete_length
) {
1238 // the redo record requires us to delete characters, so the undo record
1239 // needs to store the characters
1241 if (s
->undo_char_point
+ u
->insert_length
> s
->redo_char_point
) {
1242 u
->insert_length
= 0;
1243 u
->delete_length
= 0;
1246 u
->char_storage
= s
->undo_char_point
;
1247 s
->undo_char_point
= s
->undo_char_point
+ u
->insert_length
;
1249 // now save the characters
1250 for (i
=0; i
< u
->insert_length
; ++i
)
1251 s
->undo_char
[u
->char_storage
+ i
] = STB_TEXTEDIT_GETCHAR(str
, u
->where
+ i
);
1254 STB_TEXTEDIT_DELETECHARS(str
, r
.where
, r
.delete_length
);
1257 if (r
.insert_length
) {
1258 // easy case: need to insert n characters
1259 STB_TEXTEDIT_INSERTCHARS(str
, r
.where
, &s
->undo_char
[r
.char_storage
], r
.insert_length
);
1262 state
->cursor
= r
.where
+ r
.insert_length
;
1268 static void stb_text_makeundo_insert(STB_TexteditState
*state
, int where
, int length
)
1270 stb_text_createundo(&state
->undostate
, where
, 0, length
);
1273 static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int where
, int length
)
1276 STB_TEXTEDIT_CHARTYPE
*p
= stb_text_createundo(&state
->undostate
, where
, length
, 0);
1278 for (i
=0; i
< length
; ++i
)
1279 p
[i
] = STB_TEXTEDIT_GETCHAR(str
, where
+i
);
1283 static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, int where
, int old_length
, int new_length
)
1286 STB_TEXTEDIT_CHARTYPE
*p
= stb_text_createundo(&state
->undostate
, where
, old_length
, new_length
);
1288 for (i
=0; i
< old_length
; ++i
)
1289 p
[i
] = STB_TEXTEDIT_GETCHAR(str
, where
+i
);
1293 // reset the state to default
1294 static void stb_textedit_clear_state(STB_TexteditState
*state
, int is_single_line
)
1296 state
->undostate
.undo_point
= 0;
1297 state
->undostate
.undo_char_point
= 0;
1298 state
->undostate
.redo_point
= STB_TEXTEDIT_UNDOSTATECOUNT
;
1299 state
->undostate
.redo_char_point
= STB_TEXTEDIT_UNDOCHARCOUNT
;
1300 state
->select_end
= state
->select_start
= 0;
1302 state
->has_preferred_x
= 0;
1303 state
->preferred_x
= 0;
1304 state
->cursor_at_end_of_line
= 0;
1305 state
->initialized
= 1;
1306 state
->single_line
= (unsigned char) is_single_line
;
1307 state
->insert_mode
= 0;
1311 static void stb_textedit_initialize_state(STB_TexteditState
*state
, int is_single_line
)
1313 stb_textedit_clear_state(state
, is_single_line
);
1316 #if defined(__GNUC__) || defined(__clang__)
1317 #pragma GCC diagnostic push
1318 #pragma GCC diagnostic ignored "-Wcast-qual"
1321 static int stb_textedit_paste(STB_TEXTEDIT_STRING
*str
, STB_TexteditState
*state
, STB_TEXTEDIT_CHARTYPE
const *ctext
, int len
)
1323 return stb_textedit_paste_internal(str
, state
, (STB_TEXTEDIT_CHARTYPE
*) ctext
, len
);
1326 #if defined(__GNUC__) || defined(__clang__)
1327 #pragma GCC diagnostic pop
1330 #endif//STB_TEXTEDIT_IMPLEMENTATION