19f464c52Smaya// [DEAR IMGUI]
29f464c52Smaya// This is a slightly modified version of stb_textedit.h 1.13.
39f464c52Smaya// Those changes would need to be pushed into nothings/stb:
49f464c52Smaya// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
59f464c52Smaya// Grep for [DEAR IMGUI] to find the changes.
69f464c52Smaya
79f464c52Smaya// stb_textedit.h - v1.13  - public domain - Sean Barrett
89f464c52Smaya// Development of this library was sponsored by RAD Game Tools
99f464c52Smaya//
109f464c52Smaya// This C header file implements the guts of a multi-line text-editing
119f464c52Smaya// widget; you implement display, word-wrapping, and low-level string
129f464c52Smaya// insertion/deletion, and stb_textedit will map user inputs into
139f464c52Smaya// insertions & deletions, plus updates to the cursor position,
149f464c52Smaya// selection state, and undo state.
159f464c52Smaya//
169f464c52Smaya// It is intended for use in games and other systems that need to build
179f464c52Smaya// their own custom widgets and which do not have heavy text-editing
189f464c52Smaya// requirements (this library is not recommended for use for editing large
199f464c52Smaya// texts, as its performance does not scale and it has limited undo).
209f464c52Smaya//
219f464c52Smaya// Non-trivial behaviors are modelled after Windows text controls.
229f464c52Smaya//
239f464c52Smaya//
249f464c52Smaya// LICENSE
259f464c52Smaya//
269f464c52Smaya// See end of file for license information.
279f464c52Smaya//
289f464c52Smaya//
299f464c52Smaya// DEPENDENCIES
309f464c52Smaya//
319f464c52Smaya// Uses the C runtime function 'memmove', which you can override
329f464c52Smaya// by defining STB_TEXTEDIT_memmove before the implementation.
339f464c52Smaya// Uses no other functions. Performs no runtime allocations.
349f464c52Smaya//
359f464c52Smaya//
369f464c52Smaya// VERSION HISTORY
379f464c52Smaya//
389f464c52Smaya//   1.13 (2019-02-07) fix bug in undo size management
399f464c52Smaya//   1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash
409f464c52Smaya//   1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield
419f464c52Smaya//   1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual
429f464c52Smaya//   1.9  (2016-08-27) customizable move-by-word
439f464c52Smaya//   1.8  (2016-04-02) better keyboard handling when mouse button is down
449f464c52Smaya//   1.7  (2015-09-13) change y range handling in case baseline is non-0
459f464c52Smaya//   1.6  (2015-04-15) allow STB_TEXTEDIT_memmove
469f464c52Smaya//   1.5  (2014-09-10) add support for secondary keys for OS X
479f464c52Smaya//   1.4  (2014-08-17) fix signed/unsigned warnings
489f464c52Smaya//   1.3  (2014-06-19) fix mouse clicking to round to nearest char boundary
499f464c52Smaya//   1.2  (2014-05-27) fix some RAD types that had crept into the new code
509f464c52Smaya//   1.1  (2013-12-15) move-by-word (requires STB_TEXTEDIT_IS_SPACE )
519f464c52Smaya//   1.0  (2012-07-26) improve documentation, initial public release
529f464c52Smaya//   0.3  (2012-02-24) bugfixes, single-line mode; insert mode
539f464c52Smaya//   0.2  (2011-11-28) fixes to undo/redo
549f464c52Smaya//   0.1  (2010-07-08) initial version
559f464c52Smaya//
569f464c52Smaya// ADDITIONAL CONTRIBUTORS
579f464c52Smaya//
589f464c52Smaya//   Ulf Winklemann: move-by-word in 1.1
599f464c52Smaya//   Fabian Giesen: secondary key inputs in 1.5
609f464c52Smaya//   Martins Mozeiko: STB_TEXTEDIT_memmove in 1.6
619f464c52Smaya//
629f464c52Smaya//   Bugfixes:
639f464c52Smaya//      Scott Graham
649f464c52Smaya//      Daniel Keller
659f464c52Smaya//      Omar Cornut
669f464c52Smaya//      Dan Thompson
679f464c52Smaya//
689f464c52Smaya// USAGE
699f464c52Smaya//
709f464c52Smaya// This file behaves differently depending on what symbols you define
719f464c52Smaya// before including it.
729f464c52Smaya//
739f464c52Smaya//
749f464c52Smaya// Header-file mode:
759f464c52Smaya//
769f464c52Smaya//   If you do not define STB_TEXTEDIT_IMPLEMENTATION before including this,
779f464c52Smaya//   it will operate in "header file" mode. In this mode, it declares a
789f464c52Smaya//   single public symbol, STB_TexteditState, which encapsulates the current
799f464c52Smaya//   state of a text widget (except for the string, which you will store
809f464c52Smaya//   separately).
819f464c52Smaya//
829f464c52Smaya//   To compile in this mode, you must define STB_TEXTEDIT_CHARTYPE to a
839f464c52Smaya//   primitive type that defines a single character (e.g. char, wchar_t, etc).
849f464c52Smaya//
859f464c52Smaya//   To save space or increase undo-ability, you can optionally define the
869f464c52Smaya//   following things that are used by the undo system:
879f464c52Smaya//
889f464c52Smaya//      STB_TEXTEDIT_POSITIONTYPE         small int type encoding a valid cursor position
899f464c52Smaya//      STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
909f464c52Smaya//      STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
919f464c52Smaya//
929f464c52Smaya//   If you don't define these, they are set to permissive types and
939f464c52Smaya//   moderate sizes. The undo system does no memory allocations, so
949f464c52Smaya//   it grows STB_TexteditState by the worst-case storage which is (in bytes):
959f464c52Smaya//
969f464c52Smaya//        [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT
979f464c52Smaya//      +          sizeof(STB_TEXTEDIT_CHARTYPE)      * STB_TEXTEDIT_UNDOCHAR_COUNT
989f464c52Smaya//
999f464c52Smaya//
1009f464c52Smaya// Implementation mode:
1019f464c52Smaya//
1029f464c52Smaya//   If you define STB_TEXTEDIT_IMPLEMENTATION before including this, it
1039f464c52Smaya//   will compile the implementation of the text edit widget, depending
1049f464c52Smaya//   on a large number of symbols which must be defined before the include.
1059f464c52Smaya//
1069f464c52Smaya//   The implementation is defined only as static functions. You will then
1079f464c52Smaya//   need to provide your own APIs in the same file which will access the
1089f464c52Smaya//   static functions.
1099f464c52Smaya//
1109f464c52Smaya//   The basic concept is that you provide a "string" object which
1119f464c52Smaya//   behaves like an array of characters. stb_textedit uses indices to
1129f464c52Smaya//   refer to positions in the string, implicitly representing positions
1139f464c52Smaya//   in the displayed textedit. This is true for both plain text and
1149f464c52Smaya//   rich text; even with rich text stb_truetype interacts with your
1159f464c52Smaya//   code as if there was an array of all the displayed characters.
1169f464c52Smaya//
1179f464c52Smaya// Symbols that must be the same in header-file and implementation mode:
1189f464c52Smaya//
1199f464c52Smaya//     STB_TEXTEDIT_CHARTYPE             the character type
1209f464c52Smaya//     STB_TEXTEDIT_POSITIONTYPE         small type that is a valid cursor position
1219f464c52Smaya//     STB_TEXTEDIT_UNDOSTATECOUNT       the number of undo states to allow
1229f464c52Smaya//     STB_TEXTEDIT_UNDOCHARCOUNT        the number of characters to store in the undo buffer
1239f464c52Smaya//
1249f464c52Smaya// Symbols you must define for implementation mode:
1259f464c52Smaya//
1269f464c52Smaya//    STB_TEXTEDIT_STRING               the type of object representing a string being edited,
1279f464c52Smaya//                                      typically this is a wrapper object with other data you need
1289f464c52Smaya//
1299f464c52Smaya//    STB_TEXTEDIT_STRINGLEN(obj)       the length of the string (ideally O(1))
1309f464c52Smaya//    STB_TEXTEDIT_LAYOUTROW(&r,obj,n)  returns the results of laying out a line of characters
1319f464c52Smaya//                                        starting from character #n (see discussion below)
1329f464c52Smaya//    STB_TEXTEDIT_GETWIDTH(obj,n,i)    returns the pixel delta from the xpos of the i'th character
1339f464c52Smaya//                                        to the xpos of the i+1'th char for a line of characters
1349f464c52Smaya//                                        starting at character #n (i.e. accounts for kerning
1359f464c52Smaya//                                        with previous char)
1369f464c52Smaya//    STB_TEXTEDIT_KEYTOTEXT(k)         maps a keyboard input to an insertable character
1379f464c52Smaya//                                        (return type is int, -1 means not valid to insert)
1389f464c52Smaya//    STB_TEXTEDIT_GETCHAR(obj,i)       returns the i'th character of obj, 0-based
1399f464c52Smaya//    STB_TEXTEDIT_NEWLINE              the character returned by _GETCHAR() we recognize
1409f464c52Smaya//                                        as manually wordwrapping for end-of-line positioning
1419f464c52Smaya//
1429f464c52Smaya//    STB_TEXTEDIT_DELETECHARS(obj,i,n)      delete n characters starting at i
1439f464c52Smaya//    STB_TEXTEDIT_INSERTCHARS(obj,i,c*,n)   insert n characters at i (pointed to by STB_TEXTEDIT_CHARTYPE*)
1449f464c52Smaya//
1459f464c52Smaya//    STB_TEXTEDIT_K_SHIFT       a power of two that is or'd in to a keyboard input to represent the shift key
1469f464c52Smaya//
1479f464c52Smaya//    STB_TEXTEDIT_K_LEFT        keyboard input to move cursor left
1489f464c52Smaya//    STB_TEXTEDIT_K_RIGHT       keyboard input to move cursor right
1499f464c52Smaya//    STB_TEXTEDIT_K_UP          keyboard input to move cursor up
1509f464c52Smaya//    STB_TEXTEDIT_K_DOWN        keyboard input to move cursor down
1519f464c52Smaya//    STB_TEXTEDIT_K_LINESTART   keyboard input to move cursor to start of line  // e.g. HOME
1529f464c52Smaya//    STB_TEXTEDIT_K_LINEEND     keyboard input to move cursor to end of line    // e.g. END
1539f464c52Smaya//    STB_TEXTEDIT_K_TEXTSTART   keyboard input to move cursor to start of text  // e.g. ctrl-HOME
1549f464c52Smaya//    STB_TEXTEDIT_K_TEXTEND     keyboard input to move cursor to end of text    // e.g. ctrl-END
1559f464c52Smaya//    STB_TEXTEDIT_K_DELETE      keyboard input to delete selection or character under cursor
1569f464c52Smaya//    STB_TEXTEDIT_K_BACKSPACE   keyboard input to delete selection or character left of cursor
1579f464c52Smaya//    STB_TEXTEDIT_K_UNDO        keyboard input to perform undo
1589f464c52Smaya//    STB_TEXTEDIT_K_REDO        keyboard input to perform redo
1599f464c52Smaya//
1609f464c52Smaya// Optional:
1619f464c52Smaya//    STB_TEXTEDIT_K_INSERT              keyboard input to toggle insert mode
1629f464c52Smaya//    STB_TEXTEDIT_IS_SPACE(ch)          true if character is whitespace (e.g. 'isspace'),
1639f464c52Smaya//                                          required for default WORDLEFT/WORDRIGHT handlers
1649f464c52Smaya//    STB_TEXTEDIT_MOVEWORDLEFT(obj,i)   custom handler for WORDLEFT, returns index to move cursor to
1659f464c52Smaya//    STB_TEXTEDIT_MOVEWORDRIGHT(obj,i)  custom handler for WORDRIGHT, returns index to move cursor to
1669f464c52Smaya//    STB_TEXTEDIT_K_WORDLEFT            keyboard input to move cursor left one word // e.g. ctrl-LEFT
1679f464c52Smaya//    STB_TEXTEDIT_K_WORDRIGHT           keyboard input to move cursor right one word // e.g. ctrl-RIGHT
1689f464c52Smaya//    STB_TEXTEDIT_K_LINESTART2          secondary keyboard input to move cursor to start of line
1699f464c52Smaya//    STB_TEXTEDIT_K_LINEEND2            secondary keyboard input to move cursor to end of line
1709f464c52Smaya//    STB_TEXTEDIT_K_TEXTSTART2          secondary keyboard input to move cursor to start of text
1719f464c52Smaya//    STB_TEXTEDIT_K_TEXTEND2            secondary keyboard input to move cursor to end of text
1729f464c52Smaya//
1739f464c52Smaya// Todo:
1749f464c52Smaya//    STB_TEXTEDIT_K_PGUP        keyboard input to move cursor up a page
1759f464c52Smaya//    STB_TEXTEDIT_K_PGDOWN      keyboard input to move cursor down a page
1769f464c52Smaya//
1779f464c52Smaya// Keyboard input must be encoded as a single integer value; e.g. a character code
1789f464c52Smaya// and some bitflags that represent shift states. to simplify the interface, SHIFT must
1799f464c52Smaya// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
1809f464c52Smaya// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
1819f464c52Smaya//
1829f464c52Smaya// You can encode other things, such as CONTROL or ALT, in additional bits, and
1839f464c52Smaya// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
1849f464c52Smaya// my Windows implementations add an additional CONTROL bit, and an additional KEYDOWN
1859f464c52Smaya// bit. Then all of the STB_TEXTEDIT_K_ values bitwise-or in the KEYDOWN bit,
1869f464c52Smaya// and I pass both WM_KEYDOWN and WM_CHAR events to the "key" function in the
1879f464c52Smaya// API below. The control keys will only match WM_KEYDOWN events because of the
1889f464c52Smaya// keydown bit I add, and STB_TEXTEDIT_KEYTOTEXT only tests for the KEYDOWN
1899f464c52Smaya// bit so it only decodes WM_CHAR events.
1909f464c52Smaya//
1919f464c52Smaya// STB_TEXTEDIT_LAYOUTROW returns information about the shape of one displayed
1929f464c52Smaya// row of characters assuming they start on the i'th character--the width and
1939f464c52Smaya// the height and the number of characters consumed. This allows this library
1949f464c52Smaya// to traverse the entire layout incrementally. You need to compute word-wrapping
1959f464c52Smaya// here.
1969f464c52Smaya//
1979f464c52Smaya// Each textfield keeps its own insert mode state, which is not how normal
1989f464c52Smaya// applications work. To keep an app-wide insert mode, update/copy the
1999f464c52Smaya// "insert_mode" field of STB_TexteditState before/after calling API functions.
2009f464c52Smaya//
2019f464c52Smaya// API
2029f464c52Smaya//
2039f464c52Smaya//    void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
2049f464c52Smaya//
2059f464c52Smaya//    void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
2069f464c52Smaya//    void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
2079f464c52Smaya//    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
2089f464c52Smaya//    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
2099f464c52Smaya//    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
2109f464c52Smaya//
2119f464c52Smaya//    Each of these functions potentially updates the string and updates the
2129f464c52Smaya//    state.
2139f464c52Smaya//
2149f464c52Smaya//      initialize_state:
2159f464c52Smaya//          set the textedit state to a known good default state when initially
2169f464c52Smaya//          constructing the textedit.
2179f464c52Smaya//
2189f464c52Smaya//      click:
2199f464c52Smaya//          call this with the mouse x,y on a mouse down; it will update the cursor
2209f464c52Smaya//          and reset the selection start/end to the cursor point. the x,y must
2219f464c52Smaya//          be relative to the text widget, with (0,0) being the top left.
2229f464c52Smaya//
2239f464c52Smaya//      drag:
2249f464c52Smaya//          call this with the mouse x,y on a mouse drag/up; it will update the
2259f464c52Smaya//          cursor and the selection end point
2269f464c52Smaya//
2279f464c52Smaya//      cut:
2289f464c52Smaya//          call this to delete the current selection; returns true if there was
2299f464c52Smaya//          one. you should FIRST copy the current selection to the system paste buffer.
2309f464c52Smaya//          (To copy, just copy the current selection out of the string yourself.)
2319f464c52Smaya//
2329f464c52Smaya//      paste:
2339f464c52Smaya//          call this to paste text at the current cursor point or over the current
2349f464c52Smaya//          selection if there is one.
2359f464c52Smaya//
2369f464c52Smaya//      key:
2379f464c52Smaya//          call this for keyboard inputs sent to the textfield. you can use it
2389f464c52Smaya//          for "key down" events or for "translated" key events. if you need to
2399f464c52Smaya//          do both (as in Win32), or distinguish Unicode characters from control
2409f464c52Smaya//          inputs, set a high bit to distinguish the two; then you can define the
2419f464c52Smaya//          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
2429f464c52Smaya//          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
2439f464c52Smaya//          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
2449f464c52Smaya//          anything other type you wante before including.
2459f464c52Smaya//
2469f464c52Smaya//
2479f464c52Smaya//   When rendering, you can read the cursor position and selection state from
2489f464c52Smaya//   the STB_TexteditState.
2499f464c52Smaya//
2509f464c52Smaya//
2519f464c52Smaya// Notes:
2529f464c52Smaya//
2539f464c52Smaya// This is designed to be usable in IMGUI, so it allows for the possibility of
2549f464c52Smaya// running in an IMGUI that has NOT cached the multi-line layout. For this
2559f464c52Smaya// reason, it provides an interface that is compatible with computing the
2569f464c52Smaya// layout incrementally--we try to make sure we make as few passes through
2579f464c52Smaya// as possible. (For example, to locate the mouse pointer in the text, we
2589f464c52Smaya// could define functions that return the X and Y positions of characters
2599f464c52Smaya// and binary search Y and then X, but if we're doing dynamic layout this
2609f464c52Smaya// will run the layout algorithm many times, so instead we manually search
2619f464c52Smaya// forward in one pass. Similar logic applies to e.g. up-arrow and
2629f464c52Smaya// down-arrow movement.)
2639f464c52Smaya//
2649f464c52Smaya// If it's run in a widget that *has* cached the layout, then this is less
2659f464c52Smaya// efficient, but it's not horrible on modern computers. But you wouldn't
2669f464c52Smaya// want to edit million-line files with it.
2679f464c52Smaya
2689f464c52Smaya
2699f464c52Smaya////////////////////////////////////////////////////////////////////////////
2709f464c52Smaya////////////////////////////////////////////////////////////////////////////
2719f464c52Smaya////
2729f464c52Smaya////   Header-file mode
2739f464c52Smaya////
2749f464c52Smaya////
2759f464c52Smaya
2769f464c52Smaya#ifndef INCLUDE_STB_TEXTEDIT_H
2779f464c52Smaya#define INCLUDE_STB_TEXTEDIT_H
2789f464c52Smaya
2799f464c52Smaya////////////////////////////////////////////////////////////////////////
2809f464c52Smaya//
2819f464c52Smaya//     STB_TexteditState
2829f464c52Smaya//
2839f464c52Smaya// Definition of STB_TexteditState which you should store
2849f464c52Smaya// per-textfield; it includes cursor position, selection state,
2859f464c52Smaya// and undo state.
2869f464c52Smaya//
2879f464c52Smaya
2889f464c52Smaya#ifndef STB_TEXTEDIT_UNDOSTATECOUNT
2899f464c52Smaya#define STB_TEXTEDIT_UNDOSTATECOUNT   99
2909f464c52Smaya#endif
2919f464c52Smaya#ifndef STB_TEXTEDIT_UNDOCHARCOUNT
2929f464c52Smaya#define STB_TEXTEDIT_UNDOCHARCOUNT   999
2939f464c52Smaya#endif
2949f464c52Smaya#ifndef STB_TEXTEDIT_CHARTYPE
2959f464c52Smaya#define STB_TEXTEDIT_CHARTYPE        int
2969f464c52Smaya#endif
2979f464c52Smaya#ifndef STB_TEXTEDIT_POSITIONTYPE
2989f464c52Smaya#define STB_TEXTEDIT_POSITIONTYPE    int
2999f464c52Smaya#endif
3009f464c52Smaya
3019f464c52Smayatypedef struct
3029f464c52Smaya{
3039f464c52Smaya   // private data
3049f464c52Smaya   STB_TEXTEDIT_POSITIONTYPE  where;
3059f464c52Smaya   STB_TEXTEDIT_POSITIONTYPE  insert_length;
3069f464c52Smaya   STB_TEXTEDIT_POSITIONTYPE  delete_length;
3079f464c52Smaya   int                        char_storage;
3089f464c52Smaya} StbUndoRecord;
3099f464c52Smaya
3109f464c52Smayatypedef struct
3119f464c52Smaya{
3129f464c52Smaya   // private data
3139f464c52Smaya   StbUndoRecord          undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT];
3149f464c52Smaya   STB_TEXTEDIT_CHARTYPE  undo_char[STB_TEXTEDIT_UNDOCHARCOUNT];
3159f464c52Smaya   short undo_point, redo_point;
3169f464c52Smaya   int undo_char_point, redo_char_point;
3179f464c52Smaya} StbUndoState;
3189f464c52Smaya
3199f464c52Smayatypedef struct
3209f464c52Smaya{
3219f464c52Smaya   /////////////////////
3229f464c52Smaya   //
3239f464c52Smaya   // public data
3249f464c52Smaya   //
3259f464c52Smaya
3269f464c52Smaya   int cursor;
3279f464c52Smaya   // position of the text cursor within the string
3289f464c52Smaya
3299f464c52Smaya   int select_start;          // selection start point
3309f464c52Smaya   int select_end;
3319f464c52Smaya   // selection start and end point in characters; if equal, no selection.
3329f464c52Smaya   // note that start may be less than or greater than end (e.g. when
3339f464c52Smaya   // dragging the mouse, start is where the initial click was, and you
3349f464c52Smaya   // can drag in either direction)
3359f464c52Smaya
3369f464c52Smaya   unsigned char insert_mode;
3379f464c52Smaya   // each textfield keeps its own insert mode state. to keep an app-wide
3389f464c52Smaya   // insert mode, copy this value in/out of the app state
3399f464c52Smaya
3409f464c52Smaya   /////////////////////
3419f464c52Smaya   //
3429f464c52Smaya   // private data
3439f464c52Smaya   //
3449f464c52Smaya   unsigned char cursor_at_end_of_line; // not implemented yet
3459f464c52Smaya   unsigned char initialized;
3469f464c52Smaya   unsigned char has_preferred_x;
3479f464c52Smaya   unsigned char single_line;
3489f464c52Smaya   unsigned char padding1, padding2, padding3;
3499f464c52Smaya   float preferred_x; // this determines where the cursor up/down tries to seek to along x
3509f464c52Smaya   StbUndoState undostate;
3519f464c52Smaya} STB_TexteditState;
3529f464c52Smaya
3539f464c52Smaya
3549f464c52Smaya////////////////////////////////////////////////////////////////////////
3559f464c52Smaya//
3569f464c52Smaya//     StbTexteditRow
3579f464c52Smaya//
3589f464c52Smaya// Result of layout query, used by stb_textedit to determine where
3599f464c52Smaya// the text in each row is.
3609f464c52Smaya
3619f464c52Smaya// result of layout query
3629f464c52Smayatypedef struct
3639f464c52Smaya{
3649f464c52Smaya   float x0,x1;             // starting x location, end x location (allows for align=right, etc)
3659f464c52Smaya   float baseline_y_delta;  // position of baseline relative to previous row's baseline
3669f464c52Smaya   float ymin,ymax;         // height of row above and below baseline
3679f464c52Smaya   int num_chars;
3689f464c52Smaya} StbTexteditRow;
3699f464c52Smaya#endif //INCLUDE_STB_TEXTEDIT_H
3709f464c52Smaya
3719f464c52Smaya
3729f464c52Smaya////////////////////////////////////////////////////////////////////////////
3739f464c52Smaya////////////////////////////////////////////////////////////////////////////
3749f464c52Smaya////
3759f464c52Smaya////   Implementation mode
3769f464c52Smaya////
3779f464c52Smaya////
3789f464c52Smaya
3799f464c52Smaya
3809f464c52Smaya// implementation isn't include-guarded, since it might have indirectly
3819f464c52Smaya// included just the "header" portion
3829f464c52Smaya#ifdef STB_TEXTEDIT_IMPLEMENTATION
3839f464c52Smaya
3849f464c52Smaya#ifndef STB_TEXTEDIT_memmove
3859f464c52Smaya#include <string.h>
3869f464c52Smaya#define STB_TEXTEDIT_memmove memmove
3879f464c52Smaya#endif
3889f464c52Smaya
3899f464c52Smaya
3909f464c52Smaya/////////////////////////////////////////////////////////////////////////////
3919f464c52Smaya//
3929f464c52Smaya//      Mouse input handling
3939f464c52Smaya//
3949f464c52Smaya
3959f464c52Smaya// traverse the layout to locate the nearest character to a display position
3969f464c52Smayastatic int stb_text_locate_coord(STB_TEXTEDIT_STRING *str, float x, float y)
3979f464c52Smaya{
3989f464c52Smaya   StbTexteditRow r;
3999f464c52Smaya   int n = STB_TEXTEDIT_STRINGLEN(str);
4009f464c52Smaya   float base_y = 0, prev_x;
4019f464c52Smaya   int i=0, k;
4029f464c52Smaya
4039f464c52Smaya   r.x0 = r.x1 = 0;
4049f464c52Smaya   r.ymin = r.ymax = 0;
4059f464c52Smaya   r.num_chars = 0;
4069f464c52Smaya
4079f464c52Smaya   // search rows to find one that straddles 'y'
4089f464c52Smaya   while (i < n) {
4099f464c52Smaya      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
4109f464c52Smaya      if (r.num_chars <= 0)
4119f464c52Smaya         return n;
4129f464c52Smaya
4139f464c52Smaya      if (i==0 && y < base_y + r.ymin)
4149f464c52Smaya         return 0;
4159f464c52Smaya
4169f464c52Smaya      if (y < base_y + r.ymax)
4179f464c52Smaya         break;
4189f464c52Smaya
4199f464c52Smaya      i += r.num_chars;
4209f464c52Smaya      base_y += r.baseline_y_delta;
4219f464c52Smaya   }
4229f464c52Smaya
4239f464c52Smaya   // below all text, return 'after' last character
4249f464c52Smaya   if (i >= n)
4259f464c52Smaya      return n;
4269f464c52Smaya
4279f464c52Smaya   // check if it's before the beginning of the line
4289f464c52Smaya   if (x < r.x0)
4299f464c52Smaya      return i;
4309f464c52Smaya
4319f464c52Smaya   // check if it's before the end of the line
4329f464c52Smaya   if (x < r.x1) {
4339f464c52Smaya      // search characters in row for one that straddles 'x'
4349f464c52Smaya      prev_x = r.x0;
4359f464c52Smaya      for (k=0; k < r.num_chars; ++k) {
4369f464c52Smaya         float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
4379f464c52Smaya         if (x < prev_x+w) {
4389f464c52Smaya            if (x < prev_x+w/2)
4399f464c52Smaya               return k+i;
4409f464c52Smaya            else
4419f464c52Smaya               return k+i+1;
4429f464c52Smaya         }
4439f464c52Smaya         prev_x += w;
4449f464c52Smaya      }
4459f464c52Smaya      // shouldn't happen, but if it does, fall through to end-of-line case
4469f464c52Smaya   }
4479f464c52Smaya
4489f464c52Smaya   // if the last character is a newline, return that. otherwise return 'after' the last character
4499f464c52Smaya   if (STB_TEXTEDIT_GETCHAR(str, i+r.num_chars-1) == STB_TEXTEDIT_NEWLINE)
4509f464c52Smaya      return i+r.num_chars-1;
4519f464c52Smaya   else
4529f464c52Smaya      return i+r.num_chars;
4539f464c52Smaya}
4549f464c52Smaya
4559f464c52Smaya// API click: on mouse down, move the cursor to the clicked location, and reset the selection
4569f464c52Smayastatic void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
4579f464c52Smaya{
4589f464c52Smaya   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
4599f464c52Smaya   // goes off the top or bottom of the text
4609f464c52Smaya   if( state->single_line )
4619f464c52Smaya   {
4629f464c52Smaya      StbTexteditRow r;
4639f464c52Smaya      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
4649f464c52Smaya      y = r.ymin;
4659f464c52Smaya   }
4669f464c52Smaya
4679f464c52Smaya   state->cursor = stb_text_locate_coord(str, x, y);
4689f464c52Smaya   state->select_start = state->cursor;
4699f464c52Smaya   state->select_end = state->cursor;
4709f464c52Smaya   state->has_preferred_x = 0;
4719f464c52Smaya}
4729f464c52Smaya
4739f464c52Smaya// API drag: on mouse drag, move the cursor and selection endpoint to the clicked location
4749f464c52Smayastatic void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y)
4759f464c52Smaya{
4769f464c52Smaya   int p = 0;
4779f464c52Smaya
4789f464c52Smaya   // In single-line mode, just always make y = 0. This lets the drag keep working if the mouse
4799f464c52Smaya   // goes off the top or bottom of the text
4809f464c52Smaya   if( state->single_line )
4819f464c52Smaya   {
4829f464c52Smaya      StbTexteditRow r;
4839f464c52Smaya      STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
4849f464c52Smaya      y = r.ymin;
4859f464c52Smaya   }
4869f464c52Smaya
4879f464c52Smaya   if (state->select_start == state->select_end)
4889f464c52Smaya      state->select_start = state->cursor;
4899f464c52Smaya
4909f464c52Smaya   p = stb_text_locate_coord(str, x, y);
4919f464c52Smaya   state->cursor = state->select_end = p;
4929f464c52Smaya}
4939f464c52Smaya
4949f464c52Smaya/////////////////////////////////////////////////////////////////////////////
4959f464c52Smaya//
4969f464c52Smaya//      Keyboard input handling
4979f464c52Smaya//
4989f464c52Smaya
4999f464c52Smaya// forward declarations
5009f464c52Smayastatic void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
5019f464c52Smayastatic void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state);
5029f464c52Smayastatic void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length);
5039f464c52Smayastatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length);
5049f464c52Smayastatic void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length);
5059f464c52Smaya
5069f464c52Smayatypedef struct
5079f464c52Smaya{
5089f464c52Smaya   float x,y;    // position of n'th character
5099f464c52Smaya   float height; // height of line
5109f464c52Smaya   int first_char, length; // first char of row, and length
5119f464c52Smaya   int prev_first;  // first char of previous row
5129f464c52Smaya} StbFindState;
5139f464c52Smaya
5149f464c52Smaya// find the x/y location of a character, and remember info about the previous row in
5159f464c52Smaya// case we get a move-up event (for page up, we'll have to rescan)
5169f464c52Smayastatic void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *str, int n, int single_line)
5179f464c52Smaya{
5189f464c52Smaya   StbTexteditRow r;
5199f464c52Smaya   int prev_start = 0;
5209f464c52Smaya   int z = STB_TEXTEDIT_STRINGLEN(str);
5219f464c52Smaya   int i=0, first;
5229f464c52Smaya
5239f464c52Smaya   if (n == z) {
5249f464c52Smaya      // if it's at the end, then find the last line -- simpler than trying to
5259f464c52Smaya      // explicitly handle this case in the regular code
5269f464c52Smaya      if (single_line) {
5279f464c52Smaya         STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
5289f464c52Smaya         find->y = 0;
5299f464c52Smaya         find->first_char = 0;
5309f464c52Smaya         find->length = z;
5319f464c52Smaya         find->height = r.ymax - r.ymin;
5329f464c52Smaya         find->x = r.x1;
5339f464c52Smaya      } else {
5349f464c52Smaya         find->y = 0;
5359f464c52Smaya         find->x = 0;
5369f464c52Smaya         find->height = 1;
5379f464c52Smaya         while (i < z) {
5389f464c52Smaya            STB_TEXTEDIT_LAYOUTROW(&r, str, i);
5399f464c52Smaya            prev_start = i;
5409f464c52Smaya            i += r.num_chars;
5419f464c52Smaya         }
5429f464c52Smaya         find->first_char = i;
5439f464c52Smaya         find->length = 0;
5449f464c52Smaya         find->prev_first = prev_start;
5459f464c52Smaya      }
5469f464c52Smaya      return;
5479f464c52Smaya   }
5489f464c52Smaya
5499f464c52Smaya   // search rows to find the one that straddles character n
5509f464c52Smaya   find->y = 0;
5519f464c52Smaya
5529f464c52Smaya   for(;;) {
5539f464c52Smaya      STB_TEXTEDIT_LAYOUTROW(&r, str, i);
5549f464c52Smaya      if (n < i + r.num_chars)
5559f464c52Smaya         break;
5569f464c52Smaya      prev_start = i;
5579f464c52Smaya      i += r.num_chars;
5589f464c52Smaya      find->y += r.baseline_y_delta;
5599f464c52Smaya   }
5609f464c52Smaya
5619f464c52Smaya   find->first_char = first = i;
5629f464c52Smaya   find->length = r.num_chars;
5639f464c52Smaya   find->height = r.ymax - r.ymin;
5649f464c52Smaya   find->prev_first = prev_start;
5659f464c52Smaya
5669f464c52Smaya   // now scan to find xpos
5679f464c52Smaya   find->x = r.x0;
5689f464c52Smaya   for (i=0; first+i < n; ++i)
5699f464c52Smaya      find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
5709f464c52Smaya}
5719f464c52Smaya
5729f464c52Smaya#define STB_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
5739f464c52Smaya
5749f464c52Smaya// make the selection/cursor state valid if client altered the string
5759f464c52Smayastatic void stb_textedit_clamp(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
5769f464c52Smaya{
5779f464c52Smaya   int n = STB_TEXTEDIT_STRINGLEN(str);
5789f464c52Smaya   if (STB_TEXT_HAS_SELECTION(state)) {
5799f464c52Smaya      if (state->select_start > n) state->select_start = n;
5809f464c52Smaya      if (state->select_end   > n) state->select_end = n;
5819f464c52Smaya      // if clamping forced them to be equal, move the cursor to match
5829f464c52Smaya      if (state->select_start == state->select_end)
5839f464c52Smaya         state->cursor = state->select_start;
5849f464c52Smaya   }
5859f464c52Smaya   if (state->cursor > n) state->cursor = n;
5869f464c52Smaya}
5879f464c52Smaya
5889f464c52Smaya// delete characters while updating undo
5899f464c52Smayastatic void stb_textedit_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int len)
5909f464c52Smaya{
5919f464c52Smaya   stb_text_makeundo_delete(str, state, where, len);
5929f464c52Smaya   STB_TEXTEDIT_DELETECHARS(str, where, len);
5939f464c52Smaya   state->has_preferred_x = 0;
5949f464c52Smaya}
5959f464c52Smaya
5969f464c52Smaya// delete the section
5979f464c52Smayastatic void stb_textedit_delete_selection(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
5989f464c52Smaya{
5999f464c52Smaya   stb_textedit_clamp(str, state);
6009f464c52Smaya   if (STB_TEXT_HAS_SELECTION(state)) {
6019f464c52Smaya      if (state->select_start < state->select_end) {
6029f464c52Smaya         stb_textedit_delete(str, state, state->select_start, state->select_end - state->select_start);
6039f464c52Smaya         state->select_end = state->cursor = state->select_start;
6049f464c52Smaya      } else {
6059f464c52Smaya         stb_textedit_delete(str, state, state->select_end, state->select_start - state->select_end);
6069f464c52Smaya         state->select_start = state->cursor = state->select_end;
6079f464c52Smaya      }
6089f464c52Smaya      state->has_preferred_x = 0;
6099f464c52Smaya   }
6109f464c52Smaya}
6119f464c52Smaya
6129f464c52Smaya// canoncialize the selection so start <= end
6139f464c52Smayastatic void stb_textedit_sortselection(STB_TexteditState *state)
6149f464c52Smaya{
6159f464c52Smaya   if (state->select_end < state->select_start) {
6169f464c52Smaya      int temp = state->select_end;
6179f464c52Smaya      state->select_end = state->select_start;
6189f464c52Smaya      state->select_start = temp;
6199f464c52Smaya   }
6209f464c52Smaya}
6219f464c52Smaya
6229f464c52Smaya// move cursor to first character of selection
6239f464c52Smayastatic void stb_textedit_move_to_first(STB_TexteditState *state)
6249f464c52Smaya{
6259f464c52Smaya   if (STB_TEXT_HAS_SELECTION(state)) {
6269f464c52Smaya      stb_textedit_sortselection(state);
6279f464c52Smaya      state->cursor = state->select_start;
6289f464c52Smaya      state->select_end = state->select_start;
6299f464c52Smaya      state->has_preferred_x = 0;
6309f464c52Smaya   }
6319f464c52Smaya}
6329f464c52Smaya
6339f464c52Smaya// move cursor to last character of selection
6349f464c52Smayastatic void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
6359f464c52Smaya{
6369f464c52Smaya   if (STB_TEXT_HAS_SELECTION(state)) {
6379f464c52Smaya      stb_textedit_sortselection(state);
6389f464c52Smaya      stb_textedit_clamp(str, state);
6399f464c52Smaya      state->cursor = state->select_end;
6409f464c52Smaya      state->select_start = state->select_end;
6419f464c52Smaya      state->has_preferred_x = 0;
6429f464c52Smaya   }
6439f464c52Smaya}
6449f464c52Smaya
6459f464c52Smaya#ifdef STB_TEXTEDIT_IS_SPACE
6469f464c52Smayastatic int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx )
6479f464c52Smaya{
6489f464c52Smaya   return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1;
6499f464c52Smaya}
6509f464c52Smaya
6519f464c52Smaya#ifndef STB_TEXTEDIT_MOVEWORDLEFT
6529f464c52Smayastatic int stb_textedit_move_to_word_previous( STB_TEXTEDIT_STRING *str, int c )
6539f464c52Smaya{
6549f464c52Smaya   --c; // always move at least one character
6559f464c52Smaya   while( c >= 0 && !is_word_boundary( str, c ) )
6569f464c52Smaya      --c;
6579f464c52Smaya
6589f464c52Smaya   if( c < 0 )
6599f464c52Smaya      c = 0;
6609f464c52Smaya
6619f464c52Smaya   return c;
6629f464c52Smaya}
6639f464c52Smaya#define STB_TEXTEDIT_MOVEWORDLEFT stb_textedit_move_to_word_previous
6649f464c52Smaya#endif
6659f464c52Smaya
6669f464c52Smaya#ifndef STB_TEXTEDIT_MOVEWORDRIGHT
6679f464c52Smayastatic int stb_textedit_move_to_word_next( STB_TEXTEDIT_STRING *str, int c )
6689f464c52Smaya{
6699f464c52Smaya   const int len = STB_TEXTEDIT_STRINGLEN(str);
6709f464c52Smaya   ++c; // always move at least one character
6719f464c52Smaya   while( c < len && !is_word_boundary( str, c ) )
6729f464c52Smaya      ++c;
6739f464c52Smaya
6749f464c52Smaya   if( c > len )
6759f464c52Smaya      c = len;
6769f464c52Smaya
6779f464c52Smaya   return c;
6789f464c52Smaya}
6799f464c52Smaya#define STB_TEXTEDIT_MOVEWORDRIGHT stb_textedit_move_to_word_next
6809f464c52Smaya#endif
6819f464c52Smaya
6829f464c52Smaya#endif
6839f464c52Smaya
6849f464c52Smaya// update selection and cursor to match each other
6859f464c52Smayastatic void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state)
6869f464c52Smaya{
6879f464c52Smaya   if (!STB_TEXT_HAS_SELECTION(state))
6889f464c52Smaya      state->select_start = state->select_end = state->cursor;
6899f464c52Smaya   else
6909f464c52Smaya      state->cursor = state->select_end;
6919f464c52Smaya}
6929f464c52Smaya
6939f464c52Smaya// API cut: delete selection
6949f464c52Smayastatic int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
6959f464c52Smaya{
6969f464c52Smaya   if (STB_TEXT_HAS_SELECTION(state)) {
6979f464c52Smaya      stb_textedit_delete_selection(str,state); // implicitly clamps
6989f464c52Smaya      state->has_preferred_x = 0;
6999f464c52Smaya      return 1;
7009f464c52Smaya   }
7019f464c52Smaya   return 0;
7029f464c52Smaya}
7039f464c52Smaya
7049f464c52Smaya// API paste: replace existing selection with passed-in text
7059f464c52Smayastatic int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
7069f464c52Smaya{
7079f464c52Smaya   // if there's a selection, the paste should delete it
7089f464c52Smaya   stb_textedit_clamp(str, state);
7099f464c52Smaya   stb_textedit_delete_selection(str,state);
7109f464c52Smaya   // try to insert the characters
7119f464c52Smaya   if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, len)) {
7129f464c52Smaya      stb_text_makeundo_insert(state, state->cursor, len);
7139f464c52Smaya      state->cursor += len;
7149f464c52Smaya      state->has_preferred_x = 0;
7159f464c52Smaya      return 1;
7169f464c52Smaya   }
7179f464c52Smaya   // remove the undo since we didn't actually insert the characters
7189f464c52Smaya   if (state->undostate.undo_point)
7199f464c52Smaya      --state->undostate.undo_point;
7209f464c52Smaya   return 0;
7219f464c52Smaya}
7229f464c52Smaya
7239f464c52Smaya#ifndef STB_TEXTEDIT_KEYTYPE
7249f464c52Smaya#define STB_TEXTEDIT_KEYTYPE int
7259f464c52Smaya#endif
7269f464c52Smaya
7279f464c52Smaya// API key: process a keyboard input
7289f464c52Smayastatic void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
7299f464c52Smaya{
7309f464c52Smayaretry:
7319f464c52Smaya   switch (key) {
7329f464c52Smaya      default: {
7339f464c52Smaya         int c = STB_TEXTEDIT_KEYTOTEXT(key);
7349f464c52Smaya         if (c > 0) {
7359f464c52Smaya            STB_TEXTEDIT_CHARTYPE ch = (STB_TEXTEDIT_CHARTYPE) c;
7369f464c52Smaya
7379f464c52Smaya            // can't add newline in single-line mode
7389f464c52Smaya            if (c == '\n' && state->single_line)
7399f464c52Smaya               break;
7409f464c52Smaya
7419f464c52Smaya            if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
7429f464c52Smaya               stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
7439f464c52Smaya               STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
7449f464c52Smaya               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
7459f464c52Smaya                  ++state->cursor;
7469f464c52Smaya                  state->has_preferred_x = 0;
7479f464c52Smaya               }
7489f464c52Smaya            } else {
7499f464c52Smaya               stb_textedit_delete_selection(str,state); // implicitly clamps
7509f464c52Smaya               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) {
7519f464c52Smaya                  stb_text_makeundo_insert(state, state->cursor, 1);
7529f464c52Smaya                  ++state->cursor;
7539f464c52Smaya                  state->has_preferred_x = 0;
7549f464c52Smaya               }
7559f464c52Smaya            }
7569f464c52Smaya         }
7579f464c52Smaya         break;
7589f464c52Smaya      }
7599f464c52Smaya
7609f464c52Smaya#ifdef STB_TEXTEDIT_K_INSERT
7619f464c52Smaya      case STB_TEXTEDIT_K_INSERT:
7629f464c52Smaya         state->insert_mode = !state->insert_mode;
7639f464c52Smaya         break;
7649f464c52Smaya#endif
7659f464c52Smaya
7669f464c52Smaya      case STB_TEXTEDIT_K_UNDO:
7679f464c52Smaya         stb_text_undo(str, state);
7689f464c52Smaya         state->has_preferred_x = 0;
7699f464c52Smaya         break;
7709f464c52Smaya
7719f464c52Smaya      case STB_TEXTEDIT_K_REDO:
7729f464c52Smaya         stb_text_redo(str, state);
7739f464c52Smaya         state->has_preferred_x = 0;
7749f464c52Smaya         break;
7759f464c52Smaya
7769f464c52Smaya      case STB_TEXTEDIT_K_LEFT:
7779f464c52Smaya         // if currently there's a selection, move cursor to start of selection
7789f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
7799f464c52Smaya            stb_textedit_move_to_first(state);
7809f464c52Smaya         else
7819f464c52Smaya            if (state->cursor > 0)
7829f464c52Smaya               --state->cursor;
7839f464c52Smaya         state->has_preferred_x = 0;
7849f464c52Smaya         break;
7859f464c52Smaya
7869f464c52Smaya      case STB_TEXTEDIT_K_RIGHT:
7879f464c52Smaya         // if currently there's a selection, move cursor to end of selection
7889f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
7899f464c52Smaya            stb_textedit_move_to_last(str, state);
7909f464c52Smaya         else
7919f464c52Smaya            ++state->cursor;
7929f464c52Smaya         stb_textedit_clamp(str, state);
7939f464c52Smaya         state->has_preferred_x = 0;
7949f464c52Smaya         break;
7959f464c52Smaya
7969f464c52Smaya      case STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_SHIFT:
7979f464c52Smaya         stb_textedit_clamp(str, state);
7989f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
7999f464c52Smaya         // move selection left
8009f464c52Smaya         if (state->select_end > 0)
8019f464c52Smaya            --state->select_end;
8029f464c52Smaya         state->cursor = state->select_end;
8039f464c52Smaya         state->has_preferred_x = 0;
8049f464c52Smaya         break;
8059f464c52Smaya
8069f464c52Smaya#ifdef STB_TEXTEDIT_MOVEWORDLEFT
8079f464c52Smaya      case STB_TEXTEDIT_K_WORDLEFT:
8089f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
8099f464c52Smaya            stb_textedit_move_to_first(state);
8109f464c52Smaya         else {
8119f464c52Smaya            state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
8129f464c52Smaya            stb_textedit_clamp( str, state );
8139f464c52Smaya         }
8149f464c52Smaya         break;
8159f464c52Smaya
8169f464c52Smaya      case STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT:
8179f464c52Smaya         if( !STB_TEXT_HAS_SELECTION( state ) )
8189f464c52Smaya            stb_textedit_prep_selection_at_cursor(state);
8199f464c52Smaya
8209f464c52Smaya         state->cursor = STB_TEXTEDIT_MOVEWORDLEFT(str, state->cursor);
8219f464c52Smaya         state->select_end = state->cursor;
8229f464c52Smaya
8239f464c52Smaya         stb_textedit_clamp( str, state );
8249f464c52Smaya         break;
8259f464c52Smaya#endif
8269f464c52Smaya
8279f464c52Smaya#ifdef STB_TEXTEDIT_MOVEWORDRIGHT
8289f464c52Smaya      case STB_TEXTEDIT_K_WORDRIGHT:
8299f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
8309f464c52Smaya            stb_textedit_move_to_last(str, state);
8319f464c52Smaya         else {
8329f464c52Smaya            state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
8339f464c52Smaya            stb_textedit_clamp( str, state );
8349f464c52Smaya         }
8359f464c52Smaya         break;
8369f464c52Smaya
8379f464c52Smaya      case STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT:
8389f464c52Smaya         if( !STB_TEXT_HAS_SELECTION( state ) )
8399f464c52Smaya            stb_textedit_prep_selection_at_cursor(state);
8409f464c52Smaya
8419f464c52Smaya         state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor);
8429f464c52Smaya         state->select_end = state->cursor;
8439f464c52Smaya
8449f464c52Smaya         stb_textedit_clamp( str, state );
8459f464c52Smaya         break;
8469f464c52Smaya#endif
8479f464c52Smaya
8489f464c52Smaya      case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
8499f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
8509f464c52Smaya         // move selection right
8519f464c52Smaya         ++state->select_end;
8529f464c52Smaya         stb_textedit_clamp(str, state);
8539f464c52Smaya         state->cursor = state->select_end;
8549f464c52Smaya         state->has_preferred_x = 0;
8559f464c52Smaya         break;
8569f464c52Smaya
8579f464c52Smaya      case STB_TEXTEDIT_K_DOWN:
8589f464c52Smaya      case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
8599f464c52Smaya         StbFindState find;
8609f464c52Smaya         StbTexteditRow row;
8619f464c52Smaya         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
8629f464c52Smaya
8639f464c52Smaya         if (state->single_line) {
8649f464c52Smaya            // on windows, up&down in single-line behave like left&right
8659f464c52Smaya            key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
8669f464c52Smaya            goto retry;
8679f464c52Smaya         }
8689f464c52Smaya
8699f464c52Smaya         if (sel)
8709f464c52Smaya            stb_textedit_prep_selection_at_cursor(state);
8719f464c52Smaya         else if (STB_TEXT_HAS_SELECTION(state))
8729f464c52Smaya            stb_textedit_move_to_last(str,state);
8739f464c52Smaya
8749f464c52Smaya         // compute current position of cursor point
8759f464c52Smaya         stb_textedit_clamp(str, state);
8769f464c52Smaya         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
8779f464c52Smaya
8789f464c52Smaya         // now find character position down a row
8799f464c52Smaya         if (find.length) {
8809f464c52Smaya            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
8819f464c52Smaya            float x;
8829f464c52Smaya            int start = find.first_char + find.length;
8839f464c52Smaya            state->cursor = start;
8849f464c52Smaya            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
8859f464c52Smaya            x = row.x0;
8869f464c52Smaya            for (i=0; i < row.num_chars; ++i) {
8879f464c52Smaya               float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
8889f464c52Smaya               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
8899f464c52Smaya               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
8909f464c52Smaya                  break;
8919f464c52Smaya               #endif
8929f464c52Smaya               x += dx;
8939f464c52Smaya               if (x > goal_x)
8949f464c52Smaya                  break;
8959f464c52Smaya               ++state->cursor;
8969f464c52Smaya            }
8979f464c52Smaya            stb_textedit_clamp(str, state);
8989f464c52Smaya
8999f464c52Smaya            state->has_preferred_x = 1;
9009f464c52Smaya            state->preferred_x = goal_x;
9019f464c52Smaya
9029f464c52Smaya            if (sel)
9039f464c52Smaya               state->select_end = state->cursor;
9049f464c52Smaya         }
9059f464c52Smaya         break;
9069f464c52Smaya      }
9079f464c52Smaya
9089f464c52Smaya      case STB_TEXTEDIT_K_UP:
9099f464c52Smaya      case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
9109f464c52Smaya         StbFindState find;
9119f464c52Smaya         StbTexteditRow row;
9129f464c52Smaya         int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
9139f464c52Smaya
9149f464c52Smaya         if (state->single_line) {
9159f464c52Smaya            // on windows, up&down become left&right
9169f464c52Smaya            key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
9179f464c52Smaya            goto retry;
9189f464c52Smaya         }
9199f464c52Smaya
9209f464c52Smaya         if (sel)
9219f464c52Smaya            stb_textedit_prep_selection_at_cursor(state);
9229f464c52Smaya         else if (STB_TEXT_HAS_SELECTION(state))
9239f464c52Smaya            stb_textedit_move_to_first(state);
9249f464c52Smaya
9259f464c52Smaya         // compute current position of cursor point
9269f464c52Smaya         stb_textedit_clamp(str, state);
9279f464c52Smaya         stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
9289f464c52Smaya
9299f464c52Smaya         // can only go up if there's a previous row
9309f464c52Smaya         if (find.prev_first != find.first_char) {
9319f464c52Smaya            // now find character position up a row
9329f464c52Smaya            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
9339f464c52Smaya            float x;
9349f464c52Smaya            state->cursor = find.prev_first;
9359f464c52Smaya            STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
9369f464c52Smaya            x = row.x0;
9379f464c52Smaya            for (i=0; i < row.num_chars; ++i) {
9389f464c52Smaya               float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
9399f464c52Smaya               #ifdef STB_TEXTEDIT_GETWIDTH_NEWLINE
9409f464c52Smaya               if (dx == STB_TEXTEDIT_GETWIDTH_NEWLINE)
9419f464c52Smaya                  break;
9429f464c52Smaya               #endif
9439f464c52Smaya               x += dx;
9449f464c52Smaya               if (x > goal_x)
9459f464c52Smaya                  break;
9469f464c52Smaya               ++state->cursor;
9479f464c52Smaya            }
9489f464c52Smaya            stb_textedit_clamp(str, state);
9499f464c52Smaya
9509f464c52Smaya            state->has_preferred_x = 1;
9519f464c52Smaya            state->preferred_x = goal_x;
9529f464c52Smaya
9539f464c52Smaya            if (sel)
9549f464c52Smaya               state->select_end = state->cursor;
9559f464c52Smaya         }
9569f464c52Smaya         break;
9579f464c52Smaya      }
9589f464c52Smaya
9599f464c52Smaya      case STB_TEXTEDIT_K_DELETE:
9609f464c52Smaya      case STB_TEXTEDIT_K_DELETE | STB_TEXTEDIT_K_SHIFT:
9619f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
9629f464c52Smaya            stb_textedit_delete_selection(str, state);
9639f464c52Smaya         else {
9649f464c52Smaya            int n = STB_TEXTEDIT_STRINGLEN(str);
9659f464c52Smaya            if (state->cursor < n)
9669f464c52Smaya               stb_textedit_delete(str, state, state->cursor, 1);
9679f464c52Smaya         }
9689f464c52Smaya         state->has_preferred_x = 0;
9699f464c52Smaya         break;
9709f464c52Smaya
9719f464c52Smaya      case STB_TEXTEDIT_K_BACKSPACE:
9729f464c52Smaya      case STB_TEXTEDIT_K_BACKSPACE | STB_TEXTEDIT_K_SHIFT:
9739f464c52Smaya         if (STB_TEXT_HAS_SELECTION(state))
9749f464c52Smaya            stb_textedit_delete_selection(str, state);
9759f464c52Smaya         else {
9769f464c52Smaya            stb_textedit_clamp(str, state);
9779f464c52Smaya            if (state->cursor > 0) {
9789f464c52Smaya               stb_textedit_delete(str, state, state->cursor-1, 1);
9799f464c52Smaya               --state->cursor;
9809f464c52Smaya            }
9819f464c52Smaya         }
9829f464c52Smaya         state->has_preferred_x = 0;
9839f464c52Smaya         break;
9849f464c52Smaya
9859f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTSTART2
9869f464c52Smaya      case STB_TEXTEDIT_K_TEXTSTART2:
9879f464c52Smaya#endif
9889f464c52Smaya      case STB_TEXTEDIT_K_TEXTSTART:
9899f464c52Smaya         state->cursor = state->select_start = state->select_end = 0;
9909f464c52Smaya         state->has_preferred_x = 0;
9919f464c52Smaya         break;
9929f464c52Smaya
9939f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTEND2
9949f464c52Smaya      case STB_TEXTEDIT_K_TEXTEND2:
9959f464c52Smaya#endif
9969f464c52Smaya      case STB_TEXTEDIT_K_TEXTEND:
9979f464c52Smaya         state->cursor = STB_TEXTEDIT_STRINGLEN(str);
9989f464c52Smaya         state->select_start = state->select_end = 0;
9999f464c52Smaya         state->has_preferred_x = 0;
10009f464c52Smaya         break;
10019f464c52Smaya
10029f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTSTART2
10039f464c52Smaya      case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT:
10049f464c52Smaya#endif
10059f464c52Smaya      case STB_TEXTEDIT_K_TEXTSTART | STB_TEXTEDIT_K_SHIFT:
10069f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
10079f464c52Smaya         state->cursor = state->select_end = 0;
10089f464c52Smaya         state->has_preferred_x = 0;
10099f464c52Smaya         break;
10109f464c52Smaya
10119f464c52Smaya#ifdef STB_TEXTEDIT_K_TEXTEND2
10129f464c52Smaya      case STB_TEXTEDIT_K_TEXTEND2 | STB_TEXTEDIT_K_SHIFT:
10139f464c52Smaya#endif
10149f464c52Smaya      case STB_TEXTEDIT_K_TEXTEND | STB_TEXTEDIT_K_SHIFT:
10159f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
10169f464c52Smaya         state->cursor = state->select_end = STB_TEXTEDIT_STRINGLEN(str);
10179f464c52Smaya         state->has_preferred_x = 0;
10189f464c52Smaya         break;
10199f464c52Smaya
10209f464c52Smaya
10219f464c52Smaya#ifdef STB_TEXTEDIT_K_LINESTART2
10229f464c52Smaya      case STB_TEXTEDIT_K_LINESTART2:
10239f464c52Smaya#endif
10249f464c52Smaya      case STB_TEXTEDIT_K_LINESTART:
10259f464c52Smaya         stb_textedit_clamp(str, state);
10269f464c52Smaya         stb_textedit_move_to_first(state);
10279f464c52Smaya         if (state->single_line)
10289f464c52Smaya            state->cursor = 0;
10299f464c52Smaya         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
10309f464c52Smaya            --state->cursor;
10319f464c52Smaya         state->has_preferred_x = 0;
10329f464c52Smaya         break;
10339f464c52Smaya
10349f464c52Smaya#ifdef STB_TEXTEDIT_K_LINEEND2
10359f464c52Smaya      case STB_TEXTEDIT_K_LINEEND2:
10369f464c52Smaya#endif
10379f464c52Smaya      case STB_TEXTEDIT_K_LINEEND: {
10389f464c52Smaya         int n = STB_TEXTEDIT_STRINGLEN(str);
10399f464c52Smaya         stb_textedit_clamp(str, state);
10409f464c52Smaya         stb_textedit_move_to_first(state);
10419f464c52Smaya         if (state->single_line)
10429f464c52Smaya             state->cursor = n;
10439f464c52Smaya         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
10449f464c52Smaya             ++state->cursor;
10459f464c52Smaya         state->has_preferred_x = 0;
10469f464c52Smaya         break;
10479f464c52Smaya      }
10489f464c52Smaya
10499f464c52Smaya#ifdef STB_TEXTEDIT_K_LINESTART2
10509f464c52Smaya      case STB_TEXTEDIT_K_LINESTART2 | STB_TEXTEDIT_K_SHIFT:
10519f464c52Smaya#endif
10529f464c52Smaya      case STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT:
10539f464c52Smaya         stb_textedit_clamp(str, state);
10549f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
10559f464c52Smaya         if (state->single_line)
10569f464c52Smaya            state->cursor = 0;
10579f464c52Smaya         else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
10589f464c52Smaya            --state->cursor;
10599f464c52Smaya         state->select_end = state->cursor;
10609f464c52Smaya         state->has_preferred_x = 0;
10619f464c52Smaya         break;
10629f464c52Smaya
10639f464c52Smaya#ifdef STB_TEXTEDIT_K_LINEEND2
10649f464c52Smaya      case STB_TEXTEDIT_K_LINEEND2 | STB_TEXTEDIT_K_SHIFT:
10659f464c52Smaya#endif
10669f464c52Smaya      case STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT: {
10679f464c52Smaya         int n = STB_TEXTEDIT_STRINGLEN(str);
10689f464c52Smaya         stb_textedit_clamp(str, state);
10699f464c52Smaya         stb_textedit_prep_selection_at_cursor(state);
10709f464c52Smaya         if (state->single_line)
10719f464c52Smaya             state->cursor = n;
10729f464c52Smaya         else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
10739f464c52Smaya            ++state->cursor;
10749f464c52Smaya         state->select_end = state->cursor;
10759f464c52Smaya         state->has_preferred_x = 0;
10769f464c52Smaya         break;
10779f464c52Smaya      }
10789f464c52Smaya
10799f464c52Smaya// @TODO:
10809f464c52Smaya//    STB_TEXTEDIT_K_PGUP      - move cursor up a page
10819f464c52Smaya//    STB_TEXTEDIT_K_PGDOWN    - move cursor down a page
10829f464c52Smaya   }
10839f464c52Smaya}
10849f464c52Smaya
10859f464c52Smaya/////////////////////////////////////////////////////////////////////////////
10869f464c52Smaya//
10879f464c52Smaya//      Undo processing
10889f464c52Smaya//
10899f464c52Smaya// @OPTIMIZE: the undo/redo buffer should be circular
10909f464c52Smaya
10919f464c52Smayastatic void stb_textedit_flush_redo(StbUndoState *state)
10929f464c52Smaya{
10939f464c52Smaya   state->redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
10949f464c52Smaya   state->redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
10959f464c52Smaya}
10969f464c52Smaya
10979f464c52Smaya// discard the oldest entry in the undo list
10989f464c52Smayastatic void stb_textedit_discard_undo(StbUndoState *state)
10999f464c52Smaya{
11009f464c52Smaya   if (state->undo_point > 0) {
11019f464c52Smaya      // if the 0th undo state has characters, clean those up
11029f464c52Smaya      if (state->undo_rec[0].char_storage >= 0) {
11039f464c52Smaya         int n = state->undo_rec[0].insert_length, i;
11049f464c52Smaya         // delete n characters from all other records
11059f464c52Smaya         state->undo_char_point -= n;
11069f464c52Smaya         STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE)));
11079f464c52Smaya         for (i=0; i < state->undo_point; ++i)
11089f464c52Smaya            if (state->undo_rec[i].char_storage >= 0)
11099f464c52Smaya               state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it
11109f464c52Smaya      }
11119f464c52Smaya      --state->undo_point;
11129f464c52Smaya      STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0])));
11139f464c52Smaya   }
11149f464c52Smaya}
11159f464c52Smaya
11169f464c52Smaya// discard the oldest entry in the redo list--it's bad if this
11179f464c52Smaya// ever happens, but because undo & redo have to store the actual
11189f464c52Smaya// characters in different cases, the redo character buffer can
11199f464c52Smaya// fill up even though the undo buffer didn't
11209f464c52Smayastatic void stb_textedit_discard_redo(StbUndoState *state)
11219f464c52Smaya{
11229f464c52Smaya   int k = STB_TEXTEDIT_UNDOSTATECOUNT-1;
11239f464c52Smaya
11249f464c52Smaya   if (state->redo_point <= k) {
11259f464c52Smaya      // if the k'th undo state has characters, clean those up
11269f464c52Smaya      if (state->undo_rec[k].char_storage >= 0) {
11279f464c52Smaya         int n = state->undo_rec[k].insert_length, i;
11289f464c52Smaya         // move the remaining redo character data to the end of the buffer
11299f464c52Smaya         state->redo_char_point += n;
11309f464c52Smaya         STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE)));
11319f464c52Smaya         // adjust the position of all the other records to account for above memmove
11329f464c52Smaya         for (i=state->redo_point; i < k; ++i)
11339f464c52Smaya            if (state->undo_rec[i].char_storage >= 0)
11349f464c52Smaya               state->undo_rec[i].char_storage += n;
11359f464c52Smaya      }
11369f464c52Smaya      // now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
11379f464c52Smaya      // {DEAR IMGUI]
11389f464c52Smaya      size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
11399f464c52Smaya      const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;
11409f464c52Smaya      const char* buf_end   = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;
11419f464c52Smaya      IM_ASSERT(((char*)(state->undo_rec + state->redo_point)) >= buf_begin);
11429f464c52Smaya      IM_ASSERT(((char*)(state->undo_rec + state->redo_point + 1) + move_size) <= buf_end);
11439f464c52Smaya      STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, move_size);
11449f464c52Smaya
11459f464c52Smaya      // now move redo_point to point to the new one
11469f464c52Smaya      ++state->redo_point;
11479f464c52Smaya   }
11489f464c52Smaya}
11499f464c52Smaya
11509f464c52Smayastatic StbUndoRecord *stb_text_create_undo_record(StbUndoState *state, int numchars)
11519f464c52Smaya{
11529f464c52Smaya   // any time we create a new undo record, we discard redo
11539f464c52Smaya   stb_textedit_flush_redo(state);
11549f464c52Smaya
11559f464c52Smaya   // if we have no free records, we have to make room, by sliding the
11569f464c52Smaya   // existing records down
11579f464c52Smaya   if (state->undo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
11589f464c52Smaya      stb_textedit_discard_undo(state);
11599f464c52Smaya
11609f464c52Smaya   // if the characters to store won't possibly fit in the buffer, we can't undo
11619f464c52Smaya   if (numchars > STB_TEXTEDIT_UNDOCHARCOUNT) {
11629f464c52Smaya      state->undo_point = 0;
11639f464c52Smaya      state->undo_char_point = 0;
11649f464c52Smaya      return NULL;
11659f464c52Smaya   }
11669f464c52Smaya
11679f464c52Smaya   // if we don't have enough free characters in the buffer, we have to make room
11689f464c52Smaya   while (state->undo_char_point + numchars > STB_TEXTEDIT_UNDOCHARCOUNT)
11699f464c52Smaya      stb_textedit_discard_undo(state);
11709f464c52Smaya
11719f464c52Smaya   return &state->undo_rec[state->undo_point++];
11729f464c52Smaya}
11739f464c52Smaya
11749f464c52Smayastatic STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, int insert_len, int delete_len)
11759f464c52Smaya{
11769f464c52Smaya   StbUndoRecord *r = stb_text_create_undo_record(state, insert_len);
11779f464c52Smaya   if (r == NULL)
11789f464c52Smaya      return NULL;
11799f464c52Smaya
11809f464c52Smaya   r->where = pos;
11819f464c52Smaya   r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len;
11829f464c52Smaya   r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len;
11839f464c52Smaya
11849f464c52Smaya   if (insert_len == 0) {
11859f464c52Smaya      r->char_storage = -1;
11869f464c52Smaya      return NULL;
11879f464c52Smaya   } else {
11889f464c52Smaya      r->char_storage = state->undo_char_point;
11899f464c52Smaya      state->undo_char_point += insert_len;
11909f464c52Smaya      return &state->undo_char[r->char_storage];
11919f464c52Smaya   }
11929f464c52Smaya}
11939f464c52Smaya
11949f464c52Smayastatic void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
11959f464c52Smaya{
11969f464c52Smaya   StbUndoState *s = &state->undostate;
11979f464c52Smaya   StbUndoRecord u, *r;
11989f464c52Smaya   if (s->undo_point == 0)
11999f464c52Smaya      return;
12009f464c52Smaya
12019f464c52Smaya   // we need to do two things: apply the undo record, and create a redo record
12029f464c52Smaya   u = s->undo_rec[s->undo_point-1];
12039f464c52Smaya   r = &s->undo_rec[s->redo_point-1];
12049f464c52Smaya   r->char_storage = -1;
12059f464c52Smaya
12069f464c52Smaya   r->insert_length = u.delete_length;
12079f464c52Smaya   r->delete_length = u.insert_length;
12089f464c52Smaya   r->where = u.where;
12099f464c52Smaya
12109f464c52Smaya   if (u.delete_length) {
12119f464c52Smaya      // if the undo record says to delete characters, then the redo record will
12129f464c52Smaya      // need to re-insert the characters that get deleted, so we need to store
12139f464c52Smaya      // them.
12149f464c52Smaya
12159f464c52Smaya      // there are three cases:
12169f464c52Smaya      //    there's enough room to store the characters
12179f464c52Smaya      //    characters stored for *redoing* don't leave room for redo
12189f464c52Smaya      //    characters stored for *undoing* don't leave room for redo
12199f464c52Smaya      // if the last is true, we have to bail
12209f464c52Smaya
12219f464c52Smaya      if (s->undo_char_point + u.delete_length >= STB_TEXTEDIT_UNDOCHARCOUNT) {
12229f464c52Smaya         // the undo records take up too much character space; there's no space to store the redo characters
12239f464c52Smaya         r->insert_length = 0;
12249f464c52Smaya      } else {
12259f464c52Smaya         int i;
12269f464c52Smaya
12279f464c52Smaya         // there's definitely room to store the characters eventually
12289f464c52Smaya         while (s->undo_char_point + u.delete_length > s->redo_char_point) {
12299f464c52Smaya            // should never happen:
12309f464c52Smaya            if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
12319f464c52Smaya               return;
12329f464c52Smaya            // there's currently not enough room, so discard a redo record
12339f464c52Smaya            stb_textedit_discard_redo(s);
12349f464c52Smaya         }
12359f464c52Smaya         r = &s->undo_rec[s->redo_point-1];
12369f464c52Smaya
12379f464c52Smaya         r->char_storage = s->redo_char_point - u.delete_length;
12389f464c52Smaya         s->redo_char_point = s->redo_char_point - u.delete_length;
12399f464c52Smaya
12409f464c52Smaya         // now save the characters
12419f464c52Smaya         for (i=0; i < u.delete_length; ++i)
12429f464c52Smaya            s->undo_char[r->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u.where + i);
12439f464c52Smaya      }
12449f464c52Smaya
12459f464c52Smaya      // now we can carry out the deletion
12469f464c52Smaya      STB_TEXTEDIT_DELETECHARS(str, u.where, u.delete_length);
12479f464c52Smaya   }
12489f464c52Smaya
12499f464c52Smaya   // check type of recorded action:
12509f464c52Smaya   if (u.insert_length) {
12519f464c52Smaya      // easy case: was a deletion, so we need to insert n characters
12529f464c52Smaya      STB_TEXTEDIT_INSERTCHARS(str, u.where, &s->undo_char[u.char_storage], u.insert_length);
12539f464c52Smaya      s->undo_char_point -= u.insert_length;
12549f464c52Smaya   }
12559f464c52Smaya
12569f464c52Smaya   state->cursor = u.where + u.insert_length;
12579f464c52Smaya
12589f464c52Smaya   s->undo_point--;
12599f464c52Smaya   s->redo_point--;
12609f464c52Smaya}
12619f464c52Smaya
12629f464c52Smayastatic void stb_text_redo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
12639f464c52Smaya{
12649f464c52Smaya   StbUndoState *s = &state->undostate;
12659f464c52Smaya   StbUndoRecord *u, r;
12669f464c52Smaya   if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT)
12679f464c52Smaya      return;
12689f464c52Smaya
12699f464c52Smaya   // we need to do two things: apply the redo record, and create an undo record
12709f464c52Smaya   u = &s->undo_rec[s->undo_point];
12719f464c52Smaya   r = s->undo_rec[s->redo_point];
12729f464c52Smaya
12739f464c52Smaya   // we KNOW there must be room for the undo record, because the redo record
12749f464c52Smaya   // was derived from an undo record
12759f464c52Smaya
12769f464c52Smaya   u->delete_length = r.insert_length;
12779f464c52Smaya   u->insert_length = r.delete_length;
12789f464c52Smaya   u->where = r.where;
12799f464c52Smaya   u->char_storage = -1;
12809f464c52Smaya
12819f464c52Smaya   if (r.delete_length) {
12829f464c52Smaya      // the redo record requires us to delete characters, so the undo record
12839f464c52Smaya      // needs to store the characters
12849f464c52Smaya
12859f464c52Smaya      if (s->undo_char_point + u->insert_length > s->redo_char_point) {
12869f464c52Smaya         u->insert_length = 0;
12879f464c52Smaya         u->delete_length = 0;
12889f464c52Smaya      } else {
12899f464c52Smaya         int i;
12909f464c52Smaya         u->char_storage = s->undo_char_point;
12919f464c52Smaya         s->undo_char_point = s->undo_char_point + u->insert_length;
12929f464c52Smaya
12939f464c52Smaya         // now save the characters
12949f464c52Smaya         for (i=0; i < u->insert_length; ++i)
12959f464c52Smaya            s->undo_char[u->char_storage + i] = STB_TEXTEDIT_GETCHAR(str, u->where + i);
12969f464c52Smaya      }
12979f464c52Smaya
12989f464c52Smaya      STB_TEXTEDIT_DELETECHARS(str, r.where, r.delete_length);
12999f464c52Smaya   }
13009f464c52Smaya
13019f464c52Smaya   if (r.insert_length) {
13029f464c52Smaya      // easy case: need to insert n characters
13039f464c52Smaya      STB_TEXTEDIT_INSERTCHARS(str, r.where, &s->undo_char[r.char_storage], r.insert_length);
13049f464c52Smaya      s->redo_char_point += r.insert_length;
13059f464c52Smaya   }
13069f464c52Smaya
13079f464c52Smaya   state->cursor = r.where + r.insert_length;
13089f464c52Smaya
13099f464c52Smaya   s->undo_point++;
13109f464c52Smaya   s->redo_point++;
13119f464c52Smaya}
13129f464c52Smaya
13139f464c52Smayastatic void stb_text_makeundo_insert(STB_TexteditState *state, int where, int length)
13149f464c52Smaya{
13159f464c52Smaya   stb_text_createundo(&state->undostate, where, 0, length);
13169f464c52Smaya}
13179f464c52Smaya
13189f464c52Smayastatic void stb_text_makeundo_delete(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int length)
13199f464c52Smaya{
13209f464c52Smaya   int i;
13219f464c52Smaya   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, length, 0);
13229f464c52Smaya   if (p) {
13239f464c52Smaya      for (i=0; i < length; ++i)
13249f464c52Smaya         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
13259f464c52Smaya   }
13269f464c52Smaya}
13279f464c52Smaya
13289f464c52Smayastatic void stb_text_makeundo_replace(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int where, int old_length, int new_length)
13299f464c52Smaya{
13309f464c52Smaya   int i;
13319f464c52Smaya   STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->undostate, where, old_length, new_length);
13329f464c52Smaya   if (p) {
13339f464c52Smaya      for (i=0; i < old_length; ++i)
13349f464c52Smaya         p[i] = STB_TEXTEDIT_GETCHAR(str, where+i);
13359f464c52Smaya   }
13369f464c52Smaya}
13379f464c52Smaya
13389f464c52Smaya// reset the state to default
13399f464c52Smayastatic void stb_textedit_clear_state(STB_TexteditState *state, int is_single_line)
13409f464c52Smaya{
13419f464c52Smaya   state->undostate.undo_point = 0;
13429f464c52Smaya   state->undostate.undo_char_point = 0;
13439f464c52Smaya   state->undostate.redo_point = STB_TEXTEDIT_UNDOSTATECOUNT;
13449f464c52Smaya   state->undostate.redo_char_point = STB_TEXTEDIT_UNDOCHARCOUNT;
13459f464c52Smaya   state->select_end = state->select_start = 0;
13469f464c52Smaya   state->cursor = 0;
13479f464c52Smaya   state->has_preferred_x = 0;
13489f464c52Smaya   state->preferred_x = 0;
13499f464c52Smaya   state->cursor_at_end_of_line = 0;
13509f464c52Smaya   state->initialized = 1;
13519f464c52Smaya   state->single_line = (unsigned char) is_single_line;
13529f464c52Smaya   state->insert_mode = 0;
13539f464c52Smaya}
13549f464c52Smaya
13559f464c52Smaya// API initialize
13569f464c52Smayastatic void stb_textedit_initialize_state(STB_TexteditState *state, int is_single_line)
13579f464c52Smaya{
13589f464c52Smaya   stb_textedit_clear_state(state, is_single_line);
13599f464c52Smaya}
13609f464c52Smaya
13619f464c52Smaya#if defined(__GNUC__) || defined(__clang__)
13629f464c52Smaya#pragma GCC diagnostic push
13639f464c52Smaya#pragma GCC diagnostic ignored "-Wcast-qual"
13649f464c52Smaya#endif
13659f464c52Smaya
13669f464c52Smayastatic int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE const *ctext, int len)
13679f464c52Smaya{
13689f464c52Smaya   return stb_textedit_paste_internal(str, state, (STB_TEXTEDIT_CHARTYPE *) ctext, len);
13699f464c52Smaya}
13709f464c52Smaya
13719f464c52Smaya#if defined(__GNUC__) || defined(__clang__)
13729f464c52Smaya#pragma GCC diagnostic pop
13739f464c52Smaya#endif
13749f464c52Smaya
13759f464c52Smaya#endif//STB_TEXTEDIT_IMPLEMENTATION
13769f464c52Smaya
13779f464c52Smaya/*
13789f464c52Smaya------------------------------------------------------------------------------
13799f464c52SmayaThis software is available under 2 licenses -- choose whichever you prefer.
13809f464c52Smaya------------------------------------------------------------------------------
13819f464c52SmayaALTERNATIVE A - MIT License
13829f464c52SmayaCopyright (c) 2017 Sean Barrett
13839f464c52SmayaPermission is hereby granted, free of charge, to any person obtaining a copy of
13849f464c52Smayathis software and associated documentation files (the "Software"), to deal in
13859f464c52Smayathe Software without restriction, including without limitation the rights to
13869f464c52Smayause, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13879f464c52Smayaof the Software, and to permit persons to whom the Software is furnished to do
13889f464c52Smayaso, subject to the following conditions:
13899f464c52SmayaThe above copyright notice and this permission notice shall be included in all
13909f464c52Smayacopies or substantial portions of the Software.
13919f464c52SmayaTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13929f464c52SmayaIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13939f464c52SmayaFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13949f464c52SmayaAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
13959f464c52SmayaLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
13969f464c52SmayaOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
13979f464c52SmayaSOFTWARE.
13989f464c52Smaya------------------------------------------------------------------------------
13999f464c52SmayaALTERNATIVE B - Public Domain (www.unlicense.org)
14009f464c52SmayaThis is free and unencumbered software released into the public domain.
14019f464c52SmayaAnyone is free to copy, modify, publish, use, compile, sell, or distribute this
14029f464c52Smayasoftware, either in source code form or as a compiled binary, for any purpose,
14039f464c52Smayacommercial or non-commercial, and by any means.
14049f464c52SmayaIn jurisdictions that recognize copyright laws, the author or authors of this
14059f464c52Smayasoftware dedicate any and all copyright interest in the software to the public
14069f464c52Smayadomain. We make this dedication for the benefit of the public at large and to
14079f464c52Smayathe detriment of our heirs and successors. We intend this dedication to be an
14089f464c52Smayaovert act of relinquishment in perpetuity of all present and future rights to
14099f464c52Smayathis software under copyright law.
14109f464c52SmayaTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14119f464c52SmayaIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14129f464c52SmayaFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14139f464c52SmayaAUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
14149f464c52SmayaACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
14159f464c52SmayaWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14169f464c52Smaya------------------------------------------------------------------------------
14179f464c52Smaya*/
1418