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