Blame src-nuklear/nuklear_text_editor.c

Ivan Mahonin b53a5c
#include "nuklear.h"
Ivan Mahonin b53a5c
#include "nuklear_internal.h"
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
/* ===============================================================
Ivan Mahonin b53a5c
 *
Ivan Mahonin b53a5c
 *                          TEXT EDITOR
Ivan Mahonin b53a5c
 *
Ivan Mahonin b53a5c
 * ===============================================================*/
Ivan Mahonin b53a5c
/* stb_textedit.h - v1.8  - public domain - Sean Barrett */
Ivan Mahonin b53a5c
struct nk_text_find {
Ivan Mahonin b53a5c
   float x,y;    /* position of n'th character */
Ivan Mahonin b53a5c
   float height; /* height of line */
Ivan Mahonin b53a5c
   int first_char, length; /* first char of row, and length */
Ivan Mahonin b53a5c
   int prev_first;  /*_ first char of previous row */
Ivan Mahonin b53a5c
};
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
struct nk_text_edit_row {
Ivan Mahonin b53a5c
   float x0,x1;
Ivan Mahonin b53a5c
   /* starting x location, end x location (allows for align=right, etc) */
Ivan Mahonin b53a5c
   float baseline_y_delta;
Ivan Mahonin b53a5c
   /* position of baseline relative to previous row's baseline*/
Ivan Mahonin b53a5c
   float ymin,ymax;
Ivan Mahonin b53a5c
   /* height of row above and below baseline */
Ivan Mahonin b53a5c
   int num_chars;
Ivan Mahonin b53a5c
};
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
/* forward declarations */
Ivan Mahonin b53a5c
NK_INTERN void nk_textedit_makeundo_delete(struct nk_text_edit*, int, int);
Ivan Mahonin b53a5c
NK_INTERN void nk_textedit_makeundo_insert(struct nk_text_edit*, int, int);
Ivan Mahonin b53a5c
NK_INTERN void nk_textedit_makeundo_replace(struct nk_text_edit*, int, int, int);
Ivan Mahonin b53a5c
#define NK_TEXT_HAS_SELECTION(s)   ((s)->select_start != (s)->select_end)
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
NK_INTERN float
Ivan Mahonin b53a5c
nk_textedit_get_width(const struct nk_text_edit *edit, int line_start, int char_id,
Ivan Mahonin b53a5c
    const struct nk_user_font *font)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    int len = 0;
Ivan Mahonin b53a5c
    nk_rune unicode = 0;
Ivan Mahonin b53a5c
    const char *str = nk_str_at_const(&edit->string, line_start + char_id, &unicode, &len);
Ivan Mahonin b53a5c
    return font->width(font->userdata, font->height, str, len);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_layout_row(struct nk_text_edit_row *r, struct nk_text_edit *edit,
Ivan Mahonin b53a5c
    int line_start_id, float row_height, const struct nk_user_font *font)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    int l;
Ivan Mahonin b53a5c
    int glyphs = 0;
Ivan Mahonin b53a5c
    nk_rune unicode;
Ivan Mahonin b53a5c
    const char *remaining;
Ivan Mahonin b53a5c
    int len = nk_str_len_char(&edit->string);
Ivan Mahonin b53a5c
    const char *end = nk_str_get_const(&edit->string) + len;
Ivan Mahonin b53a5c
    const char *text = nk_str_at_const(&edit->string, line_start_id, &unicode, &l);
Ivan Mahonin b53a5c
    const struct nk_vec2 size = nk_text_calculate_text_bounds(font,
Ivan Mahonin b53a5c
        text, (int)(end - text), row_height, &remaining, 0, &glyphs, NK_STOP_ON_NEW_LINE);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    r->x0 = 0.0f;
Ivan Mahonin b53a5c
    r->x1 = size.x;
Ivan Mahonin b53a5c
    r->baseline_y_delta = size.y;
Ivan Mahonin b53a5c
    r->ymin = 0.0f;
Ivan Mahonin b53a5c
    r->ymax = size.y;
Ivan Mahonin b53a5c
    r->num_chars = glyphs;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN int
Ivan Mahonin b53a5c
nk_textedit_locate_coord(struct nk_text_edit *edit, float x, float y,
Ivan Mahonin b53a5c
    const struct nk_user_font *font, float row_height)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    struct nk_text_edit_row r;
Ivan Mahonin b53a5c
    int n = edit->string.len;
Ivan Mahonin b53a5c
    float base_y = 0, prev_x;
Ivan Mahonin b53a5c
    int i=0, k;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    r.x0 = r.x1 = 0;
Ivan Mahonin b53a5c
    r.ymin = r.ymax = 0;
Ivan Mahonin b53a5c
    r.num_chars = 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* search rows to find one that straddles 'y' */
Ivan Mahonin b53a5c
    while (i < n) {
Ivan Mahonin b53a5c
        nk_textedit_layout_row(&r, edit, i, row_height, font);
Ivan Mahonin b53a5c
        if (r.num_chars <= 0)
Ivan Mahonin b53a5c
            return n;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (i==0 && y < base_y + r.ymin)
Ivan Mahonin b53a5c
            return 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (y < base_y + r.ymax)
Ivan Mahonin b53a5c
            break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        i += r.num_chars;
Ivan Mahonin b53a5c
        base_y += r.baseline_y_delta;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* below all text, return 'after' last character */
Ivan Mahonin b53a5c
    if (i >= n)
Ivan Mahonin b53a5c
        return n;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* check if it's before the beginning of the line */
Ivan Mahonin b53a5c
    if (x < r.x0)
Ivan Mahonin b53a5c
        return i;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* check if it's before the end of the line */
Ivan Mahonin b53a5c
    if (x < r.x1) {
Ivan Mahonin b53a5c
        /* search characters in row for one that straddles 'x' */
Ivan Mahonin b53a5c
        k = i;
Ivan Mahonin b53a5c
        prev_x = r.x0;
Ivan Mahonin b53a5c
        for (i=0; i < r.num_chars; ++i) {
Ivan Mahonin b53a5c
            float w = nk_textedit_get_width(edit, k, i, font);
Ivan Mahonin b53a5c
            if (x < prev_x+w) {
Ivan Mahonin b53a5c
                if (x < prev_x+w/2)
Ivan Mahonin b53a5c
                    return k+i;
Ivan Mahonin b53a5c
                else return k+i+1;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
            prev_x += w;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        /* shouldn't happen, but if it does, fall through to end-of-line case */
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* if the last character is a newline, return that.
Ivan Mahonin b53a5c
     * otherwise return 'after' the last character */
Ivan Mahonin b53a5c
    if (nk_str_rune_at(&edit->string, i+r.num_chars-1) == '\n')
Ivan Mahonin b53a5c
        return i+r.num_chars-1;
Ivan Mahonin b53a5c
    else return i+r.num_chars;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_LIB void
Ivan Mahonin b53a5c
nk_textedit_click(struct nk_text_edit *state, float x, float y,
Ivan Mahonin b53a5c
    const struct nk_user_font *font, float row_height)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* API click: on mouse down, move the cursor to the clicked location,
Ivan Mahonin b53a5c
     * and reset the selection */
Ivan Mahonin b53a5c
    state->cursor = nk_textedit_locate_coord(state, x, y, font, row_height);
Ivan Mahonin b53a5c
    state->select_start = state->cursor;
Ivan Mahonin b53a5c
    state->select_end = state->cursor;
Ivan Mahonin b53a5c
    state->has_preferred_x = 0;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_LIB void
Ivan Mahonin b53a5c
nk_textedit_drag(struct nk_text_edit *state, float x, float y,
Ivan Mahonin b53a5c
    const struct nk_user_font *font, float row_height)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* API drag: on mouse drag, move the cursor and selection endpoint
Ivan Mahonin b53a5c
     * to the clicked location */
Ivan Mahonin b53a5c
    int p = nk_textedit_locate_coord(state, x, y, font, row_height);
Ivan Mahonin b53a5c
    if (state->select_start == state->select_end)
Ivan Mahonin b53a5c
        state->select_start = state->cursor;
Ivan Mahonin b53a5c
    state->cursor = state->select_end = p;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_find_charpos(struct nk_text_find *find, struct nk_text_edit *state,
Ivan Mahonin b53a5c
    int n, int single_line, const struct nk_user_font *font, float row_height)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* find the x/y location of a character, and remember info about the previous
Ivan Mahonin b53a5c
     * row in case we get a move-up event (for page up, we'll have to rescan) */
Ivan Mahonin b53a5c
    struct nk_text_edit_row r;
Ivan Mahonin b53a5c
    int prev_start = 0;
Ivan Mahonin b53a5c
    int z = state->string.len;
Ivan Mahonin b53a5c
    int i=0, first;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    nk_zero_struct(r);
Ivan Mahonin b53a5c
    if (n == z) {
Ivan Mahonin b53a5c
        /* if it's at the end, then find the last line -- simpler than trying to
Ivan Mahonin b53a5c
        explicitly handle this case in the regular code */
Ivan Mahonin b53a5c
        nk_textedit_layout_row(&r, state, 0, row_height, font);
Ivan Mahonin b53a5c
        if (single_line) {
Ivan Mahonin b53a5c
            find->first_char = 0;
Ivan Mahonin b53a5c
            find->length = z;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            while (i < z) {
Ivan Mahonin b53a5c
                prev_start = i;
Ivan Mahonin b53a5c
                i += r.num_chars;
Ivan Mahonin b53a5c
                nk_textedit_layout_row(&r, state, i, row_height, font);
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            find->first_char = i;
Ivan Mahonin b53a5c
            find->length = r.num_chars;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        find->x = r.x1;
Ivan Mahonin b53a5c
        find->y = r.ymin;
Ivan Mahonin b53a5c
        find->height = r.ymax - r.ymin;
Ivan Mahonin b53a5c
        find->prev_first = prev_start;
Ivan Mahonin b53a5c
        return;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* search rows to find the one that straddles character n */
Ivan Mahonin b53a5c
    find->y = 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    for(;;) {
Ivan Mahonin b53a5c
        nk_textedit_layout_row(&r, state, i, row_height, font);
Ivan Mahonin b53a5c
        if (n < i + r.num_chars) break;
Ivan Mahonin b53a5c
        prev_start = i;
Ivan Mahonin b53a5c
        i += r.num_chars;
Ivan Mahonin b53a5c
        find->y += r.baseline_y_delta;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    find->first_char = first = i;
Ivan Mahonin b53a5c
    find->length = r.num_chars;
Ivan Mahonin b53a5c
    find->height = r.ymax - r.ymin;
Ivan Mahonin b53a5c
    find->prev_first = prev_start;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* now scan to find xpos */
Ivan Mahonin b53a5c
    find->x = r.x0;
Ivan Mahonin b53a5c
    for (i=0; first+i < n; ++i)
Ivan Mahonin b53a5c
        find->x += nk_textedit_get_width(state, first, i, font);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_clamp(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* make the selection/cursor state valid if client altered the string */
Ivan Mahonin b53a5c
    int n = state->string.len;
Ivan Mahonin b53a5c
    if (NK_TEXT_HAS_SELECTION(state)) {
Ivan Mahonin b53a5c
        if (state->select_start > n) state->select_start = n;
Ivan Mahonin b53a5c
        if (state->select_end   > n) state->select_end = n;
Ivan Mahonin b53a5c
        /* if clamping forced them to be equal, move the cursor to match */
Ivan Mahonin b53a5c
        if (state->select_start == state->select_end)
Ivan Mahonin b53a5c
            state->cursor = state->select_start;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
    if (state->cursor > n) state->cursor = n;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_delete(struct nk_text_edit *state, int where, int len)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* delete characters while updating undo */
Ivan Mahonin b53a5c
    nk_textedit_makeundo_delete(state, where, len);
Ivan Mahonin b53a5c
    nk_str_delete_runes(&state->string, where, len);
Ivan Mahonin b53a5c
    state->has_preferred_x = 0;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_delete_selection(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* delete the section */
Ivan Mahonin b53a5c
    nk_textedit_clamp(state);
Ivan Mahonin b53a5c
    if (NK_TEXT_HAS_SELECTION(state)) {
Ivan Mahonin b53a5c
        if (state->select_start < state->select_end) {
Ivan Mahonin b53a5c
            nk_textedit_delete(state, state->select_start,
Ivan Mahonin b53a5c
                state->select_end - state->select_start);
Ivan Mahonin b53a5c
            state->select_end = state->cursor = state->select_start;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            nk_textedit_delete(state, state->select_end,
Ivan Mahonin b53a5c
                state->select_start - state->select_end);
Ivan Mahonin b53a5c
            state->select_start = state->cursor = state->select_end;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_sortselection(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* canonicalize the selection so start <= end */
Ivan Mahonin b53a5c
    if (state->select_end < state->select_start) {
Ivan Mahonin b53a5c
        int temp = state->select_end;
Ivan Mahonin b53a5c
        state->select_end = state->select_start;
Ivan Mahonin b53a5c
        state->select_start = temp;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_move_to_first(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* move cursor to first character of selection */
Ivan Mahonin b53a5c
    if (NK_TEXT_HAS_SELECTION(state)) {
Ivan Mahonin b53a5c
        nk_textedit_sortselection(state);
Ivan Mahonin b53a5c
        state->cursor = state->select_start;
Ivan Mahonin b53a5c
        state->select_end = state->select_start;
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_move_to_last(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* move cursor to last character of selection */
Ivan Mahonin b53a5c
    if (NK_TEXT_HAS_SELECTION(state)) {
Ivan Mahonin b53a5c
        nk_textedit_sortselection(state);
Ivan Mahonin b53a5c
        nk_textedit_clamp(state);
Ivan Mahonin b53a5c
        state->cursor = state->select_end;
Ivan Mahonin b53a5c
        state->select_start = state->select_end;
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN int
Ivan Mahonin b53a5c
nk_is_word_boundary( struct nk_text_edit *state, int idx)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    int len;
Ivan Mahonin b53a5c
    nk_rune c;
Ivan Mahonin b53a5c
    if (idx <= 0) return 1;
Ivan Mahonin b53a5c
    if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1;
Ivan Mahonin b53a5c
    return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' ||
Ivan Mahonin b53a5c
            c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' ||
Ivan Mahonin b53a5c
            c == '|');
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN int
Ivan Mahonin b53a5c
nk_textedit_move_to_word_previous(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
   int c = state->cursor - 1;
Ivan Mahonin b53a5c
   while( c >= 0 && !nk_is_word_boundary(state, c))
Ivan Mahonin b53a5c
      --c;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
   if( c < 0 )
Ivan Mahonin b53a5c
      c = 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
   return c;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN int
Ivan Mahonin b53a5c
nk_textedit_move_to_word_next(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
   const int len = state->string.len;
Ivan Mahonin b53a5c
   int c = state->cursor+1;
Ivan Mahonin b53a5c
   while( c < len && !nk_is_word_boundary(state, c))
Ivan Mahonin b53a5c
      ++c;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
   if( c > len )
Ivan Mahonin b53a5c
      c = len;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
   return c;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_prep_selection_at_cursor(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* update selection and cursor to match each other */
Ivan Mahonin b53a5c
    if (!NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
        state->select_start = state->select_end = state->cursor;
Ivan Mahonin b53a5c
    else state->cursor = state->select_end;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API nk_bool
Ivan Mahonin b53a5c
nk_textedit_cut(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* API cut: delete selection */
Ivan Mahonin b53a5c
    if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
Ivan Mahonin b53a5c
        return 0;
Ivan Mahonin b53a5c
    if (NK_TEXT_HAS_SELECTION(state)) {
Ivan Mahonin b53a5c
        nk_textedit_delete_selection(state); /* implicitly clamps */
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        return 1;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
   return 0;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API nk_bool
Ivan Mahonin b53a5c
nk_textedit_paste(struct nk_text_edit *state, char const *ctext, int len)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* API paste: replace existing selection with passed-in text */
Ivan Mahonin b53a5c
    int glyphs;
Ivan Mahonin b53a5c
    const char *text = (const char *) ctext;
Ivan Mahonin b53a5c
    if (state->mode == NK_TEXT_EDIT_MODE_VIEW) return 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* if there's a selection, the paste should delete it */
Ivan Mahonin b53a5c
    nk_textedit_clamp(state);
Ivan Mahonin b53a5c
    nk_textedit_delete_selection(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* try to insert the characters */
Ivan Mahonin b53a5c
    glyphs = nk_utf_len(ctext, len);
Ivan Mahonin b53a5c
    if (nk_str_insert_text_char(&state->string, state->cursor, text, len)) {
Ivan Mahonin b53a5c
        nk_textedit_makeundo_insert(state, state->cursor, glyphs);
Ivan Mahonin b53a5c
        state->cursor += len;
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        return 1;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
    /* remove the undo since we didn't actually insert the characters */
Ivan Mahonin b53a5c
    if (state->undo.undo_point)
Ivan Mahonin b53a5c
        --state->undo.undo_point;
Ivan Mahonin b53a5c
    return 0;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_text(struct nk_text_edit *state, const char *text, int total_len)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    nk_rune unicode;
Ivan Mahonin b53a5c
    int glyph_len;
Ivan Mahonin b53a5c
    int text_len = 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    NK_ASSERT(text);
Ivan Mahonin b53a5c
    if (!text || !total_len || state->mode == NK_TEXT_EDIT_MODE_VIEW) return;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    glyph_len = nk_utf_decode(text, &unicode, total_len);
Ivan Mahonin b53a5c
    while ((text_len < total_len) && glyph_len)
Ivan Mahonin b53a5c
    {
Ivan Mahonin b53a5c
        /* don't insert a backward delete, just process the event */
Ivan Mahonin b53a5c
        if (unicode == 127) goto next;
Ivan Mahonin b53a5c
        /* can't add newline in single-line mode */
Ivan Mahonin b53a5c
        if (unicode == '\n' && state->single_line) goto next;
Ivan Mahonin b53a5c
        /* filter incoming text */
Ivan Mahonin b53a5c
        if (state->filter && !state->filter(state, unicode)) goto next;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (!NK_TEXT_HAS_SELECTION(state) &&
Ivan Mahonin b53a5c
            state->cursor < state->string.len)
Ivan Mahonin b53a5c
        {
Ivan Mahonin b53a5c
            if (state->mode == NK_TEXT_EDIT_MODE_REPLACE) {
Ivan Mahonin b53a5c
                nk_textedit_makeundo_replace(state, state->cursor, 1, 1);
Ivan Mahonin b53a5c
                nk_str_delete_runes(&state->string, state->cursor, 1);
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
            if (nk_str_insert_text_utf8(&state->string, state->cursor,
Ivan Mahonin b53a5c
                                        text+text_len, 1))
Ivan Mahonin b53a5c
            {
Ivan Mahonin b53a5c
                ++state->cursor;
Ivan Mahonin b53a5c
                state->has_preferred_x = 0;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            nk_textedit_delete_selection(state); /* implicitly clamps */
Ivan Mahonin b53a5c
            if (nk_str_insert_text_utf8(&state->string, state->cursor,
Ivan Mahonin b53a5c
                                        text+text_len, 1))
Ivan Mahonin b53a5c
            {
Ivan Mahonin b53a5c
                nk_textedit_makeundo_insert(state, state->cursor, 1);
Ivan Mahonin b53a5c
                state->cursor = NK_MIN(state->cursor + 1, state->string.len);
Ivan Mahonin b53a5c
                state->has_preferred_x = 0;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        next:
Ivan Mahonin b53a5c
        text_len += glyph_len;
Ivan Mahonin b53a5c
        glyph_len = nk_utf_decode(text + text_len, &unicode, total_len-text_len);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_LIB void
Ivan Mahonin b53a5c
nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod,
Ivan Mahonin b53a5c
    const struct nk_user_font *font, float row_height)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
retry:
Ivan Mahonin b53a5c
    switch (key)
Ivan Mahonin b53a5c
    {
Ivan Mahonin b53a5c
    case NK_KEY_NONE:
Ivan Mahonin b53a5c
    case NK_KEY_CTRL:
Ivan Mahonin b53a5c
    case NK_KEY_ENTER:
Ivan Mahonin b53a5c
    case NK_KEY_SHIFT:
Ivan Mahonin b53a5c
    case NK_KEY_TAB:
Ivan Mahonin b53a5c
    case NK_KEY_COPY:
Ivan Mahonin b53a5c
    case NK_KEY_CUT:
Ivan Mahonin b53a5c
    case NK_KEY_PASTE:
Ivan Mahonin b53a5c
    case NK_KEY_MAX:
Ivan Mahonin b53a5c
    default: break;
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_UNDO:
Ivan Mahonin b53a5c
         nk_textedit_undo(state);
Ivan Mahonin b53a5c
         state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_REDO:
Ivan Mahonin b53a5c
        nk_textedit_redo(state);
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_SELECT_ALL:
Ivan Mahonin b53a5c
        nk_textedit_select_all(state);
Ivan Mahonin b53a5c
        state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_INSERT_MODE:
Ivan Mahonin b53a5c
        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
Ivan Mahonin b53a5c
            state->mode = NK_TEXT_EDIT_MODE_INSERT;
Ivan Mahonin b53a5c
        break;
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_REPLACE_MODE:
Ivan Mahonin b53a5c
        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
Ivan Mahonin b53a5c
            state->mode = NK_TEXT_EDIT_MODE_REPLACE;
Ivan Mahonin b53a5c
        break;
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_RESET_MODE:
Ivan Mahonin b53a5c
        if (state->mode == NK_TEXT_EDIT_MODE_INSERT ||
Ivan Mahonin b53a5c
            state->mode == NK_TEXT_EDIT_MODE_REPLACE)
Ivan Mahonin b53a5c
            state->mode = NK_TEXT_EDIT_MODE_VIEW;
Ivan Mahonin b53a5c
        break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_LEFT:
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            /* move selection left */
Ivan Mahonin b53a5c
            if (state->select_end > 0)
Ivan Mahonin b53a5c
                --state->select_end;
Ivan Mahonin b53a5c
            state->cursor = state->select_end;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            /* if currently there's a selection,
Ivan Mahonin b53a5c
             * move cursor to start of selection */
Ivan Mahonin b53a5c
            if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
                nk_textedit_move_to_first(state);
Ivan Mahonin b53a5c
            else if (state->cursor > 0)
Ivan Mahonin b53a5c
               --state->cursor;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_RIGHT:
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            /* move selection right */
Ivan Mahonin b53a5c
            ++state->select_end;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            state->cursor = state->select_end;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            /* if currently there's a selection,
Ivan Mahonin b53a5c
             * move cursor to end of selection */
Ivan Mahonin b53a5c
            if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
                nk_textedit_move_to_last(state);
Ivan Mahonin b53a5c
            else ++state->cursor;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_WORD_LEFT:
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            if( !NK_TEXT_HAS_SELECTION( state ) )
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            state->cursor = nk_textedit_move_to_word_previous(state);
Ivan Mahonin b53a5c
            state->select_end = state->cursor;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state );
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
                nk_textedit_move_to_first(state);
Ivan Mahonin b53a5c
            else {
Ivan Mahonin b53a5c
                state->cursor = nk_textedit_move_to_word_previous(state);
Ivan Mahonin b53a5c
                nk_textedit_clamp(state );
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_WORD_RIGHT:
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            if( !NK_TEXT_HAS_SELECTION( state ) )
Ivan Mahonin b53a5c
                nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            state->cursor = nk_textedit_move_to_word_next(state);
Ivan Mahonin b53a5c
            state->select_end = state->cursor;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
                nk_textedit_move_to_last(state);
Ivan Mahonin b53a5c
            else {
Ivan Mahonin b53a5c
                state->cursor = nk_textedit_move_to_word_next(state);
Ivan Mahonin b53a5c
                nk_textedit_clamp(state );
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_DOWN: {
Ivan Mahonin b53a5c
        struct nk_text_find find;
Ivan Mahonin b53a5c
        struct nk_text_edit_row row;
Ivan Mahonin b53a5c
        int i, sel = shift_mod;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (state->single_line) {
Ivan Mahonin b53a5c
            /* on windows, up&down in single-line behave like left&right */
Ivan Mahonin b53a5c
            key = NK_KEY_RIGHT;
Ivan Mahonin b53a5c
            goto retry;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (sel)
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
        else if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
            nk_textedit_move_to_last(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        /* compute current position of cursor point */
Ivan Mahonin b53a5c
        nk_textedit_clamp(state);
Ivan Mahonin b53a5c
        nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
Ivan Mahonin b53a5c
            font, row_height);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        /* now find character position down a row */
Ivan Mahonin b53a5c
        if (find.length)
Ivan Mahonin b53a5c
        {
Ivan Mahonin b53a5c
            float x;
Ivan Mahonin b53a5c
            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
Ivan Mahonin b53a5c
            int start = find.first_char + find.length;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            state->cursor = start;
Ivan Mahonin b53a5c
            nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
Ivan Mahonin b53a5c
            x = row.x0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            for (i=0; i < row.num_chars && x < row.x1; ++i) {
Ivan Mahonin b53a5c
                float dx = nk_textedit_get_width(state, start, i, font);
Ivan Mahonin b53a5c
                x += dx;
Ivan Mahonin b53a5c
                if (x > goal_x)
Ivan Mahonin b53a5c
                    break;
Ivan Mahonin b53a5c
                ++state->cursor;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            state->has_preferred_x = 1;
Ivan Mahonin b53a5c
            state->preferred_x = goal_x;
Ivan Mahonin b53a5c
            if (sel)
Ivan Mahonin b53a5c
                state->select_end = state->cursor;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
    } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_UP: {
Ivan Mahonin b53a5c
        struct nk_text_find find;
Ivan Mahonin b53a5c
        struct nk_text_edit_row row;
Ivan Mahonin b53a5c
        int i, sel = shift_mod;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (state->single_line) {
Ivan Mahonin b53a5c
            /* on windows, up&down become left&right */
Ivan Mahonin b53a5c
            key = NK_KEY_LEFT;
Ivan Mahonin b53a5c
            goto retry;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
        if (sel)
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
        else if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
            nk_textedit_move_to_first(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
         /* compute current position of cursor point */
Ivan Mahonin b53a5c
         nk_textedit_clamp(state);
Ivan Mahonin b53a5c
         nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
Ivan Mahonin b53a5c
                font, row_height);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
         /* can only go up if there's a previous row */
Ivan Mahonin b53a5c
         if (find.prev_first != find.first_char) {
Ivan Mahonin b53a5c
            /* now find character position up a row */
Ivan Mahonin b53a5c
            float x;
Ivan Mahonin b53a5c
            float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            state->cursor = find.prev_first;
Ivan Mahonin b53a5c
            nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
Ivan Mahonin b53a5c
            x = row.x0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            for (i=0; i < row.num_chars && x < row.x1; ++i) {
Ivan Mahonin b53a5c
                float dx = nk_textedit_get_width(state, find.prev_first, i, font);
Ivan Mahonin b53a5c
                x += dx;
Ivan Mahonin b53a5c
                if (x > goal_x)
Ivan Mahonin b53a5c
                    break;
Ivan Mahonin b53a5c
                ++state->cursor;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            state->has_preferred_x = 1;
Ivan Mahonin b53a5c
            state->preferred_x = goal_x;
Ivan Mahonin b53a5c
            if (sel) state->select_end = state->cursor;
Ivan Mahonin b53a5c
         }
Ivan Mahonin b53a5c
      } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_DEL:
Ivan Mahonin b53a5c
        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
Ivan Mahonin b53a5c
            break;
Ivan Mahonin b53a5c
        if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
            nk_textedit_delete_selection(state);
Ivan Mahonin b53a5c
        else {
Ivan Mahonin b53a5c
            int n = state->string.len;
Ivan Mahonin b53a5c
            if (state->cursor < n)
Ivan Mahonin b53a5c
                nk_textedit_delete(state, state->cursor, 1);
Ivan Mahonin b53a5c
         }
Ivan Mahonin b53a5c
         state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_BACKSPACE:
Ivan Mahonin b53a5c
        if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
Ivan Mahonin b53a5c
            break;
Ivan Mahonin b53a5c
        if (NK_TEXT_HAS_SELECTION(state))
Ivan Mahonin b53a5c
            nk_textedit_delete_selection(state);
Ivan Mahonin b53a5c
        else {
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            if (state->cursor > 0) {
Ivan Mahonin b53a5c
                nk_textedit_delete(state, state->cursor-1, 1);
Ivan Mahonin b53a5c
                --state->cursor;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
         }
Ivan Mahonin b53a5c
         state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_START:
Ivan Mahonin b53a5c
         if (shift_mod) {
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            state->cursor = state->select_end = 0;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         } else {
Ivan Mahonin b53a5c
            state->cursor = state->select_start = state->select_end = 0;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         }
Ivan Mahonin b53a5c
         break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_END:
Ivan Mahonin b53a5c
         if (shift_mod) {
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            state->cursor = state->select_end = state->string.len;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         } else {
Ivan Mahonin b53a5c
            state->cursor = state->string.len;
Ivan Mahonin b53a5c
            state->select_start = state->select_end = 0;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
         }
Ivan Mahonin b53a5c
         break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_LINE_START: {
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            struct nk_text_find find;
Ivan Mahonin b53a5c
           nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            if (state->string.len && state->cursor == state->string.len)
Ivan Mahonin b53a5c
                --state->cursor;
Ivan Mahonin b53a5c
            nk_textedit_find_charpos(&find, state,state->cursor, state->single_line,
Ivan Mahonin b53a5c
                font, row_height);
Ivan Mahonin b53a5c
            state->cursor = state->select_end = find.first_char;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            struct nk_text_find find;
Ivan Mahonin b53a5c
            if (state->string.len && state->cursor == state->string.len)
Ivan Mahonin b53a5c
                --state->cursor;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            nk_textedit_move_to_first(state);
Ivan Mahonin b53a5c
            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
Ivan Mahonin b53a5c
                font, row_height);
Ivan Mahonin b53a5c
            state->cursor = find.first_char;
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
      } break;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    case NK_KEY_TEXT_LINE_END: {
Ivan Mahonin b53a5c
        if (shift_mod) {
Ivan Mahonin b53a5c
            struct nk_text_find find;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            nk_textedit_prep_selection_at_cursor(state);
Ivan Mahonin b53a5c
            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
Ivan Mahonin b53a5c
                font, row_height);
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
            state->cursor = find.first_char + find.length;
Ivan Mahonin b53a5c
            if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
Ivan Mahonin b53a5c
                --state->cursor;
Ivan Mahonin b53a5c
            state->select_end = state->cursor;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            struct nk_text_find find;
Ivan Mahonin b53a5c
            nk_textedit_clamp(state);
Ivan Mahonin b53a5c
            nk_textedit_move_to_first(state);
Ivan Mahonin b53a5c
            nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
Ivan Mahonin b53a5c
                font, row_height);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            state->has_preferred_x = 0;
Ivan Mahonin b53a5c
            state->cursor = find.first_char + find.length;
Ivan Mahonin b53a5c
            if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
Ivan Mahonin b53a5c
                --state->cursor;
Ivan Mahonin b53a5c
        }} break;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_flush_redo(struct nk_text_undo_state *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    state->redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
Ivan Mahonin b53a5c
    state->redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_discard_undo(struct nk_text_undo_state *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* discard the oldest entry in the undo list */
Ivan Mahonin b53a5c
    if (state->undo_point > 0) {
Ivan Mahonin b53a5c
        /* if the 0th undo state has characters, clean those up */
Ivan Mahonin b53a5c
        if (state->undo_rec[0].char_storage >= 0) {
Ivan Mahonin b53a5c
            int n = state->undo_rec[0].insert_length, i;
Ivan Mahonin b53a5c
            /* delete n characters from all other records */
Ivan Mahonin b53a5c
            state->undo_char_point = (short)(state->undo_char_point - n);
Ivan Mahonin b53a5c
            NK_MEMCPY(state->undo_char, state->undo_char + n,
Ivan Mahonin b53a5c
                (nk_size)state->undo_char_point*sizeof(nk_rune));
Ivan Mahonin b53a5c
            for (i=0; i < state->undo_point; ++i) {
Ivan Mahonin b53a5c
                if (state->undo_rec[i].char_storage >= 0)
Ivan Mahonin b53a5c
                state->undo_rec[i].char_storage = (short)
Ivan Mahonin b53a5c
                    (state->undo_rec[i].char_storage - n);
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        --state->undo_point;
Ivan Mahonin b53a5c
        NK_MEMCPY(state->undo_rec, state->undo_rec+1,
Ivan Mahonin b53a5c
            (nk_size)((nk_size)state->undo_point * sizeof(state->undo_rec[0])));
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_discard_redo(struct nk_text_undo_state *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
/*  discard the oldest entry in the redo list--it's bad if this
Ivan Mahonin b53a5c
    ever happens, but because undo & redo have to store the actual
Ivan Mahonin b53a5c
    characters in different cases, the redo character buffer can
Ivan Mahonin b53a5c
    fill up even though the undo buffer didn't */
Ivan Mahonin b53a5c
    nk_size num;
Ivan Mahonin b53a5c
    int k = NK_TEXTEDIT_UNDOSTATECOUNT-1;
Ivan Mahonin b53a5c
    if (state->redo_point <= k) {
Ivan Mahonin b53a5c
        /* if the k'th undo state has characters, clean those up */
Ivan Mahonin b53a5c
        if (state->undo_rec[k].char_storage >= 0) {
Ivan Mahonin b53a5c
            int n = state->undo_rec[k].insert_length, i;
Ivan Mahonin b53a5c
            /* delete n characters from all other records */
Ivan Mahonin b53a5c
            state->redo_char_point = (short)(state->redo_char_point + n);
Ivan Mahonin b53a5c
            num = (nk_size)(NK_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point);
Ivan Mahonin b53a5c
            NK_MEMCPY(state->undo_char + state->redo_char_point,
Ivan Mahonin b53a5c
                state->undo_char + state->redo_char_point-n, num * sizeof(char));
Ivan Mahonin b53a5c
            for (i = state->redo_point; i < k; ++i) {
Ivan Mahonin b53a5c
                if (state->undo_rec[i].char_storage >= 0) {
Ivan Mahonin b53a5c
                    state->undo_rec[i].char_storage = (short)
Ivan Mahonin b53a5c
                        (state->undo_rec[i].char_storage + n);
Ivan Mahonin b53a5c
                }
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        ++state->redo_point;
Ivan Mahonin b53a5c
        num = (nk_size)(NK_TEXTEDIT_UNDOSTATECOUNT - state->redo_point);
Ivan Mahonin b53a5c
        if (num) NK_MEMCPY(state->undo_rec + state->redo_point-1,
Ivan Mahonin b53a5c
            state->undo_rec + state->redo_point, num * sizeof(state->undo_rec[0]));
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN struct nk_text_undo_record*
Ivan Mahonin b53a5c
nk_textedit_create_undo_record(struct nk_text_undo_state *state, int numchars)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* any time we create a new undo record, we discard redo*/
Ivan Mahonin b53a5c
    nk_textedit_flush_redo(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* if we have no free records, we have to make room,
Ivan Mahonin b53a5c
     * by sliding the existing records down */
Ivan Mahonin b53a5c
    if (state->undo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
Ivan Mahonin b53a5c
        nk_textedit_discard_undo(state);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* if the characters to store won't possibly fit in the buffer,
Ivan Mahonin b53a5c
     * we can't undo */
Ivan Mahonin b53a5c
    if (numchars > NK_TEXTEDIT_UNDOCHARCOUNT) {
Ivan Mahonin b53a5c
        state->undo_point = 0;
Ivan Mahonin b53a5c
        state->undo_char_point = 0;
Ivan Mahonin b53a5c
        return 0;
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* if we don't have enough free characters in the buffer,
Ivan Mahonin b53a5c
     * we have to make room */
Ivan Mahonin b53a5c
    while (state->undo_char_point + numchars > NK_TEXTEDIT_UNDOCHARCOUNT)
Ivan Mahonin b53a5c
        nk_textedit_discard_undo(state);
Ivan Mahonin b53a5c
    return &state->undo_rec[state->undo_point++];
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN nk_rune*
Ivan Mahonin b53a5c
nk_textedit_createundo(struct nk_text_undo_state *state, int pos,
Ivan Mahonin b53a5c
    int insert_len, int delete_len)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    struct nk_text_undo_record *r = nk_textedit_create_undo_record(state, insert_len);
Ivan Mahonin b53a5c
    if (r == 0)
Ivan Mahonin b53a5c
        return 0;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    r->where = pos;
Ivan Mahonin b53a5c
    r->insert_length = (short) insert_len;
Ivan Mahonin b53a5c
    r->delete_length = (short) delete_len;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    if (insert_len == 0) {
Ivan Mahonin b53a5c
        r->char_storage = -1;
Ivan Mahonin b53a5c
        return 0;
Ivan Mahonin b53a5c
    } else {
Ivan Mahonin b53a5c
        r->char_storage = state->undo_char_point;
Ivan Mahonin b53a5c
        state->undo_char_point = (short)(state->undo_char_point +  insert_len);
Ivan Mahonin b53a5c
        return &state->undo_char[r->char_storage];
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_undo(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    struct nk_text_undo_state *s = &state->undo;
Ivan Mahonin b53a5c
    struct nk_text_undo_record u, *r;
Ivan Mahonin b53a5c
    if (s->undo_point == 0)
Ivan Mahonin b53a5c
        return;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* we need to do two things: apply the undo record, and create a redo record */
Ivan Mahonin b53a5c
    u = s->undo_rec[s->undo_point-1];
Ivan Mahonin b53a5c
    r = &s->undo_rec[s->redo_point-1];
Ivan Mahonin b53a5c
    r->char_storage = -1;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    r->insert_length = u.delete_length;
Ivan Mahonin b53a5c
    r->delete_length = u.insert_length;
Ivan Mahonin b53a5c
    r->where = u.where;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    if (u.delete_length)
Ivan Mahonin b53a5c
    {
Ivan Mahonin b53a5c
       /*   if the undo record says to delete characters, then the redo record will
Ivan Mahonin b53a5c
            need to re-insert the characters that get deleted, so we need to store
Ivan Mahonin b53a5c
            them.
Ivan Mahonin b53a5c
            there are three cases:
Ivan Mahonin b53a5c
                - there's enough room to store the characters
Ivan Mahonin b53a5c
                - characters stored for *redoing* don't leave room for redo
Ivan Mahonin b53a5c
                - characters stored for *undoing* don't leave room for redo
Ivan Mahonin b53a5c
            if the last is true, we have to bail */
Ivan Mahonin b53a5c
        if (s->undo_char_point + u.delete_length >= NK_TEXTEDIT_UNDOCHARCOUNT) {
Ivan Mahonin b53a5c
            /* the undo records take up too much character space; there's no space
Ivan Mahonin b53a5c
            * to store the redo characters */
Ivan Mahonin b53a5c
            r->insert_length = 0;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            int i;
Ivan Mahonin b53a5c
            /* there's definitely room to store the characters eventually */
Ivan Mahonin b53a5c
            while (s->undo_char_point + u.delete_length > s->redo_char_point) {
Ivan Mahonin b53a5c
                /* there's currently not enough room, so discard a redo record */
Ivan Mahonin b53a5c
                nk_textedit_discard_redo(s);
Ivan Mahonin b53a5c
                /* should never happen: */
Ivan Mahonin b53a5c
                if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
Ivan Mahonin b53a5c
                    return;
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            r = &s->undo_rec[s->redo_point-1];
Ivan Mahonin b53a5c
            r->char_storage = (short)(s->redo_char_point - u.delete_length);
Ivan Mahonin b53a5c
            s->redo_char_point = (short)(s->redo_char_point -  u.delete_length);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            /* now save the characters */
Ivan Mahonin b53a5c
            for (i=0; i < u.delete_length; ++i)
Ivan Mahonin b53a5c
                s->undo_char[r->char_storage + i] =
Ivan Mahonin b53a5c
                    nk_str_rune_at(&state->string, u.where + i);
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        /* now we can carry out the deletion */
Ivan Mahonin b53a5c
        nk_str_delete_runes(&state->string, u.where, u.delete_length);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* check type of recorded action: */
Ivan Mahonin b53a5c
    if (u.insert_length) {
Ivan Mahonin b53a5c
        /* easy case: was a deletion, so we need to insert n characters */
Ivan Mahonin b53a5c
        nk_str_insert_text_runes(&state->string, u.where,
Ivan Mahonin b53a5c
            &s->undo_char[u.char_storage], u.insert_length);
Ivan Mahonin b53a5c
        s->undo_char_point = (short)(s->undo_char_point - u.insert_length);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
    state->cursor = (short)(u.where + u.insert_length);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    s->undo_point--;
Ivan Mahonin b53a5c
    s->redo_point--;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_redo(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    struct nk_text_undo_state *s = &state->undo;
Ivan Mahonin b53a5c
    struct nk_text_undo_record *u, r;
Ivan Mahonin b53a5c
    if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
Ivan Mahonin b53a5c
        return;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* we need to do two things: apply the redo record, and create an undo record */
Ivan Mahonin b53a5c
    u = &s->undo_rec[s->undo_point];
Ivan Mahonin b53a5c
    r = s->undo_rec[s->redo_point];
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    /* we KNOW there must be room for the undo record, because the redo record
Ivan Mahonin b53a5c
    was derived from an undo record */
Ivan Mahonin b53a5c
    u->delete_length = r.insert_length;
Ivan Mahonin b53a5c
    u->insert_length = r.delete_length;
Ivan Mahonin b53a5c
    u->where = r.where;
Ivan Mahonin b53a5c
    u->char_storage = -1;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    if (r.delete_length) {
Ivan Mahonin b53a5c
        /* the redo record requires us to delete characters, so the undo record
Ivan Mahonin b53a5c
        needs to store the characters */
Ivan Mahonin b53a5c
        if (s->undo_char_point + u->insert_length > s->redo_char_point) {
Ivan Mahonin b53a5c
            u->insert_length = 0;
Ivan Mahonin b53a5c
            u->delete_length = 0;
Ivan Mahonin b53a5c
        } else {
Ivan Mahonin b53a5c
            int i;
Ivan Mahonin b53a5c
            u->char_storage = s->undo_char_point;
Ivan Mahonin b53a5c
            s->undo_char_point = (short)(s->undo_char_point + u->insert_length);
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
            /* now save the characters */
Ivan Mahonin b53a5c
            for (i=0; i < u->insert_length; ++i) {
Ivan Mahonin b53a5c
                s->undo_char[u->char_storage + i] =
Ivan Mahonin b53a5c
                    nk_str_rune_at(&state->string, u->where + i);
Ivan Mahonin b53a5c
            }
Ivan Mahonin b53a5c
        }
Ivan Mahonin b53a5c
        nk_str_delete_runes(&state->string, r.where, r.delete_length);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    if (r.insert_length) {
Ivan Mahonin b53a5c
        /* easy case: need to insert n characters */
Ivan Mahonin b53a5c
        nk_str_insert_text_runes(&state->string, r.where,
Ivan Mahonin b53a5c
            &s->undo_char[r.char_storage], r.insert_length);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
    state->cursor = r.where + r.insert_length;
Ivan Mahonin b53a5c
Ivan Mahonin b53a5c
    s->undo_point++;
Ivan Mahonin b53a5c
    s->redo_point++;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_makeundo_insert(struct nk_text_edit *state, int where, int length)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    nk_textedit_createundo(&state->undo, where, 0, length);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_makeundo_delete(struct nk_text_edit *state, int where, int length)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    int i;
Ivan Mahonin b53a5c
    nk_rune *p = nk_textedit_createundo(&state->undo, where, length, 0);
Ivan Mahonin b53a5c
    if (p) {
Ivan Mahonin b53a5c
        for (i=0; i < length; ++i)
Ivan Mahonin b53a5c
            p[i] = nk_str_rune_at(&state->string, where+i);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_INTERN void
Ivan Mahonin b53a5c
nk_textedit_makeundo_replace(struct nk_text_edit *state, int where,
Ivan Mahonin b53a5c
    int old_length, int new_length)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    int i;
Ivan Mahonin b53a5c
    nk_rune *p = nk_textedit_createundo(&state->undo, where, old_length, new_length);
Ivan Mahonin b53a5c
    if (p) {
Ivan Mahonin b53a5c
        for (i=0; i < old_length; ++i)
Ivan Mahonin b53a5c
            p[i] = nk_str_rune_at(&state->string, where+i);
Ivan Mahonin b53a5c
    }
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_LIB void
Ivan Mahonin b53a5c
nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type,
Ivan Mahonin b53a5c
    nk_plugin_filter filter)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    /* reset the state to default */
Ivan Mahonin b53a5c
   state->undo.undo_point = 0;
Ivan Mahonin b53a5c
   state->undo.undo_char_point = 0;
Ivan Mahonin b53a5c
   state->undo.redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
Ivan Mahonin b53a5c
   state->undo.redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
Ivan Mahonin b53a5c
   state->select_end = state->select_start = 0;
Ivan Mahonin b53a5c
   state->cursor = 0;
Ivan Mahonin b53a5c
   state->has_preferred_x = 0;
Ivan Mahonin b53a5c
   state->preferred_x = 0;
Ivan Mahonin b53a5c
   state->cursor_at_end_of_line = 0;
Ivan Mahonin b53a5c
   state->initialized = 1;
Ivan Mahonin b53a5c
   state->single_line = (unsigned char)(type == NK_TEXT_EDIT_SINGLE_LINE);
Ivan Mahonin b53a5c
   state->mode = NK_TEXT_EDIT_MODE_VIEW;
Ivan Mahonin b53a5c
   state->filter = filter;
Ivan Mahonin b53a5c
   state->scrollbar = nk_vec2(0,0);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_init_fixed(struct nk_text_edit *state, void *memory, nk_size size)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    NK_ASSERT(memory);
Ivan Mahonin b53a5c
    if (!state || !memory || !size) return;
Ivan Mahonin b53a5c
    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
Ivan Mahonin b53a5c
    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
Ivan Mahonin b53a5c
    nk_str_init_fixed(&state->string, memory, size);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_init(struct nk_text_edit *state, struct nk_allocator *alloc, nk_size size)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    NK_ASSERT(alloc);
Ivan Mahonin b53a5c
    if (!state || !alloc) return;
Ivan Mahonin b53a5c
    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
Ivan Mahonin b53a5c
    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
Ivan Mahonin b53a5c
    nk_str_init(&state->string, alloc, size);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_init_default(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    if (!state) return;
Ivan Mahonin b53a5c
    NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
Ivan Mahonin b53a5c
    nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
Ivan Mahonin b53a5c
    nk_str_init_default(&state->string);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
#endif
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_select_all(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    state->select_start = 0;
Ivan Mahonin b53a5c
    state->select_end = state->string.len;
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c
NK_API void
Ivan Mahonin b53a5c
nk_textedit_free(struct nk_text_edit *state)
Ivan Mahonin b53a5c
{
Ivan Mahonin b53a5c
    NK_ASSERT(state);
Ivan Mahonin b53a5c
    if (!state) return;
Ivan Mahonin b53a5c
    nk_str_free(&state->string);
Ivan Mahonin b53a5c
}
Ivan Mahonin b53a5c