OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / extern / imgui / imstb_textedit.h
55.8 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 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
279
////////////////////////////////////////////////////////////////////////////
280
////////////////////////////////////////////////////////////////////////////
281
////
282
////   Header-file mode
283
////
284
////
285
286
#ifndef INCLUDE_IMSTB_TEXTEDIT_H
287
#define INCLUDE_IMSTB_TEXTEDIT_H
288
289
////////////////////////////////////////////////////////////////////////
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
{
331
   /////////////////////
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
354
   /////////////////////
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
368
////////////////////////////////////////////////////////////////////////
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
386
////////////////////////////////////////////////////////////////////////////
387
////////////////////////////////////////////////////////////////////////////
388
////
389
////   Implementation mode
390
////
391
////
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
404
/////////////////////////////////////////////////////////////////////////////
405
//
406
//      Mouse input handling
407
//
408
409
// traverse the layout to locate the nearest character to a display position
410
static 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
470
static 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
488
static 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
508
/////////////////////////////////////////////////////////////////////////////
509
//
510
//      Keyboard input handling
511
//
512
513
// forward declarations
514
static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
515
static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);
516
static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
517
static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
518
static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
519
520
typedef 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)
530
static 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
581
static 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
595
static 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
603
static 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
619
static 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
629
static 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
640
static 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
663
static 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
669
static 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
684
static 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
702
static 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
711
static 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
722
static 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.
743
static 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
768
static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
769
{
770
retry:
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
1136
/////////////////////////////////////////////////////////////////////////////
1137
//
1138
//      Undo processing
1139
//
1140
// @OPTIMIZE: the undo/redo buffer should be circular
1141
1142
static 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
1149
static 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
1171
static 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
1201
static 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
1225
static 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
1245
static 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
1313
static 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
1364
static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
1365
{
1366
   stb_text_createundo(&state->undostate, where, 0, length);
1367
}
1368
1369
static 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
1379
static 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
1390
static 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
1408
static 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
1418
static 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
------------------------------------------------------------------------------
1431
This software is available under 2 licenses -- choose whichever you prefer.
1432
------------------------------------------------------------------------------
1433
ALTERNATIVE A - MIT License
1434
Copyright (c) 2017 Sean Barrett
1435
Permission is hereby granted, free of charge, to any person obtaining a copy of
1436
this software and associated documentation files (the "Software"), to deal in
1437
the Software without restriction, including without limitation the rights to
1438
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
1439
of the Software, and to permit persons to whom the Software is furnished to do
1440
so, subject to the following conditions:
1441
The above copyright notice and this permission notice shall be included in all
1442
copies or substantial portions of the Software.
1443
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1444
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1445
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1446
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1447
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1448
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1449
SOFTWARE.
1450
------------------------------------------------------------------------------
1451
ALTERNATIVE B - Public Domain (www.unlicense.org)
1452
This is free and unencumbered software released into the public domain.
1453
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
1454
software, either in source code form or as a compiled binary, for any purpose,
1455
commercial or non-commercial, and by any means.
1456
In jurisdictions that recognize copyright laws, the author or authors of this
1457
software dedicate any and all copyright interest in the software to the public
1458
domain. We make this dedication for the benefit of the public at large and to
1459
the detriment of our heirs and successors. We intend this dedication to be an
1460
overt act of relinquishment in perpetuity of all present and future rights to
1461
this software under copyright law.
1462
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1463
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1464
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1465
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
1466
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1467
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1468
------------------------------------------------------------------------------
1469
*/