19f464c52Smaya// [DEAR IMGUI] 29f464c52Smaya// This is a slightly modified version of stb_textedit.h 1.13. 39f464c52Smaya// Those changes would need to be pushed into nothings/stb: 49f464c52Smaya// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) 59f464c52Smaya// Grep for [DEAR IMGUI] to find the changes. 69f464c52Smaya 79f464c52Smaya// stb_textedit.h - v1.13 - public domain - Sean Barrett 89f464c52Smaya// Development of this library was sponsored by RAD Game Tools 99f464c52Smaya// 109f464c52Smaya// This C header file implements the guts of a multi-line text-editing 119f464c52Smaya// widget; you implement display, word-wrapping, and low-level string 129f464c52Smaya// insertion/deletion, and stb_textedit will map user inputs into 139f464c52Smaya// insertions & deletions, plus updates to the cursor position, 149f464c52Smaya// selection state, and undo state. 159f464c52Smaya// 169f464c52Smaya// It is intended for use in games and other systems that need to build 179f464c52Smaya// their own custom widgets and which do not have heavy text-editing 189f464c52Smaya// requirements (this library is not recommended for use for editing large 199f464c52Smaya// texts, as its performance does not scale and it has limited undo). 209f464c52Smaya// 219f464c52Smaya// Non-trivial behaviors are modelled after Windows text controls. 229f464c52Smaya// 239f464c52Smaya// 249f464c52Smaya// LICENSE 259f464c52Smaya// 269f464c52Smaya// See end of file for license information. 279f464c52Smaya// 289f464c52Smaya// 299f464c52Smaya// DEPENDENCIES 309f464c52Smaya// 319f464c52Smaya// Uses the C runtime function 'memmove', which you can override 329f464c52Smaya// by defining STB_TEXTEDIT_memmove before the implementation. 339f464c52Smaya// Uses no other functions. Performs no runtime allocations. 349f464c52Smaya// 359f464c52Smaya// 369f464c52Smaya// VERSION HISTORY 379f464c52Smaya// 389f464c52Smaya// 1.13 (2019-02-07) fix bug in undo size management 399f464c52Smaya// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash 409f464c52Smaya// 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield 419f464c52Smaya// 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual 429f464c52Smaya// 1.9 (2016-08-27) customizable move-by-word 439f464c52Smaya// 1.8 (2016-04-02) better keyboard handling when mouse button is down 449f464c52Smaya// 1.7 (2015-09-13) change y range handling in case baseline is non-0 459f464c52Smaya// 1.6 (2015-04-15) allow STB_TEXTEDIT_memmove 469f464c52Smaya// 1.5 (2014-09-10) add support for secondary keys for OS X 479f464c52Smaya// 1.4 (2014-08-17) fix signed/unsigned warnings 489f464c52Smaya// 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary 499f464c52Smaya// 1.2 (2014-05-27) fix some RAD types that had crept into the new code 509f464c52Smaya// 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE ) 519f464c52Smaya// 1.0 (2012-07-26) improve documentation, initial public release 529f464c52Smaya// 0.3 (2012-02-24) bugfixes, single-line mode; insert mode 539f464c52Smaya// 0.2 (2011-11-28) fixes to undo/redo 549f464c52Smaya// 0.1 (2010-07-08) initial version 559f464c52Smaya// 569f464c52Smaya// ADDITIONAL CONTRIBUTORS 579f464c52Smaya// 589f464c52Smaya// Ulf Winklemann: move-by-word in 1.1 599f464c52Smaya// Fabian Giesen: secondary key inputs in 1.5 609f464c52Smaya// Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6 619f464c52Smaya// 629f464c52Smaya// Bugfixes: 639f464c52Smaya// Scott Graham 649f464c52Smaya// Daniel Keller 659f464c52Smaya// Omar Cornut 669f464c52Smaya// Dan Thompson 679f464c52Smaya// 689f464c52Smaya// USAGE 699f464c52Smaya// 709f464c52Smaya// This file behaves differently depending on what symbols you define 719f464c52Smaya// before including it. 729f464c52Smaya// 739f464c52Smaya// 749f464c52Smaya// Header-file mode: 759f464c52Smaya// 769f464c52Smaya// If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this, 779f464c52Smaya// it will operate in "header file" mode. In this mode, it declares a 789f464c52Smaya// single public symbol, STB_TexteditState, which encapsulates the current 799f464c52Smaya// state of a text widget (except for the string, which you will store 809f464c52Smaya// separately). 819f464c52Smaya// 829f464c52Smaya// To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a 839f464c52Smaya// primitive type that defines a single character (e.g. char, wchar_t, etc). 849f464c52Smaya// 859f464c52Smaya// To save space or increase undo-ability, you can optionally define the 869f464c52Smaya// following things that are used by the undo system: 879f464c52Smaya// 889f464c52Smaya// STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position 899f464c52Smaya// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 909f464c52Smaya// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 919f464c52Smaya// 929f464c52Smaya// If you don't define these, they are set to permissive types and 939f464c52Smaya// moderate sizes. The undo system does no memory allocations, so 949f464c52Smaya// it grows STB_TexteditState by the worst-case storage which is (in bytes): 959f464c52Smaya// 969f464c52Smaya// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT 979f464c52Smaya// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT 989f464c52Smaya// 999f464c52Smaya// 1009f464c52Smaya// Implementation mode: 1019f464c52Smaya// 1029f464c52Smaya// If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it 1039f464c52Smaya// will compile the implementation of the text edit widget, depending 1049f464c52Smaya// on a large number of symbols which must be defined before the include. 1059f464c52Smaya// 1069f464c52Smaya// The implementation is defined only as static functions. You will then 1079f464c52Smaya// need to provide your own APIs in the same file which will access the 1089f464c52Smaya// static functions. 1099f464c52Smaya// 1109f464c52Smaya// The basic concept is that you provide a "string" object which 1119f464c52Smaya// behaves like an array of characters. stb_textedit uses indices to 1129f464c52Smaya// refer to positions in the string, implicitly representing positions 1139f464c52Smaya// in the displayed textedit. This is true for both plain text and 1149f464c52Smaya// rich text; even with rich text stb_truetype interacts with your 1159f464c52Smaya// code as if there was an array of all the displayed characters. 1169f464c52Smaya// 1179f464c52Smaya// Symbols that must be the same in header-file and implementation mode: 1189f464c52Smaya// 1199f464c52Smaya// STB_TEXTEDIT_CHARTYPE the character type 1209f464c52Smaya// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position 1219f464c52Smaya// STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow 1229f464c52Smaya// STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer 1239f464c52Smaya// 1249f464c52Smaya// Symbols you must define for implementation mode: 1259f464c52Smaya// 1269f464c52Smaya// STB_TEXTEDIT_STRING the type of object representing a string being edited, 1279f464c52Smaya// typically this is a wrapper object with other data you need 1289f464c52Smaya// 1299f464c52Smaya// STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1)) 1309f464c52Smaya// STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters 1319f464c52Smaya// starting from character #n (see discussion below) 1329f464c52Smaya// STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character 1339f464c52Smaya// to the xpos of the i+1'th char for a line of characters 1349f464c52Smaya// starting at character #n (i.e. accounts for kerning 1359f464c52Smaya// with previous char) 1369f464c52Smaya// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character 1379f464c52Smaya// (return type is int, -1 means not valid to insert) 1389f464c52Smaya// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based 1399f464c52Smaya// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize 1409f464c52Smaya// as manually wordwrapping for end-of-line positioning 1419f464c52Smaya// 1429f464c52Smaya// STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i 1439f464c52Smaya// STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*) 1449f464c52Smaya// 1459f464c52Smaya// STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key 1469f464c52Smaya// 1479f464c52Smaya// STB_TEXTEDIT_K_LEFT keyboard input to move cursor left 1489f464c52Smaya// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right 1499f464c52Smaya// STB_TEXTEDIT_K_UP keyboard input to move cursor up 1509f464c52Smaya// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down 1519f464c52Smaya// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME 1529f464c52Smaya// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END 1539f464c52Smaya// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME 1549f464c52Smaya// STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END 1559f464c52Smaya// STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor 1569f464c52Smaya// STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor 1579f464c52Smaya// STB_TEXTEDIT_K_UNDO keyboard input to perform undo 1589f464c52Smaya// STB_TEXTEDIT_K_REDO keyboard input to perform redo 1599f464c52Smaya// 1609f464c52Smaya// Optional: 1619f464c52Smaya// STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode 1629f464c52Smaya// STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'), 1639f464c52Smaya// required for default WORDLEFT/WORDRIGHT handlers 1649f464c52Smaya// STB_TEXTEDIT_MOVEWORDLEFT(obj,i) custom handler for WORDLEFT, returns index to move cursor to 1659f464c52Smaya// STB_TEXTEDIT_MOVEWORDRIGHT(obj,i) custom handler for WORDRIGHT, returns index to move cursor to 1669f464c52Smaya// STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT 1679f464c52Smaya// STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT 1689f464c52Smaya// STB_TEXTEDIT_K_LINESTART2 secondary keyboard input to move cursor to start of line 1699f464c52Smaya// STB_TEXTEDIT_K_LINEEND2 secondary keyboard input to move cursor to end of line 1709f464c52Smaya// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text 1719f464c52Smaya// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text 1729f464c52Smaya// 1739f464c52Smaya// Todo: 1749f464c52Smaya// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page 1759f464c52Smaya// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page 1769f464c52Smaya// 1779f464c52Smaya// Keyboard input must be encoded as a single integer value; e.g. a character code 1789f464c52Smaya// and some bitflags that represent shift states. to simplify the interface, SHIFT must 1799f464c52Smaya// be a bitflag, so we can test the shifted state of cursor movements to allow selection, 1809f464c52Smaya// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. 1819f464c52Smaya// 1829f464c52Smaya// You can encode other things, such as CONTROL or ALT, in additional bits, and 1839f464c52Smaya// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, 1849f464c52Smaya// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN 1859f464c52Smaya// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit, 1869f464c52Smaya// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the 1879f464c52Smaya// API below. The control keys will only match WM_KEYDOWN events because of the 1889f464c52Smaya// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN 1899f464c52Smaya// bit so it only decodes WM_CHAR events. 1909f464c52Smaya// 1919f464c52Smaya// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed 1929f464c52Smaya// row of characters assuming they start on the i'th character--the width and 1939f464c52Smaya// the height and the number of characters consumed. This allows this library 1949f464c52Smaya// to traverse the entire layout incrementally. You need to compute word-wrapping 1959f464c52Smaya// here. 1969f464c52Smaya// 1979f464c52Smaya// Each textfield keeps its own insert mode state, which is not how normal 1989f464c52Smaya// applications work. To keep an app-wide insert mode, update/copy the 1999f464c52Smaya// "insert_mode" field of STB_TexteditState before/after calling API functions. 2009f464c52Smaya// 2019f464c52Smaya// API 2029f464c52Smaya// 2039f464c52Smaya// void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 2049f464c52Smaya// 2059f464c52Smaya// void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 2069f464c52Smaya// void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 2079f464c52Smaya// int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 2089f464c52Smaya// int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 2099f464c52Smaya// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) 2109f464c52Smaya// 2119f464c52Smaya// Each of these functions potentially updates the string and updates the 2129f464c52Smaya// state. 2139f464c52Smaya// 2149f464c52Smaya// initialize_state: 2159f464c52Smaya// set the textedit state to a known good default state when initially 2169f464c52Smaya// constructing the textedit. 2179f464c52Smaya// 2189f464c52Smaya// click: 2199f464c52Smaya// call this with the mouse x,y on a mouse down; it will update the cursor 2209f464c52Smaya// and reset the selection start/end to the cursor point. the x,y must 2219f464c52Smaya// be relative to the text widget, with (0,0) being the top left. 2229f464c52Smaya// 2239f464c52Smaya// drag: 2249f464c52Smaya// call this with the mouse x,y on a mouse drag/up; it will update the 2259f464c52Smaya// cursor and the selection end point 2269f464c52Smaya// 2279f464c52Smaya// cut: 2289f464c52Smaya// call this to delete the current selection; returns true if there was 2299f464c52Smaya// one. you should FIRST copy the current selection to the system paste buffer. 2309f464c52Smaya// (To copy, just copy the current selection out of the string yourself.) 2319f464c52Smaya// 2329f464c52Smaya// paste: 2339f464c52Smaya// call this to paste text at the current cursor point or over the current 2349f464c52Smaya// selection if there is one. 2359f464c52Smaya// 2369f464c52Smaya// key: 2379f464c52Smaya// call this for keyboard inputs sent to the textfield. you can use it 2389f464c52Smaya// for "key down" events or for "translated" key events. if you need to 2399f464c52Smaya// do both (as in Win32), or distinguish Unicode characters from control 2409f464c52Smaya// inputs, set a high bit to distinguish the two; then you can define the 2419f464c52Smaya// various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit 2429f464c52Smaya// set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is 2439f464c52Smaya// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to 2449f464c52Smaya// anything other type you wante before including. 2459f464c52Smaya// 2469f464c52Smaya// 2479f464c52Smaya// When rendering, you can read the cursor position and selection state from 2489f464c52Smaya// the STB_TexteditState. 2499f464c52Smaya// 2509f464c52Smaya// 2519f464c52Smaya// Notes: 2529f464c52Smaya// 2539f464c52Smaya// This is designed to be usable in IMGUI, so it allows for the possibility of 2549f464c52Smaya// running in an IMGUI that has NOT cached the multi-line layout. For this 2559f464c52Smaya// reason, it provides an interface that is compatible with computing the 2569f464c52Smaya// layout incrementally--we try to make sure we make as few passes through 2579f464c52Smaya// as possible. (For example, to locate the mouse pointer in the text, we 2589f464c52Smaya// could define functions that return the X and Y positions of characters 2599f464c52Smaya// and binary search Y and then X, but if we're doing dynamic layout this 2609f464c52Smaya// will run the layout algorithm many times, so instead we manually search 2619f464c52Smaya// forward in one pass. Similar logic applies to e.g. up-arrow and 2629f464c52Smaya// down-arrow movement.) 2639f464c52Smaya// 2649f464c52Smaya// If it's run in a widget that *has* cached the layout, then this is less 2659f464c52Smaya// efficient, but it's not horrible on modern computers. But you wouldn't 2669f464c52Smaya// want to edit million-line files with it. 2679f464c52Smaya 2689f464c52Smaya 2699f464c52Smaya//////////////////////////////////////////////////////////////////////////// 2709f464c52Smaya//////////////////////////////////////////////////////////////////////////// 2719f464c52Smaya//// 2729f464c52Smaya//// Header-file mode 2739f464c52Smaya//// 2749f464c52Smaya//// 2759f464c52Smaya 2769f464c52Smaya#ifndef INCLUDE_STB_TEXTEDIT_H 2779f464c52Smaya#define INCLUDE_STB_TEXTEDIT_H 2789f464c52Smaya 2799f464c52Smaya//////////////////////////////////////////////////////////////////////// 2809f464c52Smaya// 2819f464c52Smaya// STB_TexteditState 2829f464c52Smaya// 2839f464c52Smaya// Definition of STB_TexteditState which you should store 2849f464c52Smaya// per-textfield; it includes cursor position, selection state, 2859f464c52Smaya// and undo state. 2869f464c52Smaya// 2879f464c52Smaya 2889f464c52Smaya#ifndef STB_TEXTEDIT_UNDOSTATECOUNT 2899f464c52Smaya#define STB_TEXTEDIT_UNDOSTATECOUNT 99 2909f464c52Smaya#endif 2919f464c52Smaya#ifndef STB_TEXTEDIT_UNDOCHARCOUNT 2929f464c52Smaya#define STB_TEXTEDIT_UNDOCHARCOUNT 999 2939f464c52Smaya#endif 2949f464c52Smaya#ifndef STB_TEXTEDIT_CHARTYPE 2959f464c52Smaya#define STB_TEXTEDIT_CHARTYPE int 2969f464c52Smaya#endif 2979f464c52Smaya#ifndef STB_TEXTEDIT_POSITIONTYPE 2989f464c52Smaya#define STB_TEXTEDIT_POSITIONTYPE int 2999f464c52Smaya#endif 3009f464c52Smaya 3019f464c52Smayatypedef struct 3029f464c52Smaya{ 3039f464c52Smaya // private data 3049f464c52Smaya STB_TEXTEDIT_POSITIONTYPE where; 3059f464c52Smaya STB_TEXTEDIT_POSITIONTYPE insert_length; 3069f464c52Smaya STB_TEXTEDIT_POSITIONTYPE delete_length; 3079f464c52Smaya int char_storage; 3089f464c52Smaya} StbUndoRecord; 3099f464c52Smaya 3109f464c52Smayatypedef struct 3119f464c52Smaya{ 3129f464c52Smaya // private data 3139f464c52Smaya StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; 3149f464c52Smaya STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; 3159f464c52Smaya short undo_point, redo_point; 3169f464c52Smaya int undo_char_point, redo_char_point; 3179f464c52Smaya} StbUndoState; 3189f464c52Smaya 3199f464c52Smayatypedef struct 3209f464c52Smaya{ 3219f464c52Smaya ///////////////////// 3229f464c52Smaya // 3239f464c52Smaya // public data 3249f464c52Smaya // 3259f464c52Smaya 3269f464c52Smaya int cursor; 3279f464c52Smaya // position of the text cursor within the string 3289f464c52Smaya 3299f464c52Smaya int select_start; // selection start point 3309f464c52Smaya int select_end; 3319f464c52Smaya // selection start and end point in characters; if equal, no selection. 3329f464c52Smaya // note that start may be less than or greater than end (e.g. when 3339f464c52Smaya // dragging the mouse, start is where the initial click was, and you 3349f464c52Smaya // can drag in either direction) 3359f464c52Smaya 3369f464c52Smaya unsigned char insert_mode; 3379f464c52Smaya // each textfield keeps its own insert mode state. to keep an app-wide 3389f464c52Smaya // insert mode, copy this value in/out of the app state 3399f464c52Smaya 3409f464c52Smaya ///////////////////// 3419f464c52Smaya // 3429f464c52Smaya // private data 3439f464c52Smaya // 3449f464c52Smaya unsigned char cursor_at_end_of_line; // not implemented yet 3459f464c52Smaya unsigned char initialized; 3469f464c52Smaya unsigned char has_preferred_x; 3479f464c52Smaya unsigned char single_line; 3489f464c52Smaya unsigned char padding1, padding2, padding3; 3499f464c52Smaya float preferred_x; // this determines where the cursor up/down tries to seek to along x 3509f464c52Smaya StbUndoState undostate; 3519f464c52Smaya} STB_TexteditState; 3529f464c52Smaya 3539f464c52Smaya 3549f464c52Smaya//////////////////////////////////////////////////////////////////////// 3559f464c52Smaya// 3569f464c52Smaya// StbTexteditRow 3579f464c52Smaya// 3589f464c52Smaya// Result of layout query, used by stb_textedit to determine where 3599f464c52Smaya// the text in each row is. 3609f464c52Smaya 3619f464c52Smaya// result of layout query 3629f464c52Smayatypedef struct 3639f464c52Smaya{ 3649f464c52Smaya float x0,x1; // starting x location, end x location (allows for align=right, etc) 3659f464c52Smaya float baseline_y_delta; // position of baseline relative to previous row's baseline 3669f464c52Smaya float ymin,ymax; // height of row above and below baseline 3679f464c52Smaya int num_chars; 3689f464c52Smaya} StbTexteditRow; 3699f464c52Smaya#endif //INCLUDE_STB_TEXTEDIT_H 3709f464c52Smaya 3719f464c52Smaya 3729f464c52Smaya//////////////////////////////////////////////////////////////////////////// 3739f464c52Smaya//////////////////////////////////////////////////////////////////////////// 3749f464c52Smaya//// 3759f464c52Smaya//// Implementation mode 3769f464c52Smaya//// 3779f464c52Smaya//// 3789f464c52Smaya 3799f464c52Smaya 3809f464c52Smaya// implementation isn't include-guarded, since it might have indirectly 3819f464c52Smaya// included just the "header" portion 3829f464c52Smaya#ifdef STB_TEXTEDIT_IMPLEMENTATION 3839f464c52Smaya 3849f464c52Smaya#ifndef STB_TEXTEDIT_memmove 3859f464c52Smaya#include <string.h> 3869f464c52Smaya#define STB_TEXTEDIT_memmove memmove 3879f464c52Smaya#endif 3889f464c52Smaya 3899f464c52Smaya 3909f464c52Smaya///////////////////////////////////////////////////////////////////////////// 3919f464c52Smaya// 3929f464c52Smaya// Mouse input handling 3939f464c52Smaya// 3949f464c52Smaya 3959f464c52Smaya// traverse the layout to locate the nearest character to a display position 3969f464c52Smayastatic int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y) 3979f464c52Smaya{ 3989f464c52Smaya StbTexteditRow r; 3999f464c52Smaya int n = STB_TEXTEDIT_STRINGLEN(str); 4009f464c52Smaya float base_y = 0, prev_x; 4019f464c52Smaya int i=0, k; 4029f464c52Smaya 4039f464c52Smaya r.x0 = r.x1 = 0; 4049f464c52Smaya r.ymin = r.ymax = 0; 4059f464c52Smaya r.num_chars = 0; 4069f464c52Smaya 4079f464c52Smaya // search rows to find one that straddles 'y' 4089f464c52Smaya while (i < n) { 4099f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, i); 4109f464c52Smaya if (r.num_chars <= 0) 4119f464c52Smaya return n; 4129f464c52Smaya 4139f464c52Smaya if (i==0 && y < base_y + r.ymin) 4149f464c52Smaya return 0; 4159f464c52Smaya 4169f464c52Smaya if (y < base_y + r.ymax) 4179f464c52Smaya break; 4189f464c52Smaya 4199f464c52Smaya i += r.num_chars; 4209f464c52Smaya base_y += r.baseline_y_delta; 4219f464c52Smaya } 4229f464c52Smaya 4239f464c52Smaya // below all text, return 'after' last character 4249f464c52Smaya if (i >= n) 4259f464c52Smaya return n; 4269f464c52Smaya 4279f464c52Smaya // check if it's before the beginning of the line 4289f464c52Smaya if (x < r.x0) 4299f464c52Smaya return i; 4309f464c52Smaya 4319f464c52Smaya // check if it's before the end of the line 4329f464c52Smaya if (x < r.x1) { 4339f464c52Smaya // search characters in row for one that straddles 'x' 4349f464c52Smaya prev_x = r.x0; 4359f464c52Smaya for (k=0; k < r.num_chars; ++k) { 4369f464c52Smaya float w = STB_TEXTEDIT_GETWIDTH(str, i, k); 4379f464c52Smaya if (x < prev_x+w) { 4389f464c52Smaya if (x < prev_x+w/2) 4399f464c52Smaya return k+i; 4409f464c52Smaya else 4419f464c52Smaya return k+i+1; 4429f464c52Smaya } 4439f464c52Smaya prev_x += w; 4449f464c52Smaya } 4459f464c52Smaya // shouldn't happen, but if it does, fall through to end-of-line case 4469f464c52Smaya } 4479f464c52Smaya 4489f464c52Smaya // if the last character is a newline, return that. otherwise return 'after' the last character 4499f464c52Smaya if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE) 4509f464c52Smaya return i+r.num_chars-1; 4519f464c52Smaya else 4529f464c52Smaya return i+r.num_chars; 4539f464c52Smaya} 4549f464c52Smaya 4559f464c52Smaya// API click: on mouse down, move the cursor to the clicked location, and reset the selection 4569f464c52Smayastatic void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 4579f464c52Smaya{ 4589f464c52Smaya // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 4599f464c52Smaya // goes off the top or bottom of the text 4609f464c52Smaya if( state->single_line ) 4619f464c52Smaya { 4629f464c52Smaya StbTexteditRow r; 4639f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 4649f464c52Smaya y = r.ymin; 4659f464c52Smaya } 4669f464c52Smaya 4679f464c52Smaya state->cursor = stb_text_locate_coord(str, x, y); 4689f464c52Smaya state->select_start = state->cursor; 4699f464c52Smaya state->select_end = state->cursor; 4709f464c52Smaya state->has_preferred_x = 0; 4719f464c52Smaya} 4729f464c52Smaya 4739f464c52Smaya// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location 4749f464c52Smayastatic void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) 4759f464c52Smaya{ 4769f464c52Smaya int p = 0; 4779f464c52Smaya 4789f464c52Smaya // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse 4799f464c52Smaya // goes off the top or bottom of the text 4809f464c52Smaya if( state->single_line ) 4819f464c52Smaya { 4829f464c52Smaya StbTexteditRow r; 4839f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 4849f464c52Smaya y = r.ymin; 4859f464c52Smaya } 4869f464c52Smaya 4879f464c52Smaya if (state->select_start == state->select_end) 4889f464c52Smaya state->select_start = state->cursor; 4899f464c52Smaya 4909f464c52Smaya p = stb_text_locate_coord(str, x, y); 4919f464c52Smaya state->cursor = state->select_end = p; 4929f464c52Smaya} 4939f464c52Smaya 4949f464c52Smaya///////////////////////////////////////////////////////////////////////////// 4959f464c52Smaya// 4969f464c52Smaya// Keyboard input handling 4979f464c52Smaya// 4989f464c52Smaya 4999f464c52Smaya// forward declarations 5009f464c52Smayastatic void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 5019f464c52Smayastatic void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state); 5029f464c52Smayastatic void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length); 5039f464c52Smayastatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length); 5049f464c52Smayastatic void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length); 5059f464c52Smaya 5069f464c52Smayatypedef struct 5079f464c52Smaya{ 5089f464c52Smaya float x,y; // position of n'th character 5099f464c52Smaya float height; // height of line 5109f464c52Smaya int first_char, length; // first char of row, and length 5119f464c52Smaya int prev_first; // first char of previous row 5129f464c52Smaya} StbFindState; 5139f464c52Smaya 5149f464c52Smaya// find the x/y location of a character, and remember info about the previous row in 5159f464c52Smaya// case we get a move-up event (for page up, we'll have to rescan) 5169f464c52Smayastatic void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line) 5179f464c52Smaya{ 5189f464c52Smaya StbTexteditRow r; 5199f464c52Smaya int prev_start = 0; 5209f464c52Smaya int z = STB_TEXTEDIT_STRINGLEN(str); 5219f464c52Smaya int i=0, first; 5229f464c52Smaya 5239f464c52Smaya if (n == z) { 5249f464c52Smaya // if it's at the end, then find the last line -- simpler than trying to 5259f464c52Smaya // explicitly handle this case in the regular code 5269f464c52Smaya if (single_line) { 5279f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, 0); 5289f464c52Smaya find->y = 0; 5299f464c52Smaya find->first_char = 0; 5309f464c52Smaya find->length = z; 5319f464c52Smaya find->height = r.ymax - r.ymin; 5329f464c52Smaya find->x = r.x1; 5339f464c52Smaya } else { 5349f464c52Smaya find->y = 0; 5359f464c52Smaya find->x = 0; 5369f464c52Smaya find->height = 1; 5379f464c52Smaya while (i < z) { 5389f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, i); 5399f464c52Smaya prev_start = i; 5409f464c52Smaya i += r.num_chars; 5419f464c52Smaya } 5429f464c52Smaya find->first_char = i; 5439f464c52Smaya find->length = 0; 5449f464c52Smaya find->prev_first = prev_start; 5459f464c52Smaya } 5469f464c52Smaya return; 5479f464c52Smaya } 5489f464c52Smaya 5499f464c52Smaya // search rows to find the one that straddles character n 5509f464c52Smaya find->y = 0; 5519f464c52Smaya 5529f464c52Smaya for(;;) { 5539f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&r, str, i); 5549f464c52Smaya if (n < i + r.num_chars) 5559f464c52Smaya break; 5569f464c52Smaya prev_start = i; 5579f464c52Smaya i += r.num_chars; 5589f464c52Smaya find->y += r.baseline_y_delta; 5599f464c52Smaya } 5609f464c52Smaya 5619f464c52Smaya find->first_char = first = i; 5629f464c52Smaya find->length = r.num_chars; 5639f464c52Smaya find->height = r.ymax - r.ymin; 5649f464c52Smaya find->prev_first = prev_start; 5659f464c52Smaya 5669f464c52Smaya // now scan to find xpos 5679f464c52Smaya find->x = r.x0; 5689f464c52Smaya for (i=0; first+i < n; ++i) 5699f464c52Smaya find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); 5709f464c52Smaya} 5719f464c52Smaya 5729f464c52Smaya#define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end) 5739f464c52Smaya 5749f464c52Smaya// make the selection/cursor state valid if client altered the string 5759f464c52Smayastatic void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 5769f464c52Smaya{ 5779f464c52Smaya int n = STB_TEXTEDIT_STRINGLEN(str); 5789f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) { 5799f464c52Smaya if (state->select_start > n) state->select_start = n; 5809f464c52Smaya if (state->select_end > n) state->select_end = n; 5819f464c52Smaya // if clamping forced them to be equal, move the cursor to match 5829f464c52Smaya if (state->select_start == state->select_end) 5839f464c52Smaya state->cursor = state->select_start; 5849f464c52Smaya } 5859f464c52Smaya if (state->cursor > n) state->cursor = n; 5869f464c52Smaya} 5879f464c52Smaya 5889f464c52Smaya// delete characters while updating undo 5899f464c52Smayastatic void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len) 5909f464c52Smaya{ 5919f464c52Smaya stb_text_makeundo_delete(str, state, where, len); 5929f464c52Smaya STB_TEXTEDIT_DELETECHARS(str, where, len); 5939f464c52Smaya state->has_preferred_x = 0; 5949f464c52Smaya} 5959f464c52Smaya 5969f464c52Smaya// delete the section 5979f464c52Smayastatic void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 5989f464c52Smaya{ 5999f464c52Smaya stb_textedit_clamp(str, state); 6009f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) { 6019f464c52Smaya if (state->select_start < state->select_end) { 6029f464c52Smaya stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start); 6039f464c52Smaya state->select_end = state->cursor = state->select_start; 6049f464c52Smaya } else { 6059f464c52Smaya stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end); 6069f464c52Smaya state->select_start = state->cursor = state->select_end; 6079f464c52Smaya } 6089f464c52Smaya state->has_preferred_x = 0; 6099f464c52Smaya } 6109f464c52Smaya} 6119f464c52Smaya 6129f464c52Smaya// canoncialize the selection so start <= end 6139f464c52Smayastatic void stb_textedit_sortselection(STB_TexteditState *state) 6149f464c52Smaya{ 6159f464c52Smaya if (state->select_end < state->select_start) { 6169f464c52Smaya int temp = state->select_end; 6179f464c52Smaya state->select_end = state->select_start; 6189f464c52Smaya state->select_start = temp; 6199f464c52Smaya } 6209f464c52Smaya} 6219f464c52Smaya 6229f464c52Smaya// move cursor to first character of selection 6239f464c52Smayastatic void stb_textedit_move_to_first(STB_TexteditState *state) 6249f464c52Smaya{ 6259f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) { 6269f464c52Smaya stb_textedit_sortselection(state); 6279f464c52Smaya state->cursor = state->select_start; 6289f464c52Smaya state->select_end = state->select_start; 6299f464c52Smaya state->has_preferred_x = 0; 6309f464c52Smaya } 6319f464c52Smaya} 6329f464c52Smaya 6339f464c52Smaya// move cursor to last character of selection 6349f464c52Smayastatic void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 6359f464c52Smaya{ 6369f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) { 6379f464c52Smaya stb_textedit_sortselection(state); 6389f464c52Smaya stb_textedit_clamp(str, state); 6399f464c52Smaya state->cursor = state->select_end; 6409f464c52Smaya state->select_start = state->select_end; 6419f464c52Smaya state->has_preferred_x = 0; 6429f464c52Smaya } 6439f464c52Smaya} 6449f464c52Smaya 6459f464c52Smaya#ifdef STB_TEXTEDIT_IS_SPACE 6469f464c52Smayastatic int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) 6479f464c52Smaya{ 6489f464c52Smaya return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; 6499f464c52Smaya} 6509f464c52Smaya 6519f464c52Smaya#ifndef STB_TEXTEDIT_MOVEWORDLEFT 6529f464c52Smayastatic int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c ) 6539f464c52Smaya{ 6549f464c52Smaya --c; // always move at least one character 6559f464c52Smaya while( c >= 0 && !is_word_boundary( str, c ) ) 6569f464c52Smaya --c; 6579f464c52Smaya 6589f464c52Smaya if( c < 0 ) 6599f464c52Smaya c = 0; 6609f464c52Smaya 6619f464c52Smaya return c; 6629f464c52Smaya} 6639f464c52Smaya#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous 6649f464c52Smaya#endif 6659f464c52Smaya 6669f464c52Smaya#ifndef STB_TEXTEDIT_MOVEWORDRIGHT 6679f464c52Smayastatic int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c ) 6689f464c52Smaya{ 6699f464c52Smaya const int len = STB_TEXTEDIT_STRINGLEN(str); 6709f464c52Smaya ++c; // always move at least one character 6719f464c52Smaya while( c < len && !is_word_boundary( str, c ) ) 6729f464c52Smaya ++c; 6739f464c52Smaya 6749f464c52Smaya if( c > len ) 6759f464c52Smaya c = len; 6769f464c52Smaya 6779f464c52Smaya return c; 6789f464c52Smaya} 6799f464c52Smaya#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next 6809f464c52Smaya#endif 6819f464c52Smaya 6829f464c52Smaya#endif 6839f464c52Smaya 6849f464c52Smaya// update selection and cursor to match each other 6859f464c52Smayastatic void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) 6869f464c52Smaya{ 6879f464c52Smaya if (!STB_TEXT_HAS_SELECTION(state)) 6889f464c52Smaya state->select_start = state->select_end = state->cursor; 6899f464c52Smaya else 6909f464c52Smaya state->cursor = state->select_end; 6919f464c52Smaya} 6929f464c52Smaya 6939f464c52Smaya// API cut: delete selection 6949f464c52Smayastatic int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 6959f464c52Smaya{ 6969f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) { 6979f464c52Smaya stb_textedit_delete_selection(str,state); // implicitly clamps 6989f464c52Smaya state->has_preferred_x = 0; 6999f464c52Smaya return 1; 7009f464c52Smaya } 7019f464c52Smaya return 0; 7029f464c52Smaya} 7039f464c52Smaya 7049f464c52Smaya// API paste: replace existing selection with passed-in text 7059f464c52Smayastatic int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) 7069f464c52Smaya{ 7079f464c52Smaya // if there's a selection, the paste should delete it 7089f464c52Smaya stb_textedit_clamp(str, state); 7099f464c52Smaya stb_textedit_delete_selection(str,state); 7109f464c52Smaya // try to insert the characters 7119f464c52Smaya if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) { 7129f464c52Smaya stb_text_makeundo_insert(state, state->cursor, len); 7139f464c52Smaya state->cursor += len; 7149f464c52Smaya state->has_preferred_x = 0; 7159f464c52Smaya return 1; 7169f464c52Smaya } 7179f464c52Smaya // remove the undo since we didn't actually insert the characters 7189f464c52Smaya if (state->undostate.undo_point) 7199f464c52Smaya --state->undostate.undo_point; 7209f464c52Smaya return 0; 7219f464c52Smaya} 7229f464c52Smaya 7239f464c52Smaya#ifndef STB_TEXTEDIT_KEYTYPE 7249f464c52Smaya#define STB_TEXTEDIT_KEYTYPE int 7259f464c52Smaya#endif 7269f464c52Smaya 7279f464c52Smaya// API key: process a keyboard input 7289f464c52Smayastatic void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) 7299f464c52Smaya{ 7309f464c52Smayaretry: 7319f464c52Smaya switch (key) { 7329f464c52Smaya default: { 7339f464c52Smaya int c = STB_TEXTEDIT_KEYTOTEXT(key); 7349f464c52Smaya if (c > 0) { 7359f464c52Smaya STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c; 7369f464c52Smaya 7379f464c52Smaya // can't add newline in single-line mode 7389f464c52Smaya if (c == '\n' && state->single_line) 7399f464c52Smaya break; 7409f464c52Smaya 7419f464c52Smaya if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 7429f464c52Smaya stb_text_makeundo_replace(str, state, state->cursor, 1, 1); 7439f464c52Smaya STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); 7449f464c52Smaya if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 7459f464c52Smaya ++state->cursor; 7469f464c52Smaya state->has_preferred_x = 0; 7479f464c52Smaya } 7489f464c52Smaya } else { 7499f464c52Smaya stb_textedit_delete_selection(str,state); // implicitly clamps 7509f464c52Smaya if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { 7519f464c52Smaya stb_text_makeundo_insert(state, state->cursor, 1); 7529f464c52Smaya ++state->cursor; 7539f464c52Smaya state->has_preferred_x = 0; 7549f464c52Smaya } 7559f464c52Smaya } 7569f464c52Smaya } 7579f464c52Smaya break; 7589f464c52Smaya } 7599f464c52Smaya 7609f464c52Smaya#ifdef STB_TEXTEDIT_K_INSERT 7619f464c52Smaya case STB_TEXTEDIT_K_INSERT: 7629f464c52Smaya state->insert_mode = !state->insert_mode; 7639f464c52Smaya break; 7649f464c52Smaya#endif 7659f464c52Smaya 7669f464c52Smaya case STB_TEXTEDIT_K_UNDO: 7679f464c52Smaya stb_text_undo(str, state); 7689f464c52Smaya state->has_preferred_x = 0; 7699f464c52Smaya break; 7709f464c52Smaya 7719f464c52Smaya case STB_TEXTEDIT_K_REDO: 7729f464c52Smaya stb_text_redo(str, state); 7739f464c52Smaya state->has_preferred_x = 0; 7749f464c52Smaya break; 7759f464c52Smaya 7769f464c52Smaya case STB_TEXTEDIT_K_LEFT: 7779f464c52Smaya // if currently there's a selection, move cursor to start of selection 7789f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 7799f464c52Smaya stb_textedit_move_to_first(state); 7809f464c52Smaya else 7819f464c52Smaya if (state->cursor > 0) 7829f464c52Smaya --state->cursor; 7839f464c52Smaya state->has_preferred_x = 0; 7849f464c52Smaya break; 7859f464c52Smaya 7869f464c52Smaya case STB_TEXTEDIT_K_RIGHT: 7879f464c52Smaya // if currently there's a selection, move cursor to end of selection 7889f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 7899f464c52Smaya stb_textedit_move_to_last(str, state); 7909f464c52Smaya else 7919f464c52Smaya ++state->cursor; 7929f464c52Smaya stb_textedit_clamp(str, state); 7939f464c52Smaya state->has_preferred_x = 0; 7949f464c52Smaya break; 7959f464c52Smaya 7969f464c52Smaya case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT: 7979f464c52Smaya stb_textedit_clamp(str, state); 7989f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 7999f464c52Smaya // move selection left 8009f464c52Smaya if (state->select_end > 0) 8019f464c52Smaya --state->select_end; 8029f464c52Smaya state->cursor = state->select_end; 8039f464c52Smaya state->has_preferred_x = 0; 8049f464c52Smaya break; 8059f464c52Smaya 8069f464c52Smaya#ifdef STB_TEXTEDIT_MOVEWORDLEFT 8079f464c52Smaya case STB_TEXTEDIT_K_WORDLEFT: 8089f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 8099f464c52Smaya stb_textedit_move_to_first(state); 8109f464c52Smaya else { 8119f464c52Smaya state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 8129f464c52Smaya stb_textedit_clamp( str, state ); 8139f464c52Smaya } 8149f464c52Smaya break; 8159f464c52Smaya 8169f464c52Smaya case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT: 8179f464c52Smaya if( !STB_TEXT_HAS_SELECTION( state ) ) 8189f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 8199f464c52Smaya 8209f464c52Smaya state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor); 8219f464c52Smaya state->select_end = state->cursor; 8229f464c52Smaya 8239f464c52Smaya stb_textedit_clamp( str, state ); 8249f464c52Smaya break; 8259f464c52Smaya#endif 8269f464c52Smaya 8279f464c52Smaya#ifdef STB_TEXTEDIT_MOVEWORDRIGHT 8289f464c52Smaya case STB_TEXTEDIT_K_WORDRIGHT: 8299f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 8309f464c52Smaya stb_textedit_move_to_last(str, state); 8319f464c52Smaya else { 8329f464c52Smaya state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 8339f464c52Smaya stb_textedit_clamp( str, state ); 8349f464c52Smaya } 8359f464c52Smaya break; 8369f464c52Smaya 8379f464c52Smaya case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT: 8389f464c52Smaya if( !STB_TEXT_HAS_SELECTION( state ) ) 8399f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 8409f464c52Smaya 8419f464c52Smaya state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); 8429f464c52Smaya state->select_end = state->cursor; 8439f464c52Smaya 8449f464c52Smaya stb_textedit_clamp( str, state ); 8459f464c52Smaya break; 8469f464c52Smaya#endif 8479f464c52Smaya 8489f464c52Smaya case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: 8499f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 8509f464c52Smaya // move selection right 8519f464c52Smaya ++state->select_end; 8529f464c52Smaya stb_textedit_clamp(str, state); 8539f464c52Smaya state->cursor = state->select_end; 8549f464c52Smaya state->has_preferred_x = 0; 8559f464c52Smaya break; 8569f464c52Smaya 8579f464c52Smaya case STB_TEXTEDIT_K_DOWN: 8589f464c52Smaya case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: { 8599f464c52Smaya StbFindState find; 8609f464c52Smaya StbTexteditRow row; 8619f464c52Smaya int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 8629f464c52Smaya 8639f464c52Smaya if (state->single_line) { 8649f464c52Smaya // on windows, up&down in single-line behave like left&right 8659f464c52Smaya key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT); 8669f464c52Smaya goto retry; 8679f464c52Smaya } 8689f464c52Smaya 8699f464c52Smaya if (sel) 8709f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 8719f464c52Smaya else if (STB_TEXT_HAS_SELECTION(state)) 8729f464c52Smaya stb_textedit_move_to_last(str,state); 8739f464c52Smaya 8749f464c52Smaya // compute current position of cursor point 8759f464c52Smaya stb_textedit_clamp(str, state); 8769f464c52Smaya stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 8779f464c52Smaya 8789f464c52Smaya // now find character position down a row 8799f464c52Smaya if (find.length) { 8809f464c52Smaya float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 8819f464c52Smaya float x; 8829f464c52Smaya int start = find.first_char + find.length; 8839f464c52Smaya state->cursor = start; 8849f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 8859f464c52Smaya x = row.x0; 8869f464c52Smaya for (i=0; i < row.num_chars; ++i) { 8879f464c52Smaya float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); 8889f464c52Smaya #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 8899f464c52Smaya if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 8909f464c52Smaya break; 8919f464c52Smaya #endif 8929f464c52Smaya x += dx; 8939f464c52Smaya if (x > goal_x) 8949f464c52Smaya break; 8959f464c52Smaya ++state->cursor; 8969f464c52Smaya } 8979f464c52Smaya stb_textedit_clamp(str, state); 8989f464c52Smaya 8999f464c52Smaya state->has_preferred_x = 1; 9009f464c52Smaya state->preferred_x = goal_x; 9019f464c52Smaya 9029f464c52Smaya if (sel) 9039f464c52Smaya state->select_end = state->cursor; 9049f464c52Smaya } 9059f464c52Smaya break; 9069f464c52Smaya } 9079f464c52Smaya 9089f464c52Smaya case STB_TEXTEDIT_K_UP: 9099f464c52Smaya case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { 9109f464c52Smaya StbFindState find; 9119f464c52Smaya StbTexteditRow row; 9129f464c52Smaya int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0; 9139f464c52Smaya 9149f464c52Smaya if (state->single_line) { 9159f464c52Smaya // on windows, up&down become left&right 9169f464c52Smaya key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT); 9179f464c52Smaya goto retry; 9189f464c52Smaya } 9199f464c52Smaya 9209f464c52Smaya if (sel) 9219f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 9229f464c52Smaya else if (STB_TEXT_HAS_SELECTION(state)) 9239f464c52Smaya stb_textedit_move_to_first(state); 9249f464c52Smaya 9259f464c52Smaya // compute current position of cursor point 9269f464c52Smaya stb_textedit_clamp(str, state); 9279f464c52Smaya stb_textedit_find_charpos(&find, str, state->cursor, state->single_line); 9289f464c52Smaya 9299f464c52Smaya // can only go up if there's a previous row 9309f464c52Smaya if (find.prev_first != find.first_char) { 9319f464c52Smaya // now find character position up a row 9329f464c52Smaya float goal_x = state->has_preferred_x ? state->preferred_x : find.x; 9339f464c52Smaya float x; 9349f464c52Smaya state->cursor = find.prev_first; 9359f464c52Smaya STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); 9369f464c52Smaya x = row.x0; 9379f464c52Smaya for (i=0; i < row.num_chars; ++i) { 9389f464c52Smaya float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); 9399f464c52Smaya #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE 9409f464c52Smaya if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE) 9419f464c52Smaya break; 9429f464c52Smaya #endif 9439f464c52Smaya x += dx; 9449f464c52Smaya if (x > goal_x) 9459f464c52Smaya break; 9469f464c52Smaya ++state->cursor; 9479f464c52Smaya } 9489f464c52Smaya stb_textedit_clamp(str, state); 9499f464c52Smaya 9509f464c52Smaya state->has_preferred_x = 1; 9519f464c52Smaya state->preferred_x = goal_x; 9529f464c52Smaya 9539f464c52Smaya if (sel) 9549f464c52Smaya state->select_end = state->cursor; 9559f464c52Smaya } 9569f464c52Smaya break; 9579f464c52Smaya } 9589f464c52Smaya 9599f464c52Smaya case STB_TEXTEDIT_K_DELETE: 9609f464c52Smaya case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT: 9619f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 9629f464c52Smaya stb_textedit_delete_selection(str, state); 9639f464c52Smaya else { 9649f464c52Smaya int n = STB_TEXTEDIT_STRINGLEN(str); 9659f464c52Smaya if (state->cursor < n) 9669f464c52Smaya stb_textedit_delete(str, state, state->cursor, 1); 9679f464c52Smaya } 9689f464c52Smaya state->has_preferred_x = 0; 9699f464c52Smaya break; 9709f464c52Smaya 9719f464c52Smaya case STB_TEXTEDIT_K_BACKSPACE: 9729f464c52Smaya case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT: 9739f464c52Smaya if (STB_TEXT_HAS_SELECTION(state)) 9749f464c52Smaya stb_textedit_delete_selection(str, state); 9759f464c52Smaya else { 9769f464c52Smaya stb_textedit_clamp(str, state); 9779f464c52Smaya if (state->cursor > 0) { 9789f464c52Smaya stb_textedit_delete(str, state, state->cursor-1, 1); 9799f464c52Smaya --state->cursor; 9809f464c52Smaya } 9819f464c52Smaya } 9829f464c52Smaya state->has_preferred_x = 0; 9839f464c52Smaya break; 9849f464c52Smaya 9859f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTSTART2 9869f464c52Smaya case STB_TEXTEDIT_K_TEXTSTART2: 9879f464c52Smaya#endif 9889f464c52Smaya case STB_TEXTEDIT_K_TEXTSTART: 9899f464c52Smaya state->cursor = state->select_start = state->select_end = 0; 9909f464c52Smaya state->has_preferred_x = 0; 9919f464c52Smaya break; 9929f464c52Smaya 9939f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTEND2 9949f464c52Smaya case STB_TEXTEDIT_K_TEXTEND2: 9959f464c52Smaya#endif 9969f464c52Smaya case STB_TEXTEDIT_K_TEXTEND: 9979f464c52Smaya state->cursor = STB_TEXTEDIT_STRINGLEN(str); 9989f464c52Smaya state->select_start = state->select_end = 0; 9999f464c52Smaya state->has_preferred_x = 0; 10009f464c52Smaya break; 10019f464c52Smaya 10029f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTSTART2 10039f464c52Smaya case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: 10049f464c52Smaya#endif 10059f464c52Smaya case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT: 10069f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 10079f464c52Smaya state->cursor = state->select_end = 0; 10089f464c52Smaya state->has_preferred_x = 0; 10099f464c52Smaya break; 10109f464c52Smaya 10119f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTEND2 10129f464c52Smaya case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT: 10139f464c52Smaya#endif 10149f464c52Smaya case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT: 10159f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 10169f464c52Smaya state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str); 10179f464c52Smaya state->has_preferred_x = 0; 10189f464c52Smaya break; 10199f464c52Smaya 10209f464c52Smaya 10219f464c52Smaya#ifdef STB_TEXTEDIT_K_LINESTART2 10229f464c52Smaya case STB_TEXTEDIT_K_LINESTART2: 10239f464c52Smaya#endif 10249f464c52Smaya case STB_TEXTEDIT_K_LINESTART: 10259f464c52Smaya stb_textedit_clamp(str, state); 10269f464c52Smaya stb_textedit_move_to_first(state); 10279f464c52Smaya if (state->single_line) 10289f464c52Smaya state->cursor = 0; 10299f464c52Smaya else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 10309f464c52Smaya --state->cursor; 10319f464c52Smaya state->has_preferred_x = 0; 10329f464c52Smaya break; 10339f464c52Smaya 10349f464c52Smaya#ifdef STB_TEXTEDIT_K_LINEEND2 10359f464c52Smaya case STB_TEXTEDIT_K_LINEEND2: 10369f464c52Smaya#endif 10379f464c52Smaya case STB_TEXTEDIT_K_LINEEND: { 10389f464c52Smaya int n = STB_TEXTEDIT_STRINGLEN(str); 10399f464c52Smaya stb_textedit_clamp(str, state); 10409f464c52Smaya stb_textedit_move_to_first(state); 10419f464c52Smaya if (state->single_line) 10429f464c52Smaya state->cursor = n; 10439f464c52Smaya else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 10449f464c52Smaya ++state->cursor; 10459f464c52Smaya state->has_preferred_x = 0; 10469f464c52Smaya break; 10479f464c52Smaya } 10489f464c52Smaya 10499f464c52Smaya#ifdef STB_TEXTEDIT_K_LINESTART2 10509f464c52Smaya case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT: 10519f464c52Smaya#endif 10529f464c52Smaya case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: 10539f464c52Smaya stb_textedit_clamp(str, state); 10549f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 10559f464c52Smaya if (state->single_line) 10569f464c52Smaya state->cursor = 0; 10579f464c52Smaya else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) 10589f464c52Smaya --state->cursor; 10599f464c52Smaya state->select_end = state->cursor; 10609f464c52Smaya state->has_preferred_x = 0; 10619f464c52Smaya break; 10629f464c52Smaya 10639f464c52Smaya#ifdef STB_TEXTEDIT_K_LINEEND2 10649f464c52Smaya case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT: 10659f464c52Smaya#endif 10669f464c52Smaya case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: { 10679f464c52Smaya int n = STB_TEXTEDIT_STRINGLEN(str); 10689f464c52Smaya stb_textedit_clamp(str, state); 10699f464c52Smaya stb_textedit_prep_selection_at_cursor(state); 10709f464c52Smaya if (state->single_line) 10719f464c52Smaya state->cursor = n; 10729f464c52Smaya else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) 10739f464c52Smaya ++state->cursor; 10749f464c52Smaya state->select_end = state->cursor; 10759f464c52Smaya state->has_preferred_x = 0; 10769f464c52Smaya break; 10779f464c52Smaya } 10789f464c52Smaya 10799f464c52Smaya// @TODO: 10809f464c52Smaya// STB_TEXTEDIT_K_PGUP - move cursor up a page 10819f464c52Smaya// STB_TEXTEDIT_K_PGDOWN - move cursor down a page 10829f464c52Smaya } 10839f464c52Smaya} 10849f464c52Smaya 10859f464c52Smaya///////////////////////////////////////////////////////////////////////////// 10869f464c52Smaya// 10879f464c52Smaya// Undo processing 10889f464c52Smaya// 10899f464c52Smaya// @OPTIMIZE: the undo/redo buffer should be circular 10909f464c52Smaya 10919f464c52Smayastatic void stb_textedit_flush_redo(StbUndoState *state) 10929f464c52Smaya{ 10939f464c52Smaya state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 10949f464c52Smaya state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 10959f464c52Smaya} 10969f464c52Smaya 10979f464c52Smaya// discard the oldest entry in the undo list 10989f464c52Smayastatic void stb_textedit_discard_undo(StbUndoState *state) 10999f464c52Smaya{ 11009f464c52Smaya if (state->undo_point > 0) { 11019f464c52Smaya // if the 0th undo state has characters, clean those up 11029f464c52Smaya if (state->undo_rec[0].char_storage >= 0) { 11039f464c52Smaya int n = state->undo_rec[0].insert_length, i; 11049f464c52Smaya // delete n characters from all other records 11059f464c52Smaya state->undo_char_point -= n; 11069f464c52Smaya STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); 11079f464c52Smaya for (i=0; i < state->undo_point; ++i) 11089f464c52Smaya if (state->undo_rec[i].char_storage >= 0) 11099f464c52Smaya state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it 11109f464c52Smaya } 11119f464c52Smaya --state->undo_point; 11129f464c52Smaya STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); 11139f464c52Smaya } 11149f464c52Smaya} 11159f464c52Smaya 11169f464c52Smaya// discard the oldest entry in the redo list--it's bad if this 11179f464c52Smaya// ever happens, but because undo & redo have to store the actual 11189f464c52Smaya// characters in different cases, the redo character buffer can 11199f464c52Smaya// fill up even though the undo buffer didn't 11209f464c52Smayastatic void stb_textedit_discard_redo(StbUndoState *state) 11219f464c52Smaya{ 11229f464c52Smaya int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; 11239f464c52Smaya 11249f464c52Smaya if (state->redo_point <= k) { 11259f464c52Smaya // if the k'th undo state has characters, clean those up 11269f464c52Smaya if (state->undo_rec[k].char_storage >= 0) { 11279f464c52Smaya int n = state->undo_rec[k].insert_length, i; 11289f464c52Smaya // move the remaining redo character data to the end of the buffer 11299f464c52Smaya state->redo_char_point += n; 11309f464c52Smaya STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); 11319f464c52Smaya // adjust the position of all the other records to account for above memmove 11329f464c52Smaya for (i=state->redo_point; i < k; ++i) 11339f464c52Smaya if (state->undo_rec[i].char_storage >= 0) 11349f464c52Smaya state->undo_rec[i].char_storage += n; 11359f464c52Smaya } 11369f464c52Smaya // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' 11379f464c52Smaya // {DEAR IMGUI] 11389f464c52Smaya size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0])); 11399f464c52Smaya const char* buf_begin = (char*)state->undo_rec; (void)buf_begin; 11409f464c52Smaya const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end; 11419f464c52Smaya IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin); 11429f464c52Smaya IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end); 11439f464c52Smaya STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size); 11449f464c52Smaya 11459f464c52Smaya // now move redo_point to point to the new one 11469f464c52Smaya ++state->redo_point; 11479f464c52Smaya } 11489f464c52Smaya} 11499f464c52Smaya 11509f464c52Smayastatic StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars) 11519f464c52Smaya{ 11529f464c52Smaya // any time we create a new undo record, we discard redo 11539f464c52Smaya stb_textedit_flush_redo(state); 11549f464c52Smaya 11559f464c52Smaya // if we have no free records, we have to make room, by sliding the 11569f464c52Smaya // existing records down 11579f464c52Smaya if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 11589f464c52Smaya stb_textedit_discard_undo(state); 11599f464c52Smaya 11609f464c52Smaya // if the characters to store won't possibly fit in the buffer, we can't undo 11619f464c52Smaya if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) { 11629f464c52Smaya state->undo_point = 0; 11639f464c52Smaya state->undo_char_point = 0; 11649f464c52Smaya return NULL; 11659f464c52Smaya } 11669f464c52Smaya 11679f464c52Smaya // if we don't have enough free characters in the buffer, we have to make room 11689f464c52Smaya while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT) 11699f464c52Smaya stb_textedit_discard_undo(state); 11709f464c52Smaya 11719f464c52Smaya return &state->undo_rec[state->undo_point++]; 11729f464c52Smaya} 11739f464c52Smaya 11749f464c52Smayastatic STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len) 11759f464c52Smaya{ 11769f464c52Smaya StbUndoRecord *r = stb_text_create_undo_record(state, insert_len); 11779f464c52Smaya if (r == NULL) 11789f464c52Smaya return NULL; 11799f464c52Smaya 11809f464c52Smaya r->where = pos; 11819f464c52Smaya r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; 11829f464c52Smaya r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; 11839f464c52Smaya 11849f464c52Smaya if (insert_len == 0) { 11859f464c52Smaya r->char_storage = -1; 11869f464c52Smaya return NULL; 11879f464c52Smaya } else { 11889f464c52Smaya r->char_storage = state->undo_char_point; 11899f464c52Smaya state->undo_char_point += insert_len; 11909f464c52Smaya return &state->undo_char[r->char_storage]; 11919f464c52Smaya } 11929f464c52Smaya} 11939f464c52Smaya 11949f464c52Smayastatic void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 11959f464c52Smaya{ 11969f464c52Smaya StbUndoState *s = &state->undostate; 11979f464c52Smaya StbUndoRecord u, *r; 11989f464c52Smaya if (s->undo_point == 0) 11999f464c52Smaya return; 12009f464c52Smaya 12019f464c52Smaya // we need to do two things: apply the undo record, and create a redo record 12029f464c52Smaya u = s->undo_rec[s->undo_point-1]; 12039f464c52Smaya r = &s->undo_rec[s->redo_point-1]; 12049f464c52Smaya r->char_storage = -1; 12059f464c52Smaya 12069f464c52Smaya r->insert_length = u.delete_length; 12079f464c52Smaya r->delete_length = u.insert_length; 12089f464c52Smaya r->where = u.where; 12099f464c52Smaya 12109f464c52Smaya if (u.delete_length) { 12119f464c52Smaya // if the undo record says to delete characters, then the redo record will 12129f464c52Smaya // need to re-insert the characters that get deleted, so we need to store 12139f464c52Smaya // them. 12149f464c52Smaya 12159f464c52Smaya // there are three cases: 12169f464c52Smaya // there's enough room to store the characters 12179f464c52Smaya // characters stored for *redoing* don't leave room for redo 12189f464c52Smaya // characters stored for *undoing* don't leave room for redo 12199f464c52Smaya // if the last is true, we have to bail 12209f464c52Smaya 12219f464c52Smaya if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) { 12229f464c52Smaya // the undo records take up too much character space; there's no space to store the redo characters 12239f464c52Smaya r->insert_length = 0; 12249f464c52Smaya } else { 12259f464c52Smaya int i; 12269f464c52Smaya 12279f464c52Smaya // there's definitely room to store the characters eventually 12289f464c52Smaya while (s->undo_char_point + u.delete_length > s->redo_char_point) { 12299f464c52Smaya // should never happen: 12309f464c52Smaya if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 12319f464c52Smaya return; 12329f464c52Smaya // there's currently not enough room, so discard a redo record 12339f464c52Smaya stb_textedit_discard_redo(s); 12349f464c52Smaya } 12359f464c52Smaya r = &s->undo_rec[s->redo_point-1]; 12369f464c52Smaya 12379f464c52Smaya r->char_storage = s->redo_char_point - u.delete_length; 12389f464c52Smaya s->redo_char_point = s->redo_char_point - u.delete_length; 12399f464c52Smaya 12409f464c52Smaya // now save the characters 12419f464c52Smaya for (i=0; i < u.delete_length; ++i) 12429f464c52Smaya s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i); 12439f464c52Smaya } 12449f464c52Smaya 12459f464c52Smaya // now we can carry out the deletion 12469f464c52Smaya STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length); 12479f464c52Smaya } 12489f464c52Smaya 12499f464c52Smaya // check type of recorded action: 12509f464c52Smaya if (u.insert_length) { 12519f464c52Smaya // easy case: was a deletion, so we need to insert n characters 12529f464c52Smaya STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length); 12539f464c52Smaya s->undo_char_point -= u.insert_length; 12549f464c52Smaya } 12559f464c52Smaya 12569f464c52Smaya state->cursor = u.where + u.insert_length; 12579f464c52Smaya 12589f464c52Smaya s->undo_point--; 12599f464c52Smaya s->redo_point--; 12609f464c52Smaya} 12619f464c52Smaya 12629f464c52Smayastatic void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) 12639f464c52Smaya{ 12649f464c52Smaya StbUndoState *s = &state->undostate; 12659f464c52Smaya StbUndoRecord *u, r; 12669f464c52Smaya if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) 12679f464c52Smaya return; 12689f464c52Smaya 12699f464c52Smaya // we need to do two things: apply the redo record, and create an undo record 12709f464c52Smaya u = &s->undo_rec[s->undo_point]; 12719f464c52Smaya r = s->undo_rec[s->redo_point]; 12729f464c52Smaya 12739f464c52Smaya // we KNOW there must be room for the undo record, because the redo record 12749f464c52Smaya // was derived from an undo record 12759f464c52Smaya 12769f464c52Smaya u->delete_length = r.insert_length; 12779f464c52Smaya u->insert_length = r.delete_length; 12789f464c52Smaya u->where = r.where; 12799f464c52Smaya u->char_storage = -1; 12809f464c52Smaya 12819f464c52Smaya if (r.delete_length) { 12829f464c52Smaya // the redo record requires us to delete characters, so the undo record 12839f464c52Smaya // needs to store the characters 12849f464c52Smaya 12859f464c52Smaya if (s->undo_char_point + u->insert_length > s->redo_char_point) { 12869f464c52Smaya u->insert_length = 0; 12879f464c52Smaya u->delete_length = 0; 12889f464c52Smaya } else { 12899f464c52Smaya int i; 12909f464c52Smaya u->char_storage = s->undo_char_point; 12919f464c52Smaya s->undo_char_point = s->undo_char_point + u->insert_length; 12929f464c52Smaya 12939f464c52Smaya // now save the characters 12949f464c52Smaya for (i=0; i < u->insert_length; ++i) 12959f464c52Smaya s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i); 12969f464c52Smaya } 12979f464c52Smaya 12989f464c52Smaya STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length); 12999f464c52Smaya } 13009f464c52Smaya 13019f464c52Smaya if (r.insert_length) { 13029f464c52Smaya // easy case: need to insert n characters 13039f464c52Smaya STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length); 13049f464c52Smaya s->redo_char_point += r.insert_length; 13059f464c52Smaya } 13069f464c52Smaya 13079f464c52Smaya state->cursor = r.where + r.insert_length; 13089f464c52Smaya 13099f464c52Smaya s->undo_point++; 13109f464c52Smaya s->redo_point++; 13119f464c52Smaya} 13129f464c52Smaya 13139f464c52Smayastatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length) 13149f464c52Smaya{ 13159f464c52Smaya stb_text_createundo(&state->undostate, where, 0, length); 13169f464c52Smaya} 13179f464c52Smaya 13189f464c52Smayastatic void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length) 13199f464c52Smaya{ 13209f464c52Smaya int i; 13219f464c52Smaya STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0); 13229f464c52Smaya if (p) { 13239f464c52Smaya for (i=0; i < length; ++i) 13249f464c52Smaya p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 13259f464c52Smaya } 13269f464c52Smaya} 13279f464c52Smaya 13289f464c52Smayastatic void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length) 13299f464c52Smaya{ 13309f464c52Smaya int i; 13319f464c52Smaya STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length); 13329f464c52Smaya if (p) { 13339f464c52Smaya for (i=0; i < old_length; ++i) 13349f464c52Smaya p[i] = STB_TEXTEDIT_GETCHAR(str, where+i); 13359f464c52Smaya } 13369f464c52Smaya} 13379f464c52Smaya 13389f464c52Smaya// reset the state to default 13399f464c52Smayastatic void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line) 13409f464c52Smaya{ 13419f464c52Smaya state->undostate.undo_point = 0; 13429f464c52Smaya state->undostate.undo_char_point = 0; 13439f464c52Smaya state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT; 13449f464c52Smaya state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT; 13459f464c52Smaya state->select_end = state->select_start = 0; 13469f464c52Smaya state->cursor = 0; 13479f464c52Smaya state->has_preferred_x = 0; 13489f464c52Smaya state->preferred_x = 0; 13499f464c52Smaya state->cursor_at_end_of_line = 0; 13509f464c52Smaya state->initialized = 1; 13519f464c52Smaya state->single_line = (unsigned char) is_single_line; 13529f464c52Smaya state->insert_mode = 0; 13539f464c52Smaya} 13549f464c52Smaya 13559f464c52Smaya// API initialize 13569f464c52Smayastatic void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line) 13579f464c52Smaya{ 13589f464c52Smaya stb_textedit_clear_state(state, is_single_line); 13599f464c52Smaya} 13609f464c52Smaya 13619f464c52Smaya#if defined(__GNUC__) || defined(__clang__) 13629f464c52Smaya#pragma GCC diagnostic push 13639f464c52Smaya#pragma GCC diagnostic ignored "-Wcast-qual" 13649f464c52Smaya#endif 13659f464c52Smaya 13669f464c52Smayastatic int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len) 13679f464c52Smaya{ 13689f464c52Smaya return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len); 13699f464c52Smaya} 13709f464c52Smaya 13719f464c52Smaya#if defined(__GNUC__) || defined(__clang__) 13729f464c52Smaya#pragma GCC diagnostic pop 13739f464c52Smaya#endif 13749f464c52Smaya 13759f464c52Smaya#endif//STB_TEXTEDIT_IMPLEMENTATION 13769f464c52Smaya 13779f464c52Smaya/* 13789f464c52Smaya------------------------------------------------------------------------------ 13799f464c52SmayaThis software is available under 2 licenses -- choose whichever you prefer. 13809f464c52Smaya------------------------------------------------------------------------------ 13819f464c52SmayaALTERNATIVE A - MIT License 13829f464c52SmayaCopyright (c) 2017 Sean Barrett 13839f464c52SmayaPermission is hereby granted, free of charge, to any person obtaining a copy of 13849f464c52Smayathis software and associated documentation files (the "Software"), to deal in 13859f464c52Smayathe Software without restriction, including without limitation the rights to 13869f464c52Smayause, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13879f464c52Smayaof the Software, and to permit persons to whom the Software is furnished to do 13889f464c52Smayaso, subject to the following conditions: 13899f464c52SmayaThe above copyright notice and this permission notice shall be included in all 13909f464c52Smayacopies or substantial portions of the Software. 13919f464c52SmayaTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13929f464c52SmayaIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13939f464c52SmayaFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13949f464c52SmayaAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 13959f464c52SmayaLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 13969f464c52SmayaOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13979f464c52SmayaSOFTWARE. 13989f464c52Smaya------------------------------------------------------------------------------ 13999f464c52SmayaALTERNATIVE B - Public Domain (www.unlicense.org) 14009f464c52SmayaThis is free and unencumbered software released into the public domain. 14019f464c52SmayaAnyone is free to copy, modify, publish, use, compile, sell, or distribute this 14029f464c52Smayasoftware, either in source code form or as a compiled binary, for any purpose, 14039f464c52Smayacommercial or non-commercial, and by any means. 14049f464c52SmayaIn jurisdictions that recognize copyright laws, the author or authors of this 14059f464c52Smayasoftware dedicate any and all copyright interest in the software to the public 14069f464c52Smayadomain. We make this dedication for the benefit of the public at large and to 14079f464c52Smayathe detriment of our heirs and successors. We intend this dedication to be an 14089f464c52Smayaovert act of relinquishment in perpetuity of all present and future rights to 14099f464c52Smayathis software under copyright law. 14109f464c52SmayaTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14119f464c52SmayaIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14129f464c52SmayaFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14139f464c52SmayaAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 14149f464c52SmayaACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 14159f464c52SmayaWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14169f464c52Smaya------------------------------------------------------------------------------ 14179f464c52Smaya*/ 1418