Open Source Tomb Raider Engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stb_textedit.h 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254
  1. // stb_textedit.h - v1.4 - public domain - Sean Barrett
  2. // Development of this library was sponsored by RAD Game Tools
  3. //
  4. // This C header file implements the guts of a multi-line text-editing
  5. // widget; you implement display, word-wrapping, and low-level string
  6. // insertion/deletion, and stb_textedit will map user inputs into
  7. // insertions & deletions, plus updates to the cursor position,
  8. // selection state, and undo state.
  9. //
  10. // It is intended for use in games and other systems that need to build
  11. // their own custom widgets and which do not have heavy text-editing
  12. // requirements (this library is not recommended for use for editing large
  13. // texts, as its performance does not scale and it has limited undo).
  14. //
  15. // Non-trivial behaviors are modelled after Windows text controls.
  16. //
  17. //
  18. // LICENSE
  19. //
  20. // This software has been placed in the public domain by its author.
  21. // Where that dedication is not recognized, you are granted a perpetual,
  22. // irrevocable license to copy and modify this file as you see fit.
  23. //
  24. //
  25. // DEPENDENCIES
  26. //
  27. // Uses the C runtime function 'memmove'. Uses no other functions.
  28. // Performs no runtime allocations.
  29. //
  30. //
  31. // VERSION HISTORY
  32. //
  33. // 1.4 (2014-08-17) fix signed/unsigned warnings
  34. // 1.3 (2014-06-19) fix mouse clicking to round to nearest char boundary
  35. // 1.2 (2014-05-27) fix some RAD types that had crept into the new code
  36. // 1.1 (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
  37. // 1.0 (2012-07-26) improve documentation, initial public release
  38. // 0.3 (2012-02-24) bugfixes, single-line mode; insert mode
  39. // 0.2 (2011-11-28) fixes to undo/redo
  40. // 0.1 (2010-07-08) initial version
  41. //
  42. // ADDITIONAL CONTRIBUTORS
  43. //
  44. // Ulf Winklemann: move-by-word in 1.1
  45. // Scott Graham: mouse selection bugfix in 1.3
  46. //
  47. // USAGE
  48. //
  49. // This file behaves differently depending on what symbols you define
  50. // before including it.
  51. //
  52. //
  53. // Header-file mode:
  54. //
  55. // If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
  56. // it will operate in "header file" mode. In this mode, it declares a
  57. // single public symbol, STB_TexteditState, which encapsulates the current
  58. // state of a text widget (except for the string, which you will store
  59. // separately).
  60. //
  61. // To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
  62. // primitive type that defines a single character (e.g. char, wchar_t, etc).
  63. //
  64. // To save space or increase undo-ability, you can optionally define the
  65. // following things that are used by the undo system:
  66. //
  67. // STB_TEXTEDIT_POSITIONTYPE small int type encoding a valid cursor position
  68. // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow
  69. // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer
  70. //
  71. // If you don't define these, they are set to permissive types and
  72. // moderate sizes. The undo system does no memory allocations, so
  73. // it grows STB_TexteditState by the worst-case storage which is (in bytes):
  74. //
  75. // [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT
  76. // + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT
  77. //
  78. //
  79. // Implementation mode:
  80. //
  81. // If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
  82. // will compile the implementation of the text edit widget, depending
  83. // on a large number of symbols which must be defined before the include.
  84. //
  85. // The implementation is defined only as static functions. You will then
  86. // need to provide your own APIs in the same file which will access the
  87. // static functions.
  88. //
  89. // The basic concept is that you provide a "string" object which
  90. // behaves like an array of characters. stb_textedit uses indices to
  91. // refer to positions in the string, implicitly representing positions
  92. // in the displayed textedit. This is true for both plain text and
  93. // rich text; even with rich text stb_truetype interacts with your
  94. // code as if there was an array of all the displayed characters.
  95. //
  96. // Symbols that must be the same in header-file and implementation mode:
  97. //
  98. // STB_TEXTEDIT_CHARTYPE the character type
  99. // STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position
  100. // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow
  101. // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer
  102. //
  103. // Symbols you must define for implementation mode:
  104. //
  105. // STB_TEXTEDIT_STRING the type of object representing a string being edited,
  106. // typically this is a wrapper object with other data you need
  107. //
  108. // STB_TEXTEDIT_STRINGLEN(obj) the length of the string (ideally O(1))
  109. // STB_TEXTEDIT_LAYOUTROW(&r,obj,n) returns the results of laying out a line of characters
  110. // starting from character #n (see discussion below)
  111. // STB_TEXTEDIT_GETWIDTH(obj,n,i) returns the pixel delta from the xpos of the i'th character
  112. // to the xpos of the i+1'th char for a line of characters
  113. // starting at character #n (i.e. accounts for kerning
  114. // with previous char)
  115. // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
  116. // (return type is int, -1 means not valid to insert)
  117. // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
  118. // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
  119. // as manually wordwrapping for end-of-line positioning
  120. //
  121. // STB_TEXTEDIT_DELETECHARS(obj,i,n) delete n characters starting at i
  122. // STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n) insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
  123. //
  124. // STB_TEXTEDIT_K_SHIFT a power of two that is or'd in to a keyboard input to represent the shift key
  125. //
  126. // STB_TEXTEDIT_K_LEFT keyboard input to move cursor left
  127. // STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
  128. // STB_TEXTEDIT_K_UP keyboard input to move cursor up
  129. // STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
  130. // STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME
  131. // STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END
  132. // STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME
  133. // STB_TEXTEDIT_K_TEXTEND keyboard input to move cursor to end of text // e.g. ctrl-END
  134. // STB_TEXTEDIT_K_DELETE keyboard input to delete selection or character under cursor
  135. // STB_TEXTEDIT_K_BACKSPACE keyboard input to delete selection or character left of cursor
  136. // STB_TEXTEDIT_K_UNDO keyboard input to perform undo
  137. // STB_TEXTEDIT_K_REDO keyboard input to perform redo
  138. //
  139. // Optional:
  140. // STB_TEXTEDIT_K_INSERT keyboard input to toggle insert mode
  141. // STB_TEXTEDIT_IS_SPACE(ch) true if character is whitespace (e.g. 'isspace'),
  142. // required for WORDLEFT/WORDRIGHT
  143. // STB_TEXTEDIT_K_WORDLEFT keyboard input to move cursor left one word // e.g. ctrl-LEFT
  144. // STB_TEXTEDIT_K_WORDRIGHT keyboard input to move cursor right one word // e.g. ctrl-RIGHT
  145. //
  146. // Todo:
  147. // STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
  148. // STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
  149. //
  150. // Keyboard input must be encoded as a single integer value; e.g. a character code
  151. // and some bitflags that represent shift states. to simplify the interface, SHIFT must
  152. // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
  153. // i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
  154. //
  155. // You can encode other things, such as CONTROL or ALT, in additional bits, and
  156. // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
  157. // my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
  158. // bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
  159. // and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
  160. // API below. The control keys will only match WM_KEYDOWN events because of the
  161. // keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
  162. // bit so it only decodes WM_CHAR events.
  163. //
  164. // STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
  165. // row of characters assuming they start on the i'th character--the width and
  166. // the height and the number of characters consumed. This allows this library
  167. // to traverse the entire layout incrementally. You need to compute word-wrapping
  168. // here.
  169. //
  170. // Each textfield keeps its own insert mode state, which is not how normal
  171. // applications work. To keep an app-wide insert mode, update/copy the
  172. // "insert_mode" field of STB_TexteditState before/after calling API functions.
  173. //
  174. // API
  175. //
  176. // void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
  177. //
  178. // void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
  179. // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
  180. // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  181. // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
  182. // void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key)
  183. //
  184. // Each of these functions potentially updates the string and updates the
  185. // state.
  186. //
  187. // initialize_state:
  188. // set the textedit state to a known good default state when initially
  189. // constructing the textedit.
  190. //
  191. // click:
  192. // call this with the mouse x,y on a mouse down; it will update the cursor
  193. // and reset the selection start/end to the cursor point. the x,y must
  194. // be relative to the text widget, with (0,0) being the top left.
  195. //
  196. // drag:
  197. // call this with the mouse x,y on a mouse drag/up; it will update the
  198. // cursor and the selection end point
  199. //
  200. // cut:
  201. // call this to delete the current selection; returns true if there was
  202. // one. you should FIRST copy the current selection to the system paste buffer.
  203. // (To copy, just copy the current selection out of the string yourself.)
  204. //
  205. // paste:
  206. // call this to paste text at the current cursor point or over the current
  207. // selection if there is one.
  208. //
  209. // key:
  210. // call this for keyboard inputs sent to the textfield. you can use it
  211. // for "key down" events or for "translated" key events. if you need to
  212. // do both (as in Win32), or distinguish Unicode characters from control
  213. // inputs, set a high bit to distinguish the two; then you can define the
  214. // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
  215. // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
  216. // clear.
  217. //
  218. // When rendering, you can read the cursor position and selection state from
  219. // the STB_TexteditState.
  220. //
  221. //
  222. // Notes:
  223. //
  224. // This is designed to be usable in IMGUI, so it allows for the possibility of
  225. // running in an IMGUI that has NOT cached the multi-line layout. For this
  226. // reason, it provides an interface that is compatible with computing the
  227. // layout incrementally--we try to make sure we make as few passes through
  228. // as possible. (For example, to locate the mouse pointer in the text, we
  229. // could define functions that return the X and Y positions of characters
  230. // and binary search Y and then X, but if we're doing dynamic layout this
  231. // will run the layout algorithm many times, so instead we manually search
  232. // forward in one pass. Similar logic applies to e.g. up-arrow and
  233. // down-arrow movement.)
  234. //
  235. // If it's run in a widget that *has* cached the layout, then this is less
  236. // efficient, but it's not horrible on modern computers. But you wouldn't
  237. // want to edit million-line files with it.
  238. ////////////////////////////////////////////////////////////////////////////
  239. ////////////////////////////////////////////////////////////////////////////
  240. ////
  241. //// Header-file mode
  242. ////
  243. ////
  244. #ifndef INCLUDE_STB_TEXTEDIT_H
  245. #define INCLUDE_STB_TEXTEDIT_H
  246. ////////////////////////////////////////////////////////////////////////
  247. //
  248. // STB_TexteditState
  249. //
  250. // Definition of STB_TexteditState which you should store
  251. // per-textfield; it includes cursor position, selection state,
  252. // and undo state.
  253. //
  254. #ifndef STB_TEXTEDIT_UNDOSTATECOUNT
  255. #define STB_TEXTEDIT_UNDOSTATECOUNT 99
  256. #endif
  257. #ifndef STB_TEXTEDIT_UNDOCHARCOUNT
  258. #define STB_TEXTEDIT_UNDOCHARCOUNT 999
  259. #endif
  260. #ifndef STB_TEXTEDIT_CHARTYPE
  261. #define STB_TEXTEDIT_CHARTYPE int
  262. #endif
  263. #ifndef STB_TEXTEDIT_POSITIONTYPE
  264. #define STB_TEXTEDIT_POSITIONTYPE int
  265. #endif
  266. typedef struct
  267. {
  268. // private data
  269. STB_TEXTEDIT_POSITIONTYPE where;
  270. short insert_length;
  271. short delete_length;
  272. short char_storage;
  273. } StbUndoRecord;
  274. typedef struct
  275. {
  276. // private data
  277. StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
  278. STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
  279. short undo_point, redo_point;
  280. short undo_char_point, redo_char_point;
  281. } StbUndoState;
  282. typedef struct
  283. {
  284. /////////////////////
  285. //
  286. // public data
  287. //
  288. int cursor;
  289. // position of the text cursor within the string
  290. int select_start; // selection start point
  291. int select_end;
  292. // selection start and end point in characters; if equal, no selection.
  293. // note that start may be less than or greater than end (e.g. when
  294. // dragging the mouse, start is where the initial click was, and you
  295. // can drag in either direction)
  296. unsigned char insert_mode;
  297. // each textfield keeps its own insert mode state. to keep an app-wide
  298. // insert mode, copy this value in/out of the app state
  299. /////////////////////
  300. //
  301. // private data
  302. //
  303. unsigned char cursor_at_end_of_line; // not implemented yet
  304. unsigned char initialized;
  305. unsigned char has_preferred_x;
  306. unsigned char single_line;
  307. unsigned char padding1, padding2, padding3;
  308. float preferred_x; // this determines where the cursor up/down tries to seek to along x
  309. StbUndoState undostate;
  310. } STB_TexteditState;
  311. ////////////////////////////////////////////////////////////////////////
  312. //
  313. // StbTexteditRow
  314. //
  315. // Result of layout query, used by stb_textedit to determine where
  316. // the text in each row is.
  317. // result of layout query
  318. typedef struct
  319. {
  320. float x0,x1; // starting x location, end x location (allows for align=right, etc)
  321. float baseline_y_delta; // position of baseline relative to previous row's baseline
  322. float ymin,ymax; // height of row above and below baseline
  323. int num_chars;
  324. } StbTexteditRow;
  325. #endif //INCLUDE_STB_TEXTEDIT_H
  326. ////////////////////////////////////////////////////////////////////////////
  327. ////////////////////////////////////////////////////////////////////////////
  328. ////
  329. //// Implementation mode
  330. ////
  331. ////
  332. // implementation isn't include-guarded, since it might have indirectly
  333. // included just the "header" portion
  334. #ifdef STB_TEXTEDIT_IMPLEMENTATION
  335. #include <string.h> // memmove
  336. /////////////////////////////////////////////////////////////////////////////
  337. //
  338. // Mouse input handling
  339. //
  340. // traverse the layout to locate the nearest character to a display position
  341. static int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
  342. {
  343. StbTexteditRow r;
  344. int n = STB_TEXTEDIT_STRINGLEN(str);
  345. float base_y = 0, prev_x;
  346. int i=0, k;
  347. if (y < 0)
  348. return 0;
  349. r.x0 = r.x1 = 0;
  350. r.ymin = r.ymax = 0;
  351. r.num_chars = 0;
  352. // search rows to find one that straddles 'y'
  353. while (i < n) {
  354. STB_TEXTEDIT_LAYOUTROW(&r, str, i);
  355. if (r.num_chars <= 0)
  356. return n;
  357. if (y < base_y + r.ymax)
  358. break;
  359. i += r.num_chars;
  360. base_y += r.baseline_y_delta;
  361. }
  362. // below all text, return 'after' last character
  363. if (i >= n)
  364. return n;
  365. // check if it's before the beginning of the line
  366. if (x < r.x0)
  367. return i;
  368. // check if it's before the end of the line
  369. if (x < r.x1) {
  370. // search characters in row for one that straddles 'x'
  371. k = i;
  372. prev_x = r.x0;
  373. for (i=0; i < r.num_chars; ++i) {
  374. float w = STB_TEXTEDIT_GETWIDTH(str, k, i);
  375. if (x < prev_x+w) {
  376. if (x < prev_x+w/2)
  377. return k+i;
  378. else
  379. return k+i+1;
  380. }
  381. prev_x += w;
  382. }
  383. // shouldn't happen, but if it does, fall through to end-of-line case
  384. }
  385. // if the last character is a newline, return that. otherwise return 'after' the last character
  386. if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
  387. return i+r.num_chars-1;
  388. else
  389. return i+r.num_chars;
  390. }
  391. // API click: on mouse down, move the cursor to the clicked location, and reset the selection
  392. static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
  393. {
  394. state->cursor = stb_text_locate_coord(str, x, y);
  395. state->select_start = state->cursor;
  396. state->select_end = state->cursor;
  397. state->has_preferred_x = 0;
  398. }
  399. // API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
  400. static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
  401. {
  402. int p = stb_text_locate_coord(str, x, y);
  403. state->cursor = state->select_end = p;
  404. }
  405. /////////////////////////////////////////////////////////////////////////////
  406. //
  407. // Keyboard input handling
  408. //
  409. // forward declarations
  410. static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
  411. static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
  412. static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
  413. static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
  414. static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
  415. typedef struct
  416. {
  417. float x,y; // position of n'th character
  418. float height; // height of line
  419. int first_char, length; // first char of row, and length
  420. int prev_first; // first char of previous row
  421. } StbFindState;
  422. // find the x/y location of a character, and remember info about the previous row in
  423. // case we get a move-up event (for page up, we'll have to rescan)
  424. static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
  425. {
  426. StbTexteditRow r;
  427. int prev_start = 0;
  428. int z = STB_TEXTEDIT_STRINGLEN(str);
  429. int i=0, first;
  430. if (n == z) {
  431. // if it's at the end, then find the last line -- simpler than trying to
  432. // explicitly handle this case in the regular code
  433. if (single_line) {
  434. STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
  435. find->y = 0;
  436. find->first_char = 0;
  437. find->length = z;
  438. find->height = r.ymax - r.ymin;
  439. find->x = r.x1;
  440. } else {
  441. find->y = 0;
  442. find->x = 0;
  443. find->height = 1;
  444. while (i < z) {
  445. STB_TEXTEDIT_LAYOUTROW(&r, str, i);
  446. prev_start = i;
  447. i += r.num_chars;
  448. }
  449. find->first_char = i;
  450. find->length = 0;
  451. find->prev_first = prev_start;
  452. }
  453. return;
  454. }
  455. // search rows to find the one that straddles character n
  456. find->y = 0;
  457. for(;;) {
  458. STB_TEXTEDIT_LAYOUTROW(&r, str, i);
  459. if (n < i + r.num_chars)
  460. break;
  461. prev_start = i;
  462. i += r.num_chars;
  463. find->y += r.baseline_y_delta;
  464. }
  465. find->first_char = first = i;
  466. find->length = r.num_chars;
  467. find->height = r.ymax - r.ymin;
  468. find->prev_first = prev_start;
  469. // now scan to find xpos
  470. find->x = r.x0;
  471. i = 0;
  472. for (i=0; first+i < n; ++i)
  473. find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
  474. }
  475. #define STB_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)
  476. // make the selection/cursor state valid if client altered the string
  477. static void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  478. {
  479. int n = STB_TEXTEDIT_STRINGLEN(str);
  480. if (STB_TEXT_HAS_SELECTION(state)) {
  481. if (state->select_start > n) state->select_start = n;
  482. if (state->select_end > n) state->select_end = n;
  483. // if clamping forced them to be equal, move the cursor to match
  484. if (state->select_start == state->select_end)
  485. state->cursor = state->select_start;
  486. }
  487. if (state->cursor > n) state->cursor = n;
  488. }
  489. // delete characters while updating undo
  490. static void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
  491. {
  492. stb_text_makeundo_delete(str, state, where, len);
  493. STB_TEXTEDIT_DELETECHARS(str, where, len);
  494. state->has_preferred_x = 0;
  495. }
  496. // delete the section
  497. static void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  498. {
  499. stb_textedit_clamp(str, state);
  500. if (STB_TEXT_HAS_SELECTION(state)) {
  501. if (state->select_start < state->select_end) {
  502. stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
  503. state->select_end = state->cursor = state->select_start;
  504. } else {
  505. stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
  506. state->select_start = state->cursor = state->select_end;
  507. }
  508. state->has_preferred_x = 0;
  509. }
  510. }
  511. // canoncialize the selection so start <= end
  512. static void stb_textedit_sortselection(STB_TexteditState *state)
  513. {
  514. if (state->select_end < state->select_start) {
  515. int temp = state->select_end;
  516. state->select_end = state->select_start;
  517. state->select_start = temp;
  518. }
  519. }
  520. // move cursor to first character of selection
  521. static void stb_textedit_move_to_first(STB_TexteditState *state)
  522. {
  523. if (STB_TEXT_HAS_SELECTION(state)) {
  524. stb_textedit_sortselection(state);
  525. state->cursor = state->select_start;
  526. state->select_end = state->select_start;
  527. state->has_preferred_x = 0;
  528. }
  529. }
  530. // move cursor to last character of selection
  531. static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  532. {
  533. if (STB_TEXT_HAS_SELECTION(state)) {
  534. stb_textedit_sortselection(state);
  535. stb_textedit_clamp(str, state);
  536. state->cursor = state->select_end;
  537. state->select_start = state->select_end;
  538. state->has_preferred_x = 0;
  539. }
  540. }
  541. #ifdef STB_TEXTEDIT_IS_SPACE
  542. static int is_word_boundary( STB_TEXTEDIT_STRING *_str, int _idx )
  543. {
  544. return _idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(_str,_idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(_str, _idx) ) ) : 1;
  545. }
  546. static int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *_str, STB_TexteditState *_state )
  547. {
  548. int c = _state->cursor - 1;
  549. while( c >= 0 && !is_word_boundary( _str, c ) )
  550. --c;
  551. if( c < 0 )
  552. c = 0;
  553. return c;
  554. }
  555. static int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *_str, STB_TexteditState *_state )
  556. {
  557. const int len = STB_TEXTEDIT_STRINGLEN(_str);
  558. int c = _state->cursor+1;
  559. while( c < len && !is_word_boundary( _str, c ) )
  560. ++c;
  561. if( c > len )
  562. c = len;
  563. return c;
  564. }
  565. #endif
  566. // update selection and cursor to match each other
  567. static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
  568. {
  569. if (!STB_TEXT_HAS_SELECTION(state))
  570. state->select_start = state->select_end = state->cursor;
  571. else
  572. state->cursor = state->select_end;
  573. }
  574. // API cut: delete selection
  575. static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  576. {
  577. if (STB_TEXT_HAS_SELECTION(state)) {
  578. stb_textedit_delete_selection(str,state); // implicity clamps
  579. state->has_preferred_x = 0;
  580. return 1;
  581. }
  582. return 0;
  583. }
  584. // API paste: replace existing selection with passed-in text
  585. static int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len)
  586. {
  587. STB_TEXTEDIT_CHARTYPE *text = (STB_TEXTEDIT_CHARTYPE *) ctext;
  588. // if there's a selection, the paste should delete it
  589. stb_textedit_clamp(str, state);
  590. stb_textedit_delete_selection(str,state);
  591. // try to insert the characters
  592. if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
  593. stb_text_makeundo_insert(state, state->cursor, len);
  594. state->cursor += len;
  595. state->has_preferred_x = 0;
  596. return 1;
  597. }
  598. // remove the undo since we didn't actually insert the characters
  599. if (state->undostate.undo_point)
  600. --state->undostate.undo_point;
  601. return 0;
  602. }
  603. // API key: process a keyboard input
  604. static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key)
  605. {
  606. retry:
  607. switch (key) {
  608. default: {
  609. int c = STB_TEXTEDIT_KEYTOTEXT(key);
  610. if (c > 0) {
  611. STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
  612. // can't add newline in single-line mode
  613. if (c == '\n' && state->single_line)
  614. break;
  615. if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
  616. stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
  617. STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
  618. if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
  619. ++state->cursor;
  620. state->has_preferred_x = 0;
  621. }
  622. } else {
  623. stb_textedit_delete_selection(str,state); // implicity clamps
  624. if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
  625. stb_text_makeundo_insert(state, state->cursor, 1);
  626. ++state->cursor;
  627. state->has_preferred_x = 0;
  628. }
  629. }
  630. }
  631. break;
  632. }
  633. #ifdef STB_TEXTEDIT_K_INSERT
  634. case STB_TEXTEDIT_K_INSERT:
  635. state->insert_mode = !state->insert_mode;
  636. break;
  637. #endif
  638. case STB_TEXTEDIT_K_UNDO:
  639. stb_text_undo(str, state);
  640. state->has_preferred_x = 0;
  641. break;
  642. case STB_TEXTEDIT_K_REDO:
  643. stb_text_redo(str, state);
  644. state->has_preferred_x = 0;
  645. break;
  646. case STB_TEXTEDIT_K_LEFT:
  647. // if currently there's a selection, move cursor to start of selection
  648. if (STB_TEXT_HAS_SELECTION(state))
  649. stb_textedit_move_to_first(state);
  650. else
  651. if (state->cursor > 0)
  652. --state->cursor;
  653. state->has_preferred_x = 0;
  654. break;
  655. case STB_TEXTEDIT_K_RIGHT:
  656. // if currently there's a selection, move cursor to end of selection
  657. if (STB_TEXT_HAS_SELECTION(state))
  658. stb_textedit_move_to_last(str, state);
  659. else
  660. ++state->cursor;
  661. stb_textedit_clamp(str, state);
  662. state->has_preferred_x = 0;
  663. break;
  664. case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
  665. stb_textedit_clamp(str, state);
  666. stb_textedit_prep_selection_at_cursor(state);
  667. // move selection left
  668. if (state->select_end > 0)
  669. --state->select_end;
  670. state->cursor = state->select_end;
  671. state->has_preferred_x = 0;
  672. break;
  673. #ifdef STB_TEXTEDIT_IS_SPACE
  674. case STB_TEXTEDIT_K_WORDLEFT:
  675. if (STB_TEXT_HAS_SELECTION(state))
  676. stb_textedit_move_to_first(state);
  677. else {
  678. state->cursor = stb_textedit_move_to_word_previous(str, state);
  679. stb_textedit_clamp( str, state );
  680. }
  681. break;
  682. case STB_TEXTEDIT_K_WORDRIGHT:
  683. if (STB_TEXT_HAS_SELECTION(state))
  684. stb_textedit_move_to_last(str, state);
  685. else {
  686. state->cursor = stb_textedit_move_to_word_next(str, state);
  687. stb_textedit_clamp( str, state );
  688. }
  689. break;
  690. case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
  691. if( !STB_TEXT_HAS_SELECTION( state ) )
  692. stb_textedit_prep_selection_at_cursor(state);
  693. state->cursor = stb_textedit_move_to_word_previous(str, state);
  694. state->select_end = state->cursor;
  695. stb_textedit_clamp( str, state );
  696. break;
  697. case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
  698. if( !STB_TEXT_HAS_SELECTION( state ) )
  699. stb_textedit_prep_selection_at_cursor(state);
  700. state->cursor = stb_textedit_move_to_word_next(str, state);
  701. state->select_end = state->cursor;
  702. stb_textedit_clamp( str, state );
  703. break;
  704. #endif
  705. case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
  706. stb_textedit_prep_selection_at_cursor(state);
  707. // move selection right
  708. ++state->select_end;
  709. stb_textedit_clamp(str, state);
  710. state->cursor = state->select_end;
  711. state->has_preferred_x = 0;
  712. break;
  713. case STB_TEXTEDIT_K_DOWN:
  714. case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
  715. StbFindState find;
  716. StbTexteditRow row;
  717. int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
  718. if (state->single_line) {
  719. // on windows, up&down in single-line behave like left&right
  720. key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
  721. goto retry;
  722. }
  723. if (sel)
  724. stb_textedit_prep_selection_at_cursor(state);
  725. else if (STB_TEXT_HAS_SELECTION(state))
  726. stb_textedit_move_to_last(str,state);
  727. // compute current position of cursor point
  728. stb_textedit_clamp(str, state);
  729. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  730. // now find character position down a row
  731. if (find.length) {
  732. float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
  733. float x;
  734. int start = find.first_char + find.length;
  735. state->cursor = start;
  736. STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
  737. x = row.x0;
  738. for (i=0; i < row.num_chars; ++i) {
  739. float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
  740. #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
  741. if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
  742. break;
  743. #endif
  744. x += dx;
  745. if (x > goal_x)
  746. break;
  747. ++state->cursor;
  748. }
  749. stb_textedit_clamp(str, state);
  750. state->has_preferred_x = 1;
  751. state->preferred_x = goal_x;
  752. if (sel)
  753. state->select_end = state->cursor;
  754. }
  755. break;
  756. }
  757. case STB_TEXTEDIT_K_UP:
  758. case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
  759. StbFindState find;
  760. StbTexteditRow row;
  761. int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
  762. if (state->single_line) {
  763. // on windows, up&down become left&right
  764. key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
  765. goto retry;
  766. }
  767. if (sel)
  768. stb_textedit_prep_selection_at_cursor(state);
  769. else if (STB_TEXT_HAS_SELECTION(state))
  770. stb_textedit_move_to_first(state);
  771. // compute current position of cursor point
  772. stb_textedit_clamp(str, state);
  773. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  774. // can only go up if there's a previous row
  775. if (find.prev_first != find.first_char) {
  776. // now find character position up a row
  777. float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
  778. float x;
  779. state->cursor = find.prev_first;
  780. STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
  781. x = row.x0;
  782. for (i=0; i < row.num_chars; ++i) {
  783. float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
  784. #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
  785. if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
  786. break;
  787. #endif
  788. x += dx;
  789. if (x > goal_x)
  790. break;
  791. ++state->cursor;
  792. }
  793. stb_textedit_clamp(str, state);
  794. state->has_preferred_x = 1;
  795. state->preferred_x = goal_x;
  796. if (sel)
  797. state->select_end = state->cursor;
  798. }
  799. break;
  800. }
  801. case STB_TEXTEDIT_K_DELETE:
  802. case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
  803. if (STB_TEXT_HAS_SELECTION(state))
  804. stb_textedit_delete_selection(str, state);
  805. else {
  806. int n = STB_TEXTEDIT_STRINGLEN(str);
  807. if (state->cursor < n)
  808. stb_textedit_delete(str, state, state->cursor, 1);
  809. }
  810. state->has_preferred_x = 0;
  811. break;
  812. case STB_TEXTEDIT_K_BACKSPACE:
  813. case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
  814. if (STB_TEXT_HAS_SELECTION(state))
  815. stb_textedit_delete_selection(str, state);
  816. else {
  817. stb_textedit_clamp(str, state);
  818. if (state->cursor > 0) {
  819. stb_textedit_delete(str, state, state->cursor-1, 1);
  820. --state->cursor;
  821. }
  822. }
  823. state->has_preferred_x = 0;
  824. break;
  825. case STB_TEXTEDIT_K_TEXTSTART:
  826. state->cursor = state->select_start = state->select_end = 0;
  827. state->has_preferred_x = 0;
  828. break;
  829. case STB_TEXTEDIT_K_TEXTEND:
  830. state->cursor = STB_TEXTEDIT_STRINGLEN(str);
  831. state->select_start = state->select_end = 0;
  832. state->has_preferred_x = 0;
  833. break;
  834. case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
  835. stb_textedit_prep_selection_at_cursor(state);
  836. state->cursor = state->select_end = 0;
  837. state->has_preferred_x = 0;
  838. break;
  839. case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
  840. stb_textedit_prep_selection_at_cursor(state);
  841. state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
  842. state->has_preferred_x = 0;
  843. break;
  844. case STB_TEXTEDIT_K_LINESTART: {
  845. StbFindState find;
  846. stb_textedit_clamp(str, state);
  847. stb_textedit_move_to_first(state);
  848. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  849. state->cursor = find.first_char;
  850. state->has_preferred_x = 0;
  851. break;
  852. }
  853. case STB_TEXTEDIT_K_LINEEND: {
  854. StbFindState find;
  855. stb_textedit_clamp(str, state);
  856. stb_textedit_move_to_first(state);
  857. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  858. state->cursor = find.first_char + find.length;
  859. state->has_preferred_x = 0;
  860. break;
  861. }
  862. case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT: {
  863. StbFindState find;
  864. stb_textedit_clamp(str, state);
  865. stb_textedit_prep_selection_at_cursor(state);
  866. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  867. state->cursor = state->select_end = find.first_char;
  868. state->has_preferred_x = 0;
  869. break;
  870. }
  871. case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
  872. StbFindState find;
  873. stb_textedit_clamp(str, state);
  874. stb_textedit_prep_selection_at_cursor(state);
  875. stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
  876. state->cursor = state->select_end = find.first_char + find.length;
  877. state->has_preferred_x = 0;
  878. break;
  879. }
  880. // @TODO:
  881. // STB_TEXTEDIT_K_PGUP - move cursor up a page
  882. // STB_TEXTEDIT_K_PGDOWN - move cursor down a page
  883. }
  884. }
  885. /////////////////////////////////////////////////////////////////////////////
  886. //
  887. // Undo processing
  888. //
  889. // @OPTIMIZE: the undo/redo buffer should be circular
  890. static void stb_textedit_flush_redo(StbUndoState *state)
  891. {
  892. state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
  893. state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
  894. }
  895. // discard the oldest entry in the undo list
  896. static void stb_textedit_discard_undo(StbUndoState *state)
  897. {
  898. if (state->undo_point > 0) {
  899. // if the 0th undo state has characters, clean those up
  900. if (state->undo_rec[0].char_storage >= 0) {
  901. int n = state->undo_rec[0].insert_length, i;
  902. // delete n characters from all other records
  903. state->undo_char_point = state->undo_char_point - (short) n; // vsnet05
  904. memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
  905. for (i=0; i < state->undo_point; ++i)
  906. if (state->undo_rec[i].char_storage >= 0)
  907. state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it
  908. }
  909. --state->undo_point;
  910. memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
  911. }
  912. }
  913. // discard the oldest entry in the redo list--it's bad if this
  914. // ever happens, but because undo & redo have to store the actual
  915. // characters in different cases, the redo character buffer can
  916. // fill up even though the undo buffer didn't
  917. static void stb_textedit_discard_redo(StbUndoState *state)
  918. {
  919. int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
  920. if (state->redo_point <= k) {
  921. // if the k'th undo state has characters, clean those up
  922. if (state->undo_rec[k].char_storage >= 0) {
  923. int n = state->undo_rec[k].insert_length, i;
  924. // delete n characters from all other records
  925. state->redo_char_point = state->redo_char_point + (short) n; // vsnet05
  926. memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE)));
  927. for (i=state->redo_point; i < k; ++i)
  928. if (state->undo_rec[i].char_storage >= 0)
  929. state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05
  930. }
  931. ++state->redo_point;
  932. memmove(state->undo_rec + state->redo_point-1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0])));
  933. }
  934. }
  935. static StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
  936. {
  937. // any time we create a new undo record, we discard redo
  938. stb_textedit_flush_redo(state);
  939. // if we have no free records, we have to make room, by sliding the
  940. // existing records down
  941. if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
  942. stb_textedit_discard_undo(state);
  943. // if the characters to store won't possibly fit in the buffer, we can't undo
  944. if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
  945. state->undo_point = 0;
  946. state->undo_char_point = 0;
  947. return NULL;
  948. }
  949. // if we don't have enough free characters in the buffer, we have to make room
  950. while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
  951. stb_textedit_discard_undo(state);
  952. return &state->undo_rec[state->undo_point++];
  953. }
  954. static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
  955. {
  956. StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
  957. if (r == NULL)
  958. return NULL;
  959. r->where = pos;
  960. r->insert_length = (short) insert_len;
  961. r->delete_length = (short) delete_len;
  962. if (insert_len == 0) {
  963. r->char_storage = -1;
  964. return NULL;
  965. } else {
  966. r->char_storage = state->undo_char_point;
  967. state->undo_char_point = state->undo_char_point + (short) insert_len;
  968. return &state->undo_char[r->char_storage];
  969. }
  970. }
  971. static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  972. {
  973. StbUndoState *s = &state->undostate;
  974. StbUndoRecord u, *r;
  975. if (s->undo_point == 0)
  976. return;
  977. // we need to do two things: apply the undo record, and create a redo record
  978. u = s->undo_rec[s->undo_point-1];
  979. r = &s->undo_rec[s->redo_point-1];
  980. r->char_storage = -1;
  981. r->insert_length = u.delete_length;
  982. r->delete_length = u.insert_length;
  983. r->where = u.where;
  984. if (u.delete_length) {
  985. // if the undo record says to delete characters, then the redo record will
  986. // need to re-insert the characters that get deleted, so we need to store
  987. // them.
  988. // there are three cases:
  989. // there's enough room to store the characters
  990. // characters stored for *redoing* don't leave room for redo
  991. // characters stored for *undoing* don't leave room for redo
  992. // if the last is true, we have to bail
  993. if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
  994. // the undo records take up too much character space; there's no space to store the redo characters
  995. r->insert_length = 0;
  996. } else {
  997. int i;
  998. // there's definitely room to store the characters eventually
  999. while (s->undo_char_point + u.delete_length > s->redo_char_point) {
  1000. // there's currently not enough room, so discard a redo record
  1001. stb_textedit_discard_redo(s);
  1002. // should never happen:
  1003. if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
  1004. return;
  1005. }
  1006. r = &s->undo_rec[s->redo_point-1];
  1007. r->char_storage = s->redo_char_point - u.delete_length;
  1008. s->redo_char_point = s->redo_char_point - (short) u.delete_length;
  1009. // now save the characters
  1010. for (i=0; i < u.delete_length; ++i)
  1011. s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
  1012. }
  1013. // now we can carry out the deletion
  1014. STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
  1015. }
  1016. // check type of recorded action:
  1017. if (u.insert_length) {
  1018. // easy case: was a deletion, so we need to insert n characters
  1019. STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
  1020. s->undo_char_point -= u.insert_length;
  1021. }
  1022. state->cursor = u.where + u.insert_length;
  1023. s->undo_point--;
  1024. s->redo_point--;
  1025. }
  1026. static void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
  1027. {
  1028. StbUndoState *s = &state->undostate;
  1029. StbUndoRecord *u, r;
  1030. if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
  1031. return;
  1032. // we need to do two things: apply the redo record, and create an undo record
  1033. u = &s->undo_rec[s->undo_point];
  1034. r = s->undo_rec[s->redo_point];
  1035. // we KNOW there must be room for the undo record, because the redo record
  1036. // was derived from an undo record
  1037. u->delete_length = r.insert_length;
  1038. u->insert_length = r.delete_length;
  1039. u->where = r.where;
  1040. u->char_storage = -1;
  1041. if (r.delete_length) {
  1042. // the redo record requires us to delete characters, so the undo record
  1043. // needs to store the characters
  1044. if (s->undo_char_point + u->insert_length > s->redo_char_point) {
  1045. u->insert_length = 0;
  1046. u->delete_length = 0;
  1047. } else {
  1048. int i;
  1049. u->char_storage = s->undo_char_point;
  1050. s->undo_char_point = s->undo_char_point + u->insert_length;
  1051. // now save the characters
  1052. for (i=0; i < u->insert_length; ++i)
  1053. s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
  1054. }
  1055. STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
  1056. }
  1057. if (r.insert_length) {
  1058. // easy case: need to insert n characters
  1059. STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
  1060. }
  1061. state->cursor = r.where + r.insert_length;
  1062. s->undo_point++;
  1063. s->redo_point++;
  1064. }
  1065. static void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
  1066. {
  1067. stb_text_createundo(&state->undostate, where, 0, length);
  1068. }
  1069. static void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
  1070. {
  1071. int i;
  1072. STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
  1073. if (p) {
  1074. for (i=0; i < length; ++i)
  1075. p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
  1076. }
  1077. }
  1078. static void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
  1079. {
  1080. int i;
  1081. STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
  1082. if (p) {
  1083. for (i=0; i < old_length; ++i)
  1084. p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
  1085. }
  1086. }
  1087. // reset the state to default
  1088. static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
  1089. {
  1090. state->undostate.undo_point = 0;
  1091. state->undostate.undo_char_point = 0;
  1092. state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
  1093. state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
  1094. state->select_end = state->select_start = 0;
  1095. state->cursor = 0;
  1096. state->has_preferred_x = 0;
  1097. state->preferred_x = 0;
  1098. state->cursor_at_end_of_line = 0;
  1099. state->initialized = 1;
  1100. state->single_line = (unsigned char) is_single_line;
  1101. state->insert_mode = 0;
  1102. }
  1103. // API initialize
  1104. static void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
  1105. {
  1106. stb_textedit_clear_state(state, is_single_line);
  1107. }
  1108. #endif//STB_TEXTEDIT_IMPLEMENTATION