cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

imstb_textedit.h (53443B)


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