source: opengl-game/IMGUI/stb_textedit.h@ 99d44b2

feature/imgui-sdl points-test
Last change on this file since 99d44b2 was c58ebc3, checked in by Dmitry Portnoy <dmp1488@…>, 7 years ago

Create an IMGUI folder for the imgui library files.

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