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