vsgImGui: include/vsgImGui/imstb_textedit.h Source File

vsgImGui 0.7.0 VulkanSceneGraph, ImGui and ImPlot integration library |

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