stb_textedit.h 48 KB

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