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 46KB

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