Path: blob/master/dep/imgui/include/imstb_textedit.h
4246 views
// [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// (not supported if you want to use UTF-8, see below)144// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based145// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize146// as manually wordwrapping for end-of-line positioning147//148// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i149// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)150//151// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key152//153// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left154// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right155// STB_TEXTEDIT_K_UP keyboard input to move cursor up156// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down157// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page158// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page159// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME160// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END161// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME162// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END163// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor164// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor165// STB_TEXTEDIT_K_UNDO keyboard input to perform undo166// STB_TEXTEDIT_K_REDO keyboard input to perform redo167//168// Optional:169// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode170// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'),171// required for default WORDLEFT/WORDRIGHT handlers172// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to173// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to174// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT175// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT176// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line177// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line178// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text179// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text180//181// To support UTF-8:182//183// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character184// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character185// Do NOT define STB_TEXTEDIT_KEYTOTEXT.186// Instead, call stb_textedit_text() directly for text contents.187//188// Keyboard input must be encoded as a single integer value; e.g. a character code189// and some bitflags that represent shift states. to simplify the interface, SHIFT must190// be a bitflag, so we can test the shifted state of cursor movements to allow selection,191// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.192//193// You can encode other things, such as CONTROL or ALT, in additional bits, and194// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,195// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN196// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,197// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the198// API below. The control keys will only match WM_KEYDOWN events because of the199// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN200// bit so it only decodes WM_CHAR events.201//202// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed203// row of characters assuming they start on the i'th character--the width and204// the height and the number of characters consumed. This allows this library205// to traverse the entire layout incrementally. You need to compute word-wrapping206// here.207//208// Each textfield keeps its own insert mode state, which is not how normal209// applications work. To keep an app-wide insert mode, update/copy the210// "insert_mode" field of STB_TexteditState before/after calling API functions.211//212// API213//214// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)215//216// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)217// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)218// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)219// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)220// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)221// void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)222//223// Each of these functions potentially updates the string and updates the224// state.225//226// initialize_state:227// set the textedit state to a known good default state when initially228// constructing the textedit.229//230// click:231// call this with the mouse x,y on a mouse down; it will update the cursor232// and reset the selection start/end to the cursor point. the x,y must233// be relative to the text widget, with (0,0) being the top left.234//235// drag:236// call this with the mouse x,y on a mouse drag/up; it will update the237// cursor and the selection end point238//239// cut:240// call this to delete the current selection; returns true if there was241// one. you should FIRST copy the current selection to the system paste buffer.242// (To copy, just copy the current selection out of the string yourself.)243//244// paste:245// call this to paste text at the current cursor point or over the current246// selection if there is one.247//248// key:249// call this for keyboard inputs sent to the textfield. you can use it250// for "key down" events or for "translated" key events. if you need to251// do both (as in Win32), or distinguish Unicode characters from control252// inputs, set a high bit to distinguish the two; then you can define the253// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit254// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is255// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to256// anything other type you want before including.257// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are258// transformed into text and stb_textedit_text() is automatically called.259//260// text: (added 2025)261// call this to directly send text input the textfield, which is required262// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()263// cannot infer text length.264//265//266// When rendering, you can read the cursor position and selection state from267// the STB_TexteditState.268//269//270// Notes:271//272// This is designed to be usable in IMGUI, so it allows for the possibility of273// running in an IMGUI that has NOT cached the multi-line layout. For this274// reason, it provides an interface that is compatible with computing the275// layout incrementally--we try to make sure we make as few passes through276// as possible. (For example, to locate the mouse pointer in the text, we277// could define functions that return the X and Y positions of characters278// and binary search Y and then X, but if we're doing dynamic layout this279// will run the layout algorithm many times, so instead we manually search280// forward in one pass. Similar logic applies to e.g. up-arrow and281// down-arrow movement.)282//283// If it's run in a widget that *has* cached the layout, then this is less284// efficient, but it's not horrible on modern computers. But you wouldn't285// want to edit million-line files with it.286287288////////////////////////////////////////////////////////////////////////////289////////////////////////////////////////////////////////////////////////////290////291//// Header-file mode292////293////294295#ifndef INCLUDE_IMSTB_TEXTEDIT_H296#define INCLUDE_IMSTB_TEXTEDIT_H297298////////////////////////////////////////////////////////////////////////299//300// STB_TexteditState301//302// Definition of STB_TexteditState which you should store303// per-textfield; it includes cursor position, selection state,304// and undo state.305//306307#ifndef IMSTB_TEXTEDIT_UNDOSTATECOUNT308#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99309#endif310#ifndef IMSTB_TEXTEDIT_UNDOCHARCOUNT311#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999312#endif313#ifndef IMSTB_TEXTEDIT_CHARTYPE314#define IMSTB_TEXTEDIT_CHARTYPE int315#endif316#ifndef IMSTB_TEXTEDIT_POSITIONTYPE317#define IMSTB_TEXTEDIT_POSITIONTYPE int318#endif319320typedef struct321{322// private data323IMSTB_TEXTEDIT_POSITIONTYPE where;324IMSTB_TEXTEDIT_POSITIONTYPE insert_length;325IMSTB_TEXTEDIT_POSITIONTYPE delete_length;326int char_storage;327} StbUndoRecord;328329typedef struct330{331// private data332StbUndoRecord undo_rec [IMSTB_TEXTEDIT_UNDOSTATECOUNT];333IMSTB_TEXTEDIT_CHARTYPE undo_char[IMSTB_TEXTEDIT_UNDOCHARCOUNT];334short undo_point, redo_point;335int undo_char_point, redo_char_point;336} StbUndoState;337338typedef struct STB_TexteditState339{340/////////////////////341//342// public data343//344345int cursor;346// position of the text cursor within the string347348int select_start; // selection start point349int select_end;350// selection start and end point in characters; if equal, no selection.351// note that start may be less than or greater than end (e.g. when352// dragging the mouse, start is where the initial click was, and you353// can drag in either direction)354355unsigned char insert_mode;356// each textfield keeps its own insert mode state. to keep an app-wide357// insert mode, copy this value in/out of the app state358359int row_count_per_page;360// page size in number of row.361// this value MUST be set to >0 for pageup or pagedown in multilines documents.362363/////////////////////364//365// private data366//367unsigned char cursor_at_end_of_line; // not implemented yet368unsigned char initialized;369unsigned char has_preferred_x;370unsigned char single_line;371unsigned char padding1, padding2, padding3;372float preferred_x; // this determines where the cursor up/down tries to seek to along x373StbUndoState undostate;374} STB_TexteditState;375376377////////////////////////////////////////////////////////////////////////378//379// StbTexteditRow380//381// Result of layout query, used by stb_textedit to determine where382// the text in each row is.383384// result of layout query385typedef struct386{387float x0,x1; // starting x location, end x location (allows for align=right, etc)388float baseline_y_delta; // position of baseline relative to previous row's baseline389float ymin,ymax; // height of row above and below baseline390int num_chars;391} StbTexteditRow;392#endif //INCLUDE_IMSTB_TEXTEDIT_H393394395////////////////////////////////////////////////////////////////////////////396////////////////////////////////////////////////////////////////////////////397////398//// Implementation mode399////400////401402403// implementation isn't include-guarded, since it might have indirectly404// included just the "header" portion405#ifdef IMSTB_TEXTEDIT_IMPLEMENTATION406407#ifndef IMSTB_TEXTEDIT_memmove408#include <string.h>409#define IMSTB_TEXTEDIT_memmove memmove410#endif411412// [DEAR IMGUI]413// Functions must be implemented for UTF8 support414// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.415// There is not necessarily a '[DEAR IMGUI]' at the usage sites.416#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX417#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)418#endif419#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX420#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)421#endif422423/////////////////////////////////////////////////////////////////////////////424//425// Mouse input handling426//427428// traverse the layout to locate the nearest character to a display position429static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)430{431StbTexteditRow r;432int n = STB_TEXTEDIT_STRINGLEN(str);433float base_y = 0, prev_x;434int i=0, k;435436r.x0 = r.x1 = 0;437r.ymin = r.ymax = 0;438r.num_chars = 0;439440// search rows to find one that straddles 'y'441while (i < n) {442STB_TEXTEDIT_LAYOUTROW(&r, str, i);443if (r.num_chars <= 0)444return n;445446if (i==0 && y < base_y + r.ymin)447return 0;448449if (y < base_y + r.ymax)450break;451452i += r.num_chars;453base_y += r.baseline_y_delta;454}455456// below all text, return 'after' last character457if (i >= n)458return n;459460// check if it's before the beginning of the line461if (x < r.x0)462return i;463464// check if it's before the end of the line465if (x < r.x1) {466// search characters in row for one that straddles 'x'467prev_x = r.x0;468for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {469float w = STB_TEXTEDIT_GETWIDTH(str, i, k);470if (x < prev_x+w) {471if (x < prev_x+w/2)472return k+i;473else474return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);475}476prev_x += w;477}478// shouldn't happen, but if it does, fall through to end-of-line case479}480481// if the last character is a newline, return that. otherwise return 'after' the last character482if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)483return i+r.num_chars-1;484else485return i+r.num_chars;486}487488// API click: on mouse down, move the cursor to the clicked location, and reset the selection489static void stb_textedit_click(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)490{491// 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}499500state->cursor = stb_text_locate_coord(str, x, y);501state->select_start = state->cursor;502state->select_end = state->cursor;503state->has_preferred_x = 0;504}505506// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location507static void stb_textedit_drag(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)508{509int p = 0;510511// In single-line mode, just always make y = 0. This lets the drag keep working if the mouse512// goes off the top or bottom of the text513if( state->single_line )514{515StbTexteditRow r;516STB_TEXTEDIT_LAYOUTROW(&r, str, 0);517y = r.ymin;518}519520if (state->select_start == state->select_end)521state->select_start = state->cursor;522523p = stb_text_locate_coord(str, x, y);524state->cursor = state->select_end = p;525}526527/////////////////////////////////////////////////////////////////////////////528//529// Keyboard input handling530//531532// forward declarations533static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);534static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state);535static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);536static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);537static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);538539typedef struct540{541float x,y; // position of n'th character542float height; // height of line543int first_char, length; // first char of row, and length544int prev_first; // first char of previous row545} StbFindState;546547// find the x/y location of a character, and remember info about the previous row in548// case we get a move-up event (for page up, we'll have to rescan)549static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING *str, int n, int single_line)550{551StbTexteditRow r;552int prev_start = 0;553int z = STB_TEXTEDIT_STRINGLEN(str);554int i=0, first;555556if (n == z && single_line) {557// special case if it's at the end (may not be needed?)558STB_TEXTEDIT_LAYOUTROW(&r, str, 0);559find->y = 0;560find->first_char = 0;561find->length = z;562find->height = r.ymax - r.ymin;563find->x = r.x1;564return;565}566567// search rows to find the one that straddles character n568find->y = 0;569570for(;;) {571STB_TEXTEDIT_LAYOUTROW(&r, str, i);572if (n < i + r.num_chars)573break;574if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line575break; // [DEAR IMGUI]576prev_start = i;577i += r.num_chars;578find->y += r.baseline_y_delta;579if (i == z) // [DEAR IMGUI]580{581r.num_chars = 0; // [DEAR IMGUI]582break; // [DEAR IMGUI]583}584}585586find->first_char = first = i;587find->length = r.num_chars;588find->height = r.ymax - r.ymin;589find->prev_first = prev_start;590591// now scan to find xpos592find->x = r.x0;593for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)594find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);595}596597#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)598599// make the selection/cursor state valid if client altered the string600static void stb_textedit_clamp(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)601{602int n = STB_TEXTEDIT_STRINGLEN(str);603if (STB_TEXT_HAS_SELECTION(state)) {604if (state->select_start > n) state->select_start = n;605if (state->select_end > n) state->select_end = n;606// if clamping forced them to be equal, move the cursor to match607if (state->select_start == state->select_end)608state->cursor = state->select_start;609}610if (state->cursor > n) state->cursor = n;611}612613// delete characters while updating undo614static void stb_textedit_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)615{616stb_text_makeundo_delete(str, state, where, len);617STB_TEXTEDIT_DELETECHARS(str, where, len);618state->has_preferred_x = 0;619}620621// delete the section622static void stb_textedit_delete_selection(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)623{624stb_textedit_clamp(str, state);625if (STB_TEXT_HAS_SELECTION(state)) {626if (state->select_start < state->select_end) {627stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);628state->select_end = state->cursor = state->select_start;629} else {630stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);631state->select_start = state->cursor = state->select_end;632}633state->has_preferred_x = 0;634}635}636637// canoncialize the selection so start <= end638static void stb_textedit_sortselection(STB_TexteditState *state)639{640if (state->select_end < state->select_start) {641int temp = state->select_end;642state->select_end = state->select_start;643state->select_start = temp;644}645}646647// move cursor to first character of selection648static void stb_textedit_move_to_first(STB_TexteditState *state)649{650if (STB_TEXT_HAS_SELECTION(state)) {651stb_textedit_sortselection(state);652state->cursor = state->select_start;653state->select_end = state->select_start;654state->has_preferred_x = 0;655}656}657658// move cursor to last character of selection659static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)660{661if (STB_TEXT_HAS_SELECTION(state)) {662stb_textedit_sortselection(state);663stb_textedit_clamp(str, state);664state->cursor = state->select_end;665state->select_start = state->select_end;666state->has_preferred_x = 0;667}668}669670#ifdef STB_TEXTEDIT_IS_SPACE671static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )672{673return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;674}675676#ifndef STB_TEXTEDIT_MOVEWORDLEFT677static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )678{679c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character680while (c >= 0 && !is_word_boundary(str, c))681c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);682683if( c < 0 )684c = 0;685686return c;687}688#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous689#endif690691#ifndef STB_TEXTEDIT_MOVEWORDRIGHT692static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )693{694const int len = STB_TEXTEDIT_STRINGLEN(str);695c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character696while( c < len && !is_word_boundary( str, c ) )697c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);698699if( c > len )700c = len;701702return c;703}704#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next705#endif706707#endif708709// update selection and cursor to match each other710static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)711{712if (!STB_TEXT_HAS_SELECTION(state))713state->select_start = state->select_end = state->cursor;714else715state->cursor = state->select_end;716}717718// API cut: delete selection719static int stb_textedit_cut(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)720{721if (STB_TEXT_HAS_SELECTION(state)) {722stb_textedit_delete_selection(str,state); // implicitly clamps723state->has_preferred_x = 0;724return 1;725}726return 0;727}728729// API paste: replace existing selection with passed-in text730static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE *text, int len)731{732// if there's a selection, the paste should delete it733stb_textedit_clamp(str, state);734stb_textedit_delete_selection(str,state);735// try to insert the characters736if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {737stb_text_makeundo_insert(state, state->cursor, len);738state->cursor += len;739state->has_preferred_x = 0;740return 1;741}742// note: paste failure will leave deleted selection, may be restored with an undo (see https://github.com/nothings/stb/issues/734 for details)743return 0;744}745746#ifndef STB_TEXTEDIT_KEYTYPE747#define STB_TEXTEDIT_KEYTYPE int748#endif749750// API key: process text input751// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.752static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)753{754// can't add newline in single-line mode755if (text[0] == '\n' && state->single_line)756return;757758if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {759stb_text_makeundo_replace(str, state, state->cursor, 1, 1);760STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);761if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {762state->cursor += text_len;763state->has_preferred_x = 0;764}765} else {766stb_textedit_delete_selection(str, state); // implicitly clamps767if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {768stb_text_makeundo_insert(state, state->cursor, text_len);769state->cursor += text_len;770state->has_preferred_x = 0;771}772}773}774775// API key: process a keyboard input776static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)777{778retry:779switch (key) {780default: {781#ifdef STB_TEXTEDIT_KEYTOTEXT782// This is not suitable for UTF-8 support.783int c = STB_TEXTEDIT_KEYTOTEXT(key);784if (c > 0) {785IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;786stb_textedit_text(str, state, &ch, 1);787}788#endif789break;790}791792#ifdef STB_TEXTEDIT_K_INSERT793case STB_TEXTEDIT_K_INSERT:794state->insert_mode = !state->insert_mode;795break;796#endif797798case STB_TEXTEDIT_K_UNDO:799stb_text_undo(str, state);800state->has_preferred_x = 0;801break;802803case STB_TEXTEDIT_K_REDO:804stb_text_redo(str, state);805state->has_preferred_x = 0;806break;807808case STB_TEXTEDIT_K_LEFT:809// if currently there's a selection, move cursor to start of selection810if (STB_TEXT_HAS_SELECTION(state))811stb_textedit_move_to_first(state);812else813if (state->cursor > 0)814state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);815state->has_preferred_x = 0;816break;817818case STB_TEXTEDIT_K_RIGHT:819// if currently there's a selection, move cursor to end of selection820if (STB_TEXT_HAS_SELECTION(state))821stb_textedit_move_to_last(str, state);822else823state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);824stb_textedit_clamp(str, state);825state->has_preferred_x = 0;826break;827828case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:829stb_textedit_clamp(str, state);830stb_textedit_prep_selection_at_cursor(state);831// move selection left832if (state->select_end > 0)833state->select_end = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->select_end);834state->cursor = state->select_end;835state->has_preferred_x = 0;836break;837838#ifdef STB_TEXTEDIT_MOVEWORDLEFT839case STB_TEXTEDIT_K_WORDLEFT:840if (STB_TEXT_HAS_SELECTION(state))841stb_textedit_move_to_first(state);842else {843state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);844stb_textedit_clamp( str, state );845}846break;847848case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:849if( !STB_TEXT_HAS_SELECTION( state ) )850stb_textedit_prep_selection_at_cursor(state);851852state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);853state->select_end = state->cursor;854855stb_textedit_clamp( str, state );856break;857#endif858859#ifdef STB_TEXTEDIT_MOVEWORDRIGHT860case STB_TEXTEDIT_K_WORDRIGHT:861if (STB_TEXT_HAS_SELECTION(state))862stb_textedit_move_to_last(str, state);863else {864state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);865stb_textedit_clamp( str, state );866}867break;868869case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:870if( !STB_TEXT_HAS_SELECTION( state ) )871stb_textedit_prep_selection_at_cursor(state);872873state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);874state->select_end = state->cursor;875876stb_textedit_clamp( str, state );877break;878#endif879880case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:881stb_textedit_prep_selection_at_cursor(state);882// move selection right883state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);884stb_textedit_clamp(str, state);885state->cursor = state->select_end;886state->has_preferred_x = 0;887break;888889case STB_TEXTEDIT_K_DOWN:890case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:891case STB_TEXTEDIT_K_PGDOWN:892case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {893StbFindState find;894StbTexteditRow row;895int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;896int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;897int row_count = is_page ? state->row_count_per_page : 1;898899if (!is_page && state->single_line) {900// on windows, up&down in single-line behave like left&right901key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);902goto retry;903}904905if (sel)906stb_textedit_prep_selection_at_cursor(state);907else if (STB_TEXT_HAS_SELECTION(state))908stb_textedit_move_to_last(str, state);909910// compute current position of cursor point911stb_textedit_clamp(str, state);912stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);913914for (j = 0; j < row_count; ++j) {915float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;916int start = find.first_char + find.length;917918if (find.length == 0)919break;920921// [DEAR IMGUI]922// going down while being on the last line shouldn't bring us to that line end923if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)924break;925926// now find character position down a row927state->cursor = start;928STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);929x = row.x0;930for (i=0; i < row.num_chars; ) {931float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);932int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);933#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE934if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)935break;936#endif937x += dx;938if (x > goal_x)939break;940i += next - state->cursor;941state->cursor = next;942}943stb_textedit_clamp(str, state);944945state->has_preferred_x = 1;946state->preferred_x = goal_x;947948if (sel)949state->select_end = state->cursor;950951// go to next line952find.first_char = find.first_char + find.length;953find.length = row.num_chars;954}955break;956}957958case STB_TEXTEDIT_K_UP:959case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:960case STB_TEXTEDIT_K_PGUP:961case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {962StbFindState find;963StbTexteditRow row;964int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;965int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;966int row_count = is_page ? state->row_count_per_page : 1;967968if (!is_page && state->single_line) {969// on windows, up&down become left&right970key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);971goto retry;972}973974if (sel)975stb_textedit_prep_selection_at_cursor(state);976else if (STB_TEXT_HAS_SELECTION(state))977stb_textedit_move_to_first(state);978979// compute current position of cursor point980stb_textedit_clamp(str, state);981stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);982983for (j = 0; j < row_count; ++j) {984float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;985986// can only go up if there's a previous row987if (find.prev_first == find.first_char)988break;989990// now find character position up a row991state->cursor = find.prev_first;992STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);993x = row.x0;994for (i=0; i < row.num_chars; ) {995float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);996int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);997#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE998if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)999break;1000#endif1001x += dx;1002if (x > goal_x)1003break;1004i += next - state->cursor;1005state->cursor = next;1006}1007stb_textedit_clamp(str, state);10081009state->has_preferred_x = 1;1010state->preferred_x = goal_x;10111012if (sel)1013state->select_end = state->cursor;10141015// go to previous line1016// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)1017prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;1018while (prev_scan > 0)1019{1020int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);1021if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)1022break;1023prev_scan = prev;1024}1025find.first_char = find.prev_first;1026find.prev_first = prev_scan;1027}1028break;1029}10301031case STB_TEXTEDIT_K_DELETE:1032case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:1033if (STB_TEXT_HAS_SELECTION(state))1034stb_textedit_delete_selection(str, state);1035else {1036int n = STB_TEXTEDIT_STRINGLEN(str);1037if (state->cursor < n)1038stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);1039}1040state->has_preferred_x = 0;1041break;10421043case STB_TEXTEDIT_K_BACKSPACE:1044case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:1045if (STB_TEXT_HAS_SELECTION(state))1046stb_textedit_delete_selection(str, state);1047else {1048stb_textedit_clamp(str, state);1049if (state->cursor > 0) {1050int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);1051stb_textedit_delete(str, state, prev, state->cursor - prev);1052state->cursor = prev;1053}1054}1055state->has_preferred_x = 0;1056break;10571058#ifdef STB_TEXTEDIT_K_TEXTSTART21059case STB_TEXTEDIT_K_TEXTSTART2:1060#endif1061case STB_TEXTEDIT_K_TEXTSTART:1062state->cursor = state->select_start = state->select_end = 0;1063state->has_preferred_x = 0;1064break;10651066#ifdef STB_TEXTEDIT_K_TEXTEND21067case STB_TEXTEDIT_K_TEXTEND2:1068#endif1069case STB_TEXTEDIT_K_TEXTEND:1070state->cursor = STB_TEXTEDIT_STRINGLEN(str);1071state->select_start = state->select_end = 0;1072state->has_preferred_x = 0;1073break;10741075#ifdef STB_TEXTEDIT_K_TEXTSTART21076case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:1077#endif1078case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:1079stb_textedit_prep_selection_at_cursor(state);1080state->cursor = state->select_end = 0;1081state->has_preferred_x = 0;1082break;10831084#ifdef STB_TEXTEDIT_K_TEXTEND21085case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:1086#endif1087case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:1088stb_textedit_prep_selection_at_cursor(state);1089state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);1090state->has_preferred_x = 0;1091break;109210931094#ifdef STB_TEXTEDIT_K_LINESTART21095case STB_TEXTEDIT_K_LINESTART2:1096#endif1097case STB_TEXTEDIT_K_LINESTART:1098stb_textedit_clamp(str, state);1099stb_textedit_move_to_first(state);1100if (state->single_line)1101state->cursor = 0;1102else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)1103state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);1104state->has_preferred_x = 0;1105break;11061107#ifdef STB_TEXTEDIT_K_LINEEND21108case STB_TEXTEDIT_K_LINEEND2:1109#endif1110case STB_TEXTEDIT_K_LINEEND: {1111int n = STB_TEXTEDIT_STRINGLEN(str);1112stb_textedit_clamp(str, state);1113stb_textedit_move_to_first(state);1114if (state->single_line)1115state->cursor = n;1116else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)1117state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);1118state->has_preferred_x = 0;1119break;1120}11211122#ifdef STB_TEXTEDIT_K_LINESTART21123case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:1124#endif1125case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:1126stb_textedit_clamp(str, state);1127stb_textedit_prep_selection_at_cursor(state);1128if (state->single_line)1129state->cursor = 0;1130else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)1131state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);1132state->select_end = state->cursor;1133state->has_preferred_x = 0;1134break;11351136#ifdef STB_TEXTEDIT_K_LINEEND21137case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:1138#endif1139case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {1140int n = STB_TEXTEDIT_STRINGLEN(str);1141stb_textedit_clamp(str, state);1142stb_textedit_prep_selection_at_cursor(state);1143if (state->single_line)1144state->cursor = n;1145else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)1146state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);1147state->select_end = state->cursor;1148state->has_preferred_x = 0;1149break;1150}1151}1152}11531154/////////////////////////////////////////////////////////////////////////////1155//1156// Undo processing1157//1158// @OPTIMIZE: the undo/redo buffer should be circular11591160static void stb_textedit_flush_redo(StbUndoState *state)1161{1162state->redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;1163state->redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;1164}11651166// discard the oldest entry in the undo list1167static void stb_textedit_discard_undo(StbUndoState *state)1168{1169if (state->undo_point > 0) {1170// if the 0th undo state has characters, clean those up1171if (state->undo_rec[0].char_storage >= 0) {1172int n = state->undo_rec[0].insert_length, i;1173// delete n characters from all other records1174state->undo_char_point -= n;1175IMSTB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(IMSTB_TEXTEDIT_CHARTYPE)));1176for (i=0; i < state->undo_point; ++i)1177if (state->undo_rec[i].char_storage >= 0)1178state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it1179}1180--state->undo_point;1181IMSTB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));1182}1183}11841185// discard the oldest entry in the redo list--it's bad if this1186// ever happens, but because undo & redo have to store the actual1187// characters in different cases, the redo character buffer can1188// fill up even though the undo buffer didn't1189static void stb_textedit_discard_redo(StbUndoState *state)1190{1191int k = IMSTB_TEXTEDIT_UNDOSTATECOUNT-1;11921193if (state->redo_point <= k) {1194// if the k'th undo state has characters, clean those up1195if (state->undo_rec[k].char_storage >= 0) {1196int n = state->undo_rec[k].insert_length, i;1197// move the remaining redo character data to the end of the buffer1198state->redo_char_point += n;1199IMSTB_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)));1200// adjust the position of all the other records to account for above memmove1201for (i=state->redo_point; i < k; ++i)1202if (state->undo_rec[i].char_storage >= 0)1203state->undo_rec[i].char_storage += n;1204}1205// now move all the redo records towards the end of the buffer; the first one is at 'redo_point'1206// [DEAR IMGUI]1207size_t move_size = (size_t)((IMSTB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));1208const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;1209const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;1210IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);1211IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);1212IMSTB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);12131214// now move redo_point to point to the new one1215++state->redo_point;1216}1217}12181219static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)1220{1221// any time we create a new undo record, we discard redo1222stb_textedit_flush_redo(state);12231224// if we have no free records, we have to make room, by sliding the1225// existing records down1226if (state->undo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1227stb_textedit_discard_undo(state);12281229// if the characters to store won't possibly fit in the buffer, we can't undo1230if (numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT) {1231state->undo_point = 0;1232state->undo_char_point = 0;1233return NULL;1234}12351236// if we don't have enough free characters in the buffer, we have to make room1237while (state->undo_char_point + numchars > IMSTB_TEXTEDIT_UNDOCHARCOUNT)1238stb_textedit_discard_undo(state);12391240return &state->undo_rec[state->undo_point++];1241}12421243static IMSTB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)1244{1245StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);1246if (r == NULL)1247return NULL;12481249r->where = pos;1250r->insert_length = (IMSTB_TEXTEDIT_POSITIONTYPE) insert_len;1251r->delete_length = (IMSTB_TEXTEDIT_POSITIONTYPE) delete_len;12521253if (insert_len == 0) {1254r->char_storage = -1;1255return NULL;1256} else {1257r->char_storage = state->undo_char_point;1258state->undo_char_point += insert_len;1259return &state->undo_char[r->char_storage];1260}1261}12621263static void stb_text_undo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)1264{1265StbUndoState *s = &state->undostate;1266StbUndoRecord u, *r;1267if (s->undo_point == 0)1268return;12691270// we need to do two things: apply the undo record, and create a redo record1271u = s->undo_rec[s->undo_point-1];1272r = &s->undo_rec[s->redo_point-1];1273r->char_storage = -1;12741275r->insert_length = u.delete_length;1276r->delete_length = u.insert_length;1277r->where = u.where;12781279if (u.delete_length) {1280// if the undo record says to delete characters, then the redo record will1281// need to re-insert the characters that get deleted, so we need to store1282// them.12831284// there are three cases:1285// there's enough room to store the characters1286// characters stored for *redoing* don't leave room for redo1287// characters stored for *undoing* don't leave room for redo1288// if the last is true, we have to bail12891290if (s->undo_char_point + u.delete_length >= IMSTB_TEXTEDIT_UNDOCHARCOUNT) {1291// the undo records take up too much character space; there's no space to store the redo characters1292r->insert_length = 0;1293} else {1294int i;12951296// there's definitely room to store the characters eventually1297while (s->undo_char_point + u.delete_length > s->redo_char_point) {1298// should never happen:1299if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1300return;1301// there's currently not enough room, so discard a redo record1302stb_textedit_discard_redo(s);1303}1304r = &s->undo_rec[s->redo_point-1];13051306r->char_storage = s->redo_char_point - u.delete_length;1307s->redo_char_point = s->redo_char_point - u.delete_length;13081309// now save the characters1310for (i=0; i < u.delete_length; ++i)1311s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);1312}13131314// now we can carry out the deletion1315STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);1316}13171318// check type of recorded action:1319if (u.insert_length) {1320// easy case: was a deletion, so we need to insert n characters1321STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);1322s->undo_char_point -= u.insert_length;1323}13241325state->cursor = u.where + u.insert_length;13261327s->undo_point--;1328s->redo_point--;1329}13301331static void stb_text_redo(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state)1332{1333StbUndoState *s = &state->undostate;1334StbUndoRecord *u, r;1335if (s->redo_point == IMSTB_TEXTEDIT_UNDOSTATECOUNT)1336return;13371338// we need to do two things: apply the redo record, and create an undo record1339u = &s->undo_rec[s->undo_point];1340r = s->undo_rec[s->redo_point];13411342// we KNOW there must be room for the undo record, because the redo record1343// was derived from an undo record13441345u->delete_length = r.insert_length;1346u->insert_length = r.delete_length;1347u->where = r.where;1348u->char_storage = -1;13491350if (r.delete_length) {1351// the redo record requires us to delete characters, so the undo record1352// needs to store the characters13531354if (s->undo_char_point + u->insert_length > s->redo_char_point) {1355u->insert_length = 0;1356u->delete_length = 0;1357} else {1358int i;1359u->char_storage = s->undo_char_point;1360s->undo_char_point = s->undo_char_point + u->insert_length;13611362// now save the characters1363for (i=0; i < u->insert_length; ++i)1364s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);1365}13661367STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);1368}13691370if (r.insert_length) {1371// easy case: need to insert n characters1372STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);1373s->redo_char_point += r.insert_length;1374}13751376state->cursor = r.where + r.insert_length;13771378s->undo_point++;1379s->redo_point++;1380}13811382static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)1383{1384stb_text_createundo(&state->undostate, where, 0, length);1385}13861387static void stb_text_makeundo_delete(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)1388{1389int i;1390IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);1391if (p) {1392for (i=0; i < length; ++i)1393p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);1394}1395}13961397static void stb_text_makeundo_replace(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)1398{1399int i;1400IMSTB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);1401if (p) {1402for (i=0; i < old_length; ++i)1403p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);1404}1405}14061407// reset the state to default1408static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)1409{1410state->undostate.undo_point = 0;1411state->undostate.undo_char_point = 0;1412state->undostate.redo_point = IMSTB_TEXTEDIT_UNDOSTATECOUNT;1413state->undostate.redo_char_point = IMSTB_TEXTEDIT_UNDOCHARCOUNT;1414state->select_end = state->select_start = 0;1415state->cursor = 0;1416state->has_preferred_x = 0;1417state->preferred_x = 0;1418state->cursor_at_end_of_line = 0;1419state->initialized = 1;1420state->single_line = (unsigned char) is_single_line;1421state->insert_mode = 0;1422state->row_count_per_page = 0;1423}14241425// API initialize1426static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)1427{1428stb_textedit_clear_state(state, is_single_line);1429}14301431#if defined(__GNUC__) || defined(__clang__)1432#pragma GCC diagnostic push1433#pragma GCC diagnostic ignored "-Wcast-qual"1434#endif14351436static int stb_textedit_paste(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, IMSTB_TEXTEDIT_CHARTYPE const *ctext, int len)1437{1438return stb_textedit_paste_internal(str, state, (IMSTB_TEXTEDIT_CHARTYPE *) ctext, len);1439}14401441#if defined(__GNUC__) || defined(__clang__)1442#pragma GCC diagnostic pop1443#endif14441445#endif//IMSTB_TEXTEDIT_IMPLEMENTATION14461447/*1448------------------------------------------------------------------------------1449This software is available under 2 licenses -- choose whichever you prefer.1450------------------------------------------------------------------------------1451ALTERNATIVE A - MIT License1452Copyright (c) 2017 Sean Barrett1453Permission is hereby granted, free of charge, to any person obtaining a copy of1454this software and associated documentation files (the "Software"), to deal in1455the Software without restriction, including without limitation the rights to1456use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies1457of the Software, and to permit persons to whom the Software is furnished to do1458so, subject to the following conditions:1459The above copyright notice and this permission notice shall be included in all1460copies or substantial portions of the Software.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 OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER1465LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,1466OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE1467SOFTWARE.1468------------------------------------------------------------------------------1469ALTERNATIVE B - Public Domain (www.unlicense.org)1470This is free and unencumbered software released into the public domain.1471Anyone is free to copy, modify, publish, use, compile, sell, or distribute this1472software, either in source code form or as a compiled binary, for any purpose,1473commercial or non-commercial, and by any means.1474In jurisdictions that recognize copyright laws, the author or authors of this1475software dedicate any and all copyright interest in the software to the public1476domain. We make this dedication for the benefit of the public at large and to1477the detriment of our heirs and successors. We intend this dedication to be an1478overt act of relinquishment in perpetuity of all present and future rights to1479this software under copyright law.1480THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR1481IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,1482FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE1483AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN1484ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION1485WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.1486------------------------------------------------------------------------------1487*/148814891490