Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/ext/imgui/imstb_textedit.h
3186 views
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
*/
1470
1471