// [DEAR IMGUI]1// This is a slightly modified version of stb_textedit.h 1.14.2// Those changes would need to be pushed into nothings/stb:3// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)4// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)5// - Added name to struct or it may be forward declared in our code.6// - Added UTF-8 support (see https://github.com/nothings/stb/issues/188 + https://github.com/ocornut/imgui/pull/7925)7// Grep for [DEAR IMGUI] to find the changes.8// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*910// stb_textedit.h - v1.14 - public domain - Sean Barrett11// Development of this library was sponsored by RAD Game Tools12//13// This C header file implements the guts of a multi-line text-editing14// widget; you implement display, word-wrapping, and low-level string15// insertion/deletion, and stb_textedit will map user inputs into16// insertions & deletions, plus updates to the cursor position,17// selection state, and undo state.18//19// It is intended for use in games and other systems that need to build20// their own custom widgets and which do not have heavy text-editing21// requirements (this library is not recommended for use for editing large22// texts, as its performance does not scale and it has limited undo).23//24// Non-trivial behaviors are modelled after Windows text controls.25//26//27// LICENSE28//29// See end of file for license information.30//31//32// DEPENDENCIES33//34// Uses the C runtime function 'memmove', which you can override35// by defining IMSTB_TEXTEDIT_memmove before the implementation.36// Uses no other functions. Performs no runtime allocations.37//38//39// VERSION HISTORY40//41// 1.14 (2021-07-11) page up/down, various fixes42// 1.13 (2019-02-07) fix bug in undo size management43// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash44// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield45// 1.10 (2016-10-25) suppress warnings about casting away const with -Wcast-qual46// 1.9 (2016-08-27) customizable move-by-word47// 1.8 (2016-04-02) better keyboard handling when mouse button is down48// 1.7 (2015-09-13) change y range handling in case baseline is non-049// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove50// 1.5 (2014-09-10) add support for secondary keys for OS X51// 1.4 (2014-08-17) fix signed/unsigned warnings52// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary53// 1.2 (2014-05-27) fix some RAD types that had crept into the new code54// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )55// 1.0 (2012-07-26) improve documentation, initial public release56// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode57// 0.2 (2011-11-28) fixes to undo/redo58// 0.1 (2010-07-08) initial version59//60// ADDITIONAL CONTRIBUTORS61//62// Ulf Winklemann: move-by-word in 1.163// Fabian Giesen: secondary key inputs in 1.564// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.665// Louis Schnellbach: page up/down in 1.1466//67// Bugfixes:68// Scott Graham69// Daniel Keller70// Omar Cornut71// Dan Thompson72//73// USAGE74//75// This file behaves differently depending on what symbols you define76// before including it.77//78//79// Header-file mode:80//81// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,82// it will operate in "header file" mode. In this mode, it declares a83// single public symbol, STB_TexteditState, which encapsulates the current84// state of a text widget (except for the string, which you will store85// separately).86//87// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a88// primitive type that defines a single character (e.g. char, wchar_t, etc).89//90// To save space or increase undo-ability, you can optionally define the91// following things that are used by the undo system:92//93// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position94// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow95// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer96//97// If you don't define these, they are set to permissive types and98// moderate sizes. The undo system does no memory allocations, so99// it grows STB_TexteditState by the worst-case storage which is (in bytes):100//101// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATECOUNT102// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHARCOUNT103//104//105// Implementation mode:106//107// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it108// will compile the implementation of the text edit widget, depending109// on a large number of symbols which must be defined before the include.110//111// The implementation is defined only as static functions. You will then112// need to provide your own APIs in the same file which will access the113// static functions.114//115// The basic concept is that you provide a "string" object which116// behaves like an array of characters. stb_textedit uses indices to117// refer to positions in the string, implicitly representing positions118// in the displayed textedit. This is true for both plain text and119// rich text; even with rich text stb_truetype interacts with your120// code as if there was an array of all the displayed characters.121//122// Symbols that must be the same in header-file and implementation mode:123//124// STB_TEXTEDIT_CHARTYPE the character type125// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position126// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow127// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer128//129// Symbols you must define for implementation mode:130//131// STB_TEXTEDIT_STRING the type of object representing a string being edited,132// typically this is a wrapper object with other data you need133//134// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))135// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters136// starting from character #n (see discussion below)137// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character138// to the xpos of the i+1'th char for a line of characters139// starting at character #n (i.e. accounts for kerning140// with previous char)141// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character142// (return type is int, -1 means not valid to insert)143// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based144// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize145// as manually wordwrapping for end-of-line positioning146//147// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i148// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)149//150// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key151//152// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left153// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right154// STB_TEXTEDIT_K_UP keyboard input to move cursor up155// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down156// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page157// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page158// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME159// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END160// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME161// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END162// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor163// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor164// STB_TEXTEDIT_K_UNDO keyboard input to perform undo165// STB_TEXTEDIT_K_REDO keyboard input to perform redo166//167// Optional:168// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode169// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'),170// required for default WORDLEFT/WORDRIGHT handlers171// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to172// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to173// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT174// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT175// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line176// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line177// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text178// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text179//180// Keyboard input must be encoded as a single integer value; e.g. a character code181// and some bitflags that represent shift states. to simplify the interface, SHIFT must182// be a bitflag, so we can test the shifted state of cursor movements to allow selection,183// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.184//185// You can encode other things, such as CONTROL or ALT, in additional bits, and186// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,187// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN188// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,189// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the190// API below. The control keys will only match WM_KEYDOWN events because of the191// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN192// bit so it only decodes WM_CHAR events.193//194// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed195// row of characters assuming they start on the i'th character--the width and196// the height and the number of characters consumed. This allows this library197// to traverse the entire layout incrementally. You need to compute word-wrapping198// here.199//200// Each textfield keeps its own insert mode state, which is not how normal201// applications work. To keep an app-wide insert mode, update/copy the202// "insert_mode" field of STB_TexteditState before/after calling API functions.203//204// API205//206// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)207//208// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)209// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)210// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)211// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)212// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)213// void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)214//215// Each of these functions potentially updates the string and updates the216// state.217//218// initialize_state:219// set the textedit state to a known good default state when initially220// constructing the textedit.221//222// click:223// call this with the mouse x,y on a mouse down; it will update the cursor224// and reset the selection start/end to the cursor point. the x,y must225// be relative to the text widget, with (0,0) being the top left.226//227// drag:228// call this with the mouse x,y on a mouse drag/up; it will update the229// cursor and the selection end point230//231// cut:232// call this to delete the current selection; returns true if there was233// one. you should FIRST copy the current selection to the system paste buffer.234// (To copy, just copy the current selection out of the string yourself.)235//236// paste:237// call this to paste text at the current cursor point or over the current238// selection if there is one.239//240// key:241// call this for keyboard inputs sent to the textfield. you can use it242// for "key down" events or for "translated" key events. if you need to243// do both (as in Win32), or distinguish Unicode characters from control244// inputs, set a high bit to distinguish the two; then you can define the245// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit246// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is247// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to248// anything other type you want before including.249// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are250// transformed into text and stb_textedit_text() is automatically called.251//252// text: [DEAR IMGUI] added 2024-09253// call this to text inputs sent to the textfield.254//255//256// When rendering, you can read the cursor position and selection state from257// the STB_TexteditState.258//259//260// Notes:261//262// This is designed to be usable in IMGUI, so it allows for the possibility of263// running in an IMGUI that has NOT cached the multi-line layout. For this264// reason, it provides an interface that is compatible with computing the265// layout incrementally--we try to make sure we make as few passes through266// as possible. (For example, to locate the mouse pointer in the text, we267// could define functions that return the X and Y positions of characters268// and binary search Y and then X, but if we're doing dynamic layout this269// will run the layout algorithm many times, so instead we manually search270// forward in one pass. Similar logic applies to e.g. up-arrow and271// down-arrow movement.)272//273// If it's run in a widget that *has* cached the layout, then this is less274// efficient, but it's not horrible on modern computers. But you wouldn't275// want to edit million-line files with it.276277278////////////////////////////////////////////////////////////////////////////279////////////////////////////////////////////////////////////////////////////280////281//// Header-file mode282////283////284285#ifndef INCLUDE_IMSTB_TEXTEDIT_H286#define INCLUDE_IMSTB_TEXTEDIT_H287288////////////////////////////////////////////////////////////////////////289//290// STB_TexteditState291//292// Definition of STB_TexteditState which you should store293// per-textfield; it includes cursor position, selection state,294// and undo state.295//296297#ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT298#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99299#endif300#ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT301#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999302#endif303#ifndef IMSTB_TEXTEDIT_CHARTYPE304#define IMSTB_TEXTEDIT_CHARTYPE int305#endif306#ifndef IMSTB_TEXTEDIT_POSITIONTYPE307#define IMSTB_TEXTEDIT_POSITIONTYPE int308#endif309310typedef struct311{312// private data313IMSTB_TEXTEDIT_POSITIONTYPE where;314IMSTB_TEXTEDIT_POSITIONTYPE insert_length;315IMSTB_TEXTEDIT_POSITIONTYPE delete_length;316int char_storage;317} StbUndoRecord;318319typedef struct320{321// private data322StbUndoRecord undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT];323IMSTB_TEXTEDIT_CHARTYPE undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT];324short undo_point, redo_point;325int undo_char_point, redo_char_point;326} StbUndoState;327328typedef struct STB_TexteditState329{330/////////////////////331//332// public data333//334335int cursor;336// position of the text cursor within the string337338int select_start; // selection start point339int 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. when342// dragging the mouse, start is where the initial click was, and you343// can drag in either direction)344345unsigned char insert_mode;346// each textfield keeps its own insert mode state. to keep an app-wide347// insert mode, copy this value in/out of the app state348349int 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.352353/////////////////////354//355// private data356//357unsigned char cursor_at_end_of_line; // not implemented yet358unsigned char initialized;359unsigned char has_preferred_x;360unsigned char single_line;361unsigned char padding1, padding2, padding3;362float preferred_x; // this determines where the cursor up/down tries to seek to along x363StbUndoState undostate;364} STB_TexteditState;365366367////////////////////////////////////////////////////////////////////////368//369// StbTexteditRow370//371// Result of layout query, used by stb_textedit to determine where372// the text in each row is.373374// result of layout query375typedef struct376{377float x0,x1; // starting x location, end x location (allows for align=right, etc)378float baseline_y_delta; // position of baseline relative to previous row's baseline379float ymin,ymax; // height of row above and below baseline380int num_chars;381} StbTexteditRow;382#endif //INCLUDE_IMSTB_TEXTEDIT_H383384385////////////////////////////////////////////////////////////////////////////386////////////////////////////////////////////////////////////////////////////387////388//// Implementation mode389////390////391392393// implementation isn't include-guarded, since it might have indirectly394// included just the "header" portion395#ifdef IMSTB_TEXTEDIT_IMPLEMENTATION396397#ifndef IMSTB_TEXTEDIT_memmove398#include <string.h>399#define IMSTB_TEXTEDIT_memmove memmove400#endif401402403/////////////////////////////////////////////////////////////////////////////404//405// Mouse input handling406//407408// traverse the layout to locate the nearest character to a display position409static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)410{411StbTexteditRow r;412int n = STB_TEXTEDIT_STRINGLEN(str);413float base_y = 0, prev_x;414int i=0, k;415416r.x0 = r.x1 = 0;417r.ymin = r.ymax = 0;418r.num_chars = 0;419420// search rows to find one that straddles 'y'421while (i < n) {422STB_TEXTEDIT_LAYOUTROW(&r, str, i);423if (r.num_chars <= 0)424return n;425426if (i==0 && y < base_y + r.ymin)427return 0;428429if (y < base_y + r.ymax)430break;431432i += r.num_chars;433base_y += r.baseline_y_delta;434}435436// below all text, return 'after' last character437if (i >= n)438return n;439440// check if it's before the beginning of the line441if (x < r.x0)442return i;443444// check if it's before the end of the line445if (x < r.x1) {446// search characters in row for one that straddles 'x'447prev_x = r.x0;448for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {449float w = STB_TEXTEDIT_GETWIDTH(str, i, k);450if (x < prev_x+w) {451if (x < prev_x+w/2)452return k+i;453else454return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);455}456prev_x += w;457}458// shouldn't happen, but if it does, fall through to end-of-line case459}460461// if the last character is a newline, return that. otherwise return 'after' the last character462if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)463return i+r.num_chars-1;464else465return i+r.num_chars;466}467468// API click: on mouse down, move the cursor to the clicked location, and reset the selection469static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)470{471// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse472// goes off the top or bottom of the text473if( state->single_line )474{475StbTexteditRow r;476STB_TEXTEDIT_LAYOUTROW(&r, str, 0);477y = r.ymin;478}479480state->cursor = stb_text_locate_coord(str, x, y);481state->select_start = state->cursor;482state->select_end = state->cursor;483state->has_preferred_x = 0;484}485486// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location487static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)488{489int p = 0;490491// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse492// goes off the top or bottom of the text493if( state->single_line )494{495StbTexteditRow r;496STB_TEXTEDIT_LAYOUTROW(&r, str, 0);497y = r.ymin;498}499500if (state->select_start == state->select_end)501state->select_start = state->cursor;502503p = stb_text_locate_coord(str, x, y);504state->cursor = state->select_end = p;505}506507/////////////////////////////////////////////////////////////////////////////508//509// Keyboard input handling510//511512// forward declarations513static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);514static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);515static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);516static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);517static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);518519typedef struct520{521float x,y; // position of n'th character522float height; // height of line523int first_char, length; // first char of row, and length524int prev_first; // first char of previous row525} StbFindState;526527// find the x/y location of a character, and remember info about the previous row in528// case we get a move-up event (for page up, we'll have to rescan)529static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line)530{531StbTexteditRow r;532int prev_start = 0;533int z = STB_TEXTEDIT_STRINGLEN(str);534int i=0, first;535536if (n == z && single_line) {537// special case if it's at the end (may not be needed?)538STB_TEXTEDIT_LAYOUTROW(&r, str, 0);539find->y = 0;540find->first_char = 0;541find->length = z;542find->height = r.ymax - r.ymin;543find->x = r.x1;544return;545}546547// search rows to find the one that straddles character n548find->y = 0;549550for(;;) {551STB_TEXTEDIT_LAYOUTROW(&r, str, i);552if (n < i + r.num_chars)553break;554if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line555break; // [DEAR IMGUI]556prev_start = i;557i += r.num_chars;558find->y += r.baseline_y_delta;559if (i == z) // [DEAR IMGUI]560{561r.num_chars = 0; // [DEAR IMGUI]562break; // [DEAR IMGUI]563}564}565566find->first_char = first = i;567find->length = r.num_chars;568find->height = r.ymax - r.ymin;569find->prev_first = prev_start;570571// now scan to find xpos572find->x = r.x0;573for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)574find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);575}576577#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)578579// make the selection/cursor state valid if client altered the string580static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)581{582int n = STB_TEXTEDIT_STRINGLEN(str);583if (STB_TEXT_HAS_SELECTION(state)) {584if (state->select_start > n) state->select_start = n;585if (state->select_end > n) state->select_end = n;586// if clamping forced them to be equal, move the cursor to match587if (state->select_start == state->select_end)588state->cursor = state->select_start;589}590if (state->cursor > n) state->cursor = n;591}592593// delete characters while updating undo594static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)595{596stb_text_makeundo_delete(str, state, where, len);597STB_TEXTEDIT_DELETECHARS(str, where, len);598state->has_preferred_x = 0;599}600601// delete the section602static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)603{604stb_textedit_clamp(str, state);605if (STB_TEXT_HAS_SELECTION(state)) {606if (state->select_start < state->select_end) {607stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);608state->select_end = state->cursor = state->select_start;609} else {610stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);611state->select_start = state->cursor = state->select_end;612}613state->has_preferred_x = 0;614}615}616617// canoncialize the selection so start <= end618static void stb_textedit_sortselection(STB_TexteditState *state)619{620if (state->select_end < state->select_start) {621int temp = state->select_end;622state->select_end = state->select_start;623state->select_start = temp;624}625}626627// move cursor to first character of selection628static void stb_textedit_move_to_first(STB_TexteditState *state)629{630if (STB_TEXT_HAS_SELECTION(state)) {631stb_textedit_sortselection(state);632state->cursor = state->select_start;633state->select_end = state->select_start;634state->has_preferred_x = 0;635}636}637638// move cursor to last character of selection639static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)640{641if (STB_TEXT_HAS_SELECTION(state)) {642stb_textedit_sortselection(state);643stb_textedit_clamp(str, state);644state->cursor = state->select_end;645state->select_start = state->select_end;646state->has_preferred_x = 0;647}648}649650// [DEAR IMGUI]651// Functions must be implemented for UTF8 support652// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.653// There is not necessarily a '[DEAR IMGUI]' at the usage sites.654#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX655#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)656#endif657#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX658#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)659#endif660661#ifdef STB_TEXTEDIT_IS_SPACE662static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )663{664return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;665}666667#ifndef STB_TEXTEDIT_MOVEWORDLEFT668static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )669{670--c; // always move at least one character671while( c >= 0 && !is_word_boundary( str, c ) )672--c;673674if( c < 0 )675c = 0;676677return c;678}679#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous680#endif681682#ifndef STB_TEXTEDIT_MOVEWORDRIGHT683static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )684{685const int len = STB_TEXTEDIT_STRINGLEN(str);686++c; // always move at least one character687while( c < len && !is_word_boundary( str, c ) )688++c;689690if( c > len )691c = len;692693return c;694}695#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next696#endif697698#endif699700// update selection and cursor to match each other701static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)702{703if (!STB_TEXT_HAS_SELECTION(state))704state->select_start = state->select_end = state->cursor;705else706state->cursor = state->select_end;707}708709// API cut: delete selection710static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)711{712if (STB_TEXT_HAS_SELECTION(state)) {713stb_textedit_delete_selection(str,state); // implicitly clamps714state->has_preferred_x = 0;715return 1;716}717return 0;718}719720// API paste: replace existing selection with passed-in text721static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len)722{723// if there's a selection, the paste should delete it724stb_textedit_clamp(str, state);725stb_textedit_delete_selection(str,state);726// try to insert the characters727if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {728stb_text_makeundo_insert(state, state->cursor, len);729state->cursor += len;730state->has_preferred_x = 0;731return 1;732}733// note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details)734return 0;735}736737#ifndef STB_TEXTEDIT_KEYTYPE738#define STB_TEXTEDIT_KEYTYPE int739#endif740741// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.742static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)743{744// can't add newline in single-line mode745if (text[0] == '\n' && state->single_line)746return;747748if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {749stb_text_makeundo_replace(str, state, state->cursor, 1, 1);750STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);751if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {752state->cursor += text_len;753state->has_preferred_x = 0;754}755}756else {757stb_textedit_delete_selection(str, state); // implicitly clamps758if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {759stb_text_makeundo_insert(state, state->cursor, text_len);760state->cursor += text_len;761state->has_preferred_x = 0;762}763}764}765766// API key: process a keyboard input767static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)768{769retry:770switch (key) {771default: {772#ifdef STB_TEXTEDIT_KEYTOTEXT773int c = STB_TEXTEDIT_KEYTOTEXT(key);774if (c > 0) {775IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;776stb_textedit_text(str, state, &ch, 1);777}778#endif779break;780}781782#ifdef STB_TEXTEDIT_K_INSERT783case STB_TEXTEDIT_K_INSERT:784state->insert_mode = !state->insert_mode;785break;786#endif787788case STB_TEXTEDIT_K_UNDO:789stb_text_undo(str, state);790state->has_preferred_x = 0;791break;792793case STB_TEXTEDIT_K_REDO:794stb_text_redo(str, state);795state->has_preferred_x = 0;796break;797798case STB_TEXTEDIT_K_LEFT:799// if currently there's a selection, move cursor to start of selection800if (STB_TEXT_HAS_SELECTION(state))801stb_textedit_move_to_first(state);802else803if (state->cursor > 0)804state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);805state->has_preferred_x = 0;806break;807808case STB_TEXTEDIT_K_RIGHT:809// if currently there's a selection, move cursor to end of selection810if (STB_TEXT_HAS_SELECTION(state))811stb_textedit_move_to_last(str, state);812else813state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);814stb_textedit_clamp(str, state);815state->has_preferred_x = 0;816break;817818case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:819stb_textedit_clamp(str, state);820stb_textedit_prep_selection_at_cursor(state);821// move selection left822if (state->select_end > 0)823state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);824state->cursor = state->select_end;825state->has_preferred_x = 0;826break;827828#ifdef STB_TEXTEDIT_MOVEWORDLEFT829case STB_TEXTEDIT_K_WORDLEFT:830if (STB_TEXT_HAS_SELECTION(state))831stb_textedit_move_to_first(state);832else {833state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);834stb_textedit_clamp( str, state );835}836break;837838case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:839if( !STB_TEXT_HAS_SELECTION( state ) )840stb_textedit_prep_selection_at_cursor(state);841842state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);843state->select_end = state->cursor;844845stb_textedit_clamp( str, state );846break;847#endif848849#ifdef STB_TEXTEDIT_MOVEWORDRIGHT850case STB_TEXTEDIT_K_WORDRIGHT:851if (STB_TEXT_HAS_SELECTION(state))852stb_textedit_move_to_last(str, state);853else {854state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);855stb_textedit_clamp( str, state );856}857break;858859case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:860if( !STB_TEXT_HAS_SELECTION( state ) )861stb_textedit_prep_selection_at_cursor(state);862863state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);864state->select_end = state->cursor;865866stb_textedit_clamp( str, state );867break;868#endif869870case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:871stb_textedit_prep_selection_at_cursor(state);872// move selection right873state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);874stb_textedit_clamp(str, state);875state->cursor = state->select_end;876state->has_preferred_x = 0;877break;878879case STB_TEXTEDIT_K_DOWN:880case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:881case STB_TEXTEDIT_K_PGDOWN:882case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {883StbFindState find;884StbTexteditRow row;885int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;886int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;887int row_count = is_page ? state->row_count_per_page : 1;888889if (!is_page && state->single_line) {890// on windows, up&down in single-line behave like left&right891key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);892goto retry;893}894895if (sel)896stb_textedit_prep_selection_at_cursor(state);897else if (STB_TEXT_HAS_SELECTION(state))898stb_textedit_move_to_last(str, state);899900// compute current position of cursor point901stb_textedit_clamp(str, state);902stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);903904for (j = 0; j < row_count; ++j) {905float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;906int start = find.first_char + find.length;907908if (find.length == 0)909break;910911// [DEAR IMGUI]912// going down while being on the last line shouldn't bring us to that line end913if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)914break;915916// now find character position down a row917state->cursor = start;918STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);919x = row.x0;920for (i=0; i < row.num_chars; ++i) {921float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);922#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE923if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)924break;925#endif926x += dx;927if (x > goal_x)928break;929state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);930}931stb_textedit_clamp(str, state);932933state->has_preferred_x = 1;934state->preferred_x = goal_x;935936if (sel)937state->select_end = state->cursor;938939// go to next line940find.first_char = find.first_char + find.length;941find.length = row.num_chars;942}943break;944}945946case STB_TEXTEDIT_K_UP:947case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:948case STB_TEXTEDIT_K_PGUP:949case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {950StbFindState find;951StbTexteditRow row;952int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;953int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;954int row_count = is_page ? state->row_count_per_page : 1;955956if (!is_page && state->single_line) {957// on windows, up&down become left&right958key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);959goto retry;960}961962if (sel)963stb_textedit_prep_selection_at_cursor(state);964else if (STB_TEXT_HAS_SELECTION(state))965stb_textedit_move_to_first(state);966967// compute current position of cursor point968stb_textedit_clamp(str, state);969stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);970971for (j = 0; j < row_count; ++j) {972float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;973974// can only go up if there's a previous row975if (find.prev_first == find.first_char)976break;977978// now find character position up a row979state->cursor = find.prev_first;980STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);981x = row.x0;982for (i=0; i < row.num_chars; ++i) {983float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);984#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE985if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)986break;987#endif988x += dx;989if (x > goal_x)990break;991state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);992}993stb_textedit_clamp(str, state);994995state->has_preferred_x = 1;996state->preferred_x = goal_x;997998if (sel)999state->select_end = state->cursor;10001001// go to previous line1002// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)1003prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;1004while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)1005--prev_scan;1006find.first_char = find.prev_first;1007find.prev_first = prev_scan;1008}1009break;1010}10111012case STB_TEXTEDIT_K_DELETE:1013case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:1014if (STB_TEXT_HAS_SELECTION(state))1015stb_textedit_delete_selection(str, state);1016else {1017int n = STB_TEXTEDIT_STRINGLEN(str);1018if (state->cursor < n)1019stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);1020}1021state->has_preferred_x = 0;1022break;10231024case STB_TEXTEDIT_K_BACKSPACE:1025case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:1026if (STB_TEXT_HAS_SELECTION(state))1027stb_textedit_delete_selection(str, state);1028else {1029stb_textedit_clamp(str, state);1030if (state->cursor > 0) {1031int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);1032stb_textedit_delete(str, state, prev, state->cursor - prev);1033state->cursor = prev;1034}1035}1036state->has_preferred_x = 0;1037break;10381039#ifdef STB_TEXTEDIT_K_TEXTSTART21040case STB_TEXTEDIT_K_TEXTSTART2:1041#endif1042case STB_TEXTEDIT_K_TEXTSTART:1043state->cursor = state->select_start = state->select_end = 0;1044state->has_preferred_x = 0;1045break;10461047#ifdef STB_TEXTEDIT_K_TEXTEND21048case STB_TEXTEDIT_K_TEXTEND2:1049#endif1050case STB_TEXTEDIT_K_TEXTEND:1051state->cursor = STB_TEXTEDIT_STRINGLEN(str);1052state->select_start = state->select_end = 0;1053state->has_preferred_x = 0;1054break;10551056#ifdef STB_TEXTEDIT_K_TEXTSTART21057case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:1058#endif1059case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:1060stb_textedit_prep_selection_at_cursor(state);1061state->cursor = state->select_end = 0;1062state->has_preferred_x = 0;1063break;10641065#ifdef STB_TEXTEDIT_K_TEXTEND21066case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:1067#endif1068case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:1069stb_textedit_prep_selection_at_cursor(state);1070state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);1071state->has_preferred_x = 0;1072break;107310741075#ifdef STB_TEXTEDIT_K_LINESTART21076case STB_TEXTEDIT_K_LINESTART2:1077#endif1078case STB_TEXTEDIT_K_LINESTART:1079stb_textedit_clamp(str, state);1080stb_textedit_move_to_first(state);1081if (state->single_line)1082state->cursor = 0;1083else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)1084--state->cursor;1085state->has_preferred_x = 0;1086break;10871088#ifdef STB_TEXTEDIT_K_LINEEND21089case STB_TEXTEDIT_K_LINEEND2:1090#endif1091case STB_TEXTEDIT_K_LINEEND: {1092int n = STB_TEXTEDIT_STRINGLEN(str);1093stb_textedit_clamp(str, state);1094stb_textedit_move_to_first(state);1095if (state->single_line)1096state->cursor = n;1097else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)1098++state->cursor;1099state->has_preferred_x = 0;1100break;1101}11021103#ifdef STB_TEXTEDIT_K_LINESTART21104case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:1105#endif1106case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:1107stb_textedit_clamp(str, state);1108stb_textedit_prep_selection_at_cursor(state);1109if (state->single_line)1110state->cursor = 0;1111else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)1112--state->cursor;1113state->select_end = state->cursor;1114state->has_preferred_x = 0;1115break;11161117#ifdef STB_TEXTEDIT_K_LINEEND21118case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:1119#endif1120case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {1121int n = STB_TEXTEDIT_STRINGLEN(str);1122stb_textedit_clamp(str, state);1123stb_textedit_prep_selection_at_cursor(state);1124if (state->single_line)1125state->cursor = n;1126else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)1127++state->cursor;1128state->select_end = state->cursor;1129state->has_preferred_x = 0;1130break;1131}1132}1133}11341135/////////////////////////////////////////////////////////////////////////////1136//1137// Undo processing1138//1139// @OPTIMIZE: the undo/redo buffer should be circular11401141static void stb_textedit_flush_redo(StbUndoState *state)1142{1143state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;1144state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;1145}11461147// discard the oldest entry in the undo list1148static void stb_textedit_discard_undo(StbUndoState *state)1149{1150if (state->undo_point > 0) {1151// if the 0th undo state has characters, clean those up1152if (state->undo_rec[0].char_storage >= 0) {1153int n = state->undo_rec[0].insert_length, i;1154// delete n characters from all other records1155state->undo_char_point -= n;1156IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));1157for (i=0; i < state->undo_point; ++i)1158if (state->undo_rec[i].char_storage >= 0)1159state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it1160}1161--state->undo_point;1162IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));1163}1164}11651166// discard the oldest entry in the redo list--it's bad if this1167// ever happens, but because undo & redo have to store the actual1168// characters in different cases, the redo character buffer can1169// fill up even though the undo buffer didn't1170static void stb_textedit_discard_redo(StbUndoState *state)1171{1172int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1;11731174if (state->redo_point <= k) {1175// if the k'th undo state has characters, clean those up1176if (state->undo_rec[k].char_storage >= 0) {1177int n = state->undo_rec[k].insert_length, i;1178// move the remaining redo character data to the end of the buffer1179state->redo_char_point += n;1180IMSTB_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)));1181// adjust the position of all the other records to account for above memmove1182for (i=state->redo_point; i < k; ++i)1183if (state->undo_rec[i].char_storage >= 0)1184state->undo_rec[i].char_storage += n;1185}1186// now move all the redo records towards the end of the buffer; the first one is at 'redo_point'1187// [DEAR IMGUI]1188size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));1189const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;1190const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;1191IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);1192IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);1193IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);11941195// now move redo_point to point to the new one1196++state->redo_point;1197}1198}11991200static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)1201{1202// any time we create a new undo record, we discard redo1203stb_textedit_flush_redo(state);12041205// if we have no free records, we have to make room, by sliding the1206// existing records down1207if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1208stb_textedit_discard_undo(state);12091210// if the characters to store won't possibly fit in the buffer, we can't undo1211if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) {1212state->undo_point = 0;1213state->undo_char_point = 0;1214return NULL;1215}12161217// if we don't have enough free characters in the buffer, we have to make room1218while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT)1219stb_textedit_discard_undo(state);12201221return &state->undo_rec[state->undo_point++];1222}12231224static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)1225{1226StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);1227if (r == NULL)1228return NULL;12291230r->where = pos;1231r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len;1232r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len;12331234if (insert_len == 0) {1235r->char_storage = -1;1236return NULL;1237} else {1238r->char_storage = state->undo_char_point;1239state->undo_char_point += insert_len;1240return &state->undo_char[r->char_storage];1241}1242}12431244static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)1245{1246StbUndoState *s = &state->undostate;1247StbUndoRecord u, *r;1248if (s->undo_point == 0)1249return;12501251// we need to do two things: apply the undo record, and create a redo record1252u = s->undo_rec[s->undo_point-1];1253r = &s->undo_rec[s->redo_point-1];1254r->char_storage = -1;12551256r->insert_length = u.delete_length;1257r->delete_length = u.insert_length;1258r->where = u.where;12591260if (u.delete_length) {1261// if the undo record says to delete characters, then the redo record will1262// need to re-insert the characters that get deleted, so we need to store1263// them.12641265// there are three cases:1266// there's enough room to store the characters1267// characters stored for *redoing* don't leave room for redo1268// characters stored for *undoing* don't leave room for redo1269// if the last is true, we have to bail12701271if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) {1272// the undo records take up too much character space; there's no space to store the redo characters1273r->insert_length = 0;1274} else {1275int i;12761277// there's definitely room to store the characters eventually1278while (s->undo_char_point + u.delete_length > s->redo_char_point) {1279// should never happen:1280if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1281return;1282// there's currently not enough room, so discard a redo record1283stb_textedit_discard_redo(s);1284}1285r = &s->undo_rec[s->redo_point-1];12861287r->char_storage = s->redo_char_point - u.delete_length;1288s->redo_char_point = s->redo_char_point - u.delete_length;12891290// now save the characters1291for (i=0; i < u.delete_length; ++i)1292s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);1293}12941295// now we can carry out the deletion1296STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);1297}12981299// check type of recorded action:1300if (u.insert_length) {1301// easy case: was a deletion, so we need to insert n characters1302STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);1303s->undo_char_point -= u.insert_length;1304}13051306state->cursor = u.where + u.insert_length;13071308s->undo_point--;1309s->redo_point--;1310}13111312static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)1313{1314StbUndoState *s = &state->undostate;1315StbUndoRecord *u, r;1316if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1317return;13181319// we need to do two things: apply the redo record, and create an undo record1320u = &s->undo_rec[s->undo_point];1321r = s->undo_rec[s->redo_point];13221323// we KNOW there must be room for the undo record, because the redo record1324// was derived from an undo record13251326u->delete_length = r.insert_length;1327u->insert_length = r.delete_length;1328u->where = r.where;1329u->char_storage = -1;13301331if (r.delete_length) {1332// the redo record requires us to delete characters, so the undo record1333// needs to store the characters13341335if (s->undo_char_point + u->insert_length > s->redo_char_point) {1336u->insert_length = 0;1337u->delete_length = 0;1338} else {1339int i;1340u->char_storage = s->undo_char_point;1341s->undo_char_point = s->undo_char_point + u->insert_length;13421343// now save the characters1344for (i=0; i < u->insert_length; ++i)1345s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);1346}13471348STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);1349}13501351if (r.insert_length) {1352// easy case: need to insert n characters1353STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);1354s->redo_char_point += r.insert_length;1355}13561357state->cursor = r.where + r.insert_length;13581359s->undo_point++;1360s->redo_point++;1361}13621363static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)1364{1365stb_text_createundo(&state->undostate, where, 0, length);1366}13671368static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)1369{1370int i;1371IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);1372if (p) {1373for (i=0; i < length; ++i)1374p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);1375}1376}13771378static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)1379{1380int i;1381IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);1382if (p) {1383for (i=0; i < old_length; ++i)1384p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);1385}1386}13871388// reset the state to default1389static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)1390{1391state->undostate.undo_point = 0;1392state->undostate.undo_char_point = 0;1393state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;1394state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;1395state->select_end = state->select_start = 0;1396state->cursor = 0;1397state->has_preferred_x = 0;1398state->preferred_x = 0;1399state->cursor_at_end_of_line = 0;1400state->initialized = 1;1401state->single_line = (unsigned char) is_single_line;1402state->insert_mode = 0;1403state->row_count_per_page = 0;1404}14051406// API initialize1407static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)1408{1409stb_textedit_clear_state(state, is_single_line);1410}14111412#if defined(__GNUC__) || defined(__clang__)1413#pragma GCC diagnostic push1414#pragma GCC diagnostic ignored "-Wcast-qual"1415#endif14161417static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len)1418{1419return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len);1420}14211422#if defined(__GNUC__) || defined(__clang__)1423#pragma GCC diagnostic pop1424#endif14251426#endif//IMSTB_TEXTEDIT_IMPLEMENTATION14271428/*1429------------------------------------------------------------------------------1430This software is available under 2 licenses -- choose whichever you prefer.1431------------------------------------------------------------------------------1432ALTERNATIVE A - MIT License1433Copyright (c) 2017 Sean Barrett1434Permission is hereby granted, free of charge, to any person obtaining a copy of1435this software and associated documentation files (the "Software"), to deal in1436the Software without restriction, including without limitation the rights to1437use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies1438of the Software, and to permit persons to whom the Software is furnished to do1439so, subject to the following conditions:1440The above copyright notice and this permission notice shall be included in all1441copies or substantial portions of the Software.1442THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR1443IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,1444FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE1445AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER1446LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,1447OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE1448SOFTWARE.1449------------------------------------------------------------------------------1450ALTERNATIVE B - Public Domain (www.unlicense.org)1451This is free and unencumbered software released into the public domain.1452Anyone is free to copy, modify, publish, use, compile, sell, or distribute this1453software, either in source code form or as a compiled binary, for any purpose,1454commercial or non-commercial, and by any means.1455In jurisdictions that recognize copyright laws, the author or authors of this1456software dedicate any and all copyright interest in the software to the public1457domain. We make this dedication for the benefit of the public at large and to1458the detriment of our heirs and successors. We intend this dedication to be an1459overt act of relinquishment in perpetuity of all present and future rights to1460this software under copyright law.1461THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR1462IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,1463FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE1464AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN1465ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION1466WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.1467------------------------------------------------------------------------------1468*/146914701471