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