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

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