Nuklear
This is a minimal-state, immediate-mode graphical user interface toolkit written in ANSI C and licensed under public domain. It was designed as a simple embeddable user interface for application and does not have any dependencies, a default render backend or OS window/input handling but instead provides a highly modular, library-based approach, with simple input state for input and draw commands describing primitive shapes as output. So instead of providing a layered library that tries to abstract over a number of platform and render backends, it focuses only on the actual UI.
nuklear_text_editor.c
1 #include "nuklear.h"
2 #include "nuklear_internal.h"
3 
4 /* ===============================================================
5  *
6  * TEXT EDITOR
7  *
8  * ===============================================================*/
9 /* stb_textedit.h - v1.8 - public domain - Sean Barrett */
10 struct nk_text_find {
11  float x,y; /* position of n'th character */
12  float height; /* height of line */
13  int first_char, length; /* first char of row, and length */
14  int prev_first; /*_ first char of previous row */
15 };
16 
18  float x0,x1;
19  /* starting x location, end x location (allows for align=right, etc) */
20  float baseline_y_delta;
21  /* position of baseline relative to previous row's baseline*/
22  float ymin,ymax;
23  /* height of row above and below baseline */
24  int num_chars;
25 };
26 
27 /* forward declarations */
28 NK_INTERN void nk_textedit_makeundo_delete(struct nk_text_edit*, int, int);
29 NK_INTERN void nk_textedit_makeundo_insert(struct nk_text_edit*, int, int);
30 NK_INTERN void nk_textedit_makeundo_replace(struct nk_text_edit*, int, int, int);
31 #define NK_TEXT_HAS_SELECTION(s) ((s)->select_start != (s)->select_end)
32 
33 NK_INTERN float
34 nk_textedit_get_width(const struct nk_text_edit *edit, int line_start, int char_id,
35  const struct nk_user_font *font)
36 {
37  int len = 0;
38  nk_rune unicode = 0;
39  const char *str = nk_str_at_const(&edit->string, line_start + char_id, &unicode, &len);
40  return font->width(font->userdata, font->height, str, len);
41 }
42 NK_INTERN void
43 nk_textedit_layout_row(struct nk_text_edit_row *r, struct nk_text_edit *edit,
44  int line_start_id, float row_height, const struct nk_user_font *font)
45 {
46  int l;
47  int glyphs = 0;
48  nk_rune unicode;
49  const char *remaining;
50  int len = nk_str_len_char(&edit->string);
51  const char *end = nk_str_get_const(&edit->string) + len;
52  const char *text = nk_str_at_const(&edit->string, line_start_id, &unicode, &l);
53  const struct nk_vec2 size = nk_text_calculate_text_bounds(font,
54  text, (int)(end - text), row_height, &remaining, 0, &glyphs, NK_STOP_ON_NEW_LINE);
55 
56  r->x0 = 0.0f;
57  r->x1 = size.x;
58  r->baseline_y_delta = size.y;
59  r->ymin = 0.0f;
60  r->ymax = size.y;
61  r->num_chars = glyphs;
62 }
63 NK_INTERN int
64 nk_textedit_locate_coord(struct nk_text_edit *edit, float x, float y,
65  const struct nk_user_font *font, float row_height)
66 {
67  struct nk_text_edit_row r;
68  int n = edit->string.len;
69  float base_y = 0, prev_x;
70  int i=0, k;
71 
72  r.x0 = r.x1 = 0;
73  r.ymin = r.ymax = 0;
74  r.num_chars = 0;
75 
76  /* search rows to find one that straddles 'y' */
77  while (i < n) {
78  nk_textedit_layout_row(&r, edit, i, row_height, font);
79  if (r.num_chars <= 0)
80  return n;
81 
82  if (i==0 && y < base_y + r.ymin)
83  return 0;
84 
85  if (y < base_y + r.ymax)
86  break;
87 
88  i += r.num_chars;
89  base_y += r.baseline_y_delta;
90  }
91 
92  /* below all text, return 'after' last character */
93  if (i >= n)
94  return n;
95 
96  /* check if it's before the beginning of the line */
97  if (x < r.x0)
98  return i;
99 
100  /* check if it's before the end of the line */
101  if (x < r.x1) {
102  /* search characters in row for one that straddles 'x' */
103  k = i;
104  prev_x = r.x0;
105  for (i=0; i < r.num_chars; ++i) {
106  float w = nk_textedit_get_width(edit, k, i, font);
107  if (x < prev_x+w) {
108  if (x < prev_x+w/2)
109  return k+i;
110  else return k+i+1;
111  }
112  prev_x += w;
113  }
114  /* shouldn't happen, but if it does, fall through to end-of-line case */
115  }
116 
117  /* if the last character is a newline, return that.
118  * otherwise return 'after' the last character */
119  if (nk_str_rune_at(&edit->string, i+r.num_chars-1) == '\n')
120  return i+r.num_chars-1;
121  else return i+r.num_chars;
122 }
123 NK_LIB void
124 nk_textedit_click(struct nk_text_edit *state, float x, float y,
125  const struct nk_user_font *font, float row_height)
126 {
127  /* API click: on mouse down, move the cursor to the clicked location,
128  * and reset the selection */
129  state->cursor = nk_textedit_locate_coord(state, x, y, font, row_height);
130  state->select_start = state->cursor;
131  state->select_end = state->cursor;
132  state->has_preferred_x = 0;
133 }
134 NK_LIB void
135 nk_textedit_drag(struct nk_text_edit *state, float x, float y,
136  const struct nk_user_font *font, float row_height)
137 {
138  /* API drag: on mouse drag, move the cursor and selection endpoint
139  * to the clicked location */
140  int p = nk_textedit_locate_coord(state, x, y, font, row_height);
141  if (state->select_start == state->select_end)
142  state->select_start = state->cursor;
143  state->cursor = state->select_end = p;
144 }
145 NK_INTERN void
146 nk_textedit_find_charpos(struct nk_text_find *find, struct nk_text_edit *state,
147  int n, int single_line, const struct nk_user_font *font, float row_height)
148 {
149  /* find the x/y location of a character, and remember info about the previous
150  * row in case we get a move-up event (for page up, we'll have to rescan) */
151  struct nk_text_edit_row r;
152  int prev_start = 0;
153  int z = state->string.len;
154  int i=0, first;
155 
156  nk_zero_struct(r);
157  if (n == z) {
158  /* if it's at the end, then find the last line -- simpler than trying to
159  explicitly handle this case in the regular code */
160  nk_textedit_layout_row(&r, state, 0, row_height, font);
161  if (single_line) {
162  find->first_char = 0;
163  find->length = z;
164  } else {
165  while (i < z) {
166  prev_start = i;
167  i += r.num_chars;
168  nk_textedit_layout_row(&r, state, i, row_height, font);
169  }
170 
171  find->first_char = i;
172  find->length = r.num_chars;
173  }
174  find->x = r.x1;
175  find->y = r.ymin;
176  find->height = r.ymax - r.ymin;
177  find->prev_first = prev_start;
178  return;
179  }
180 
181  /* search rows to find the one that straddles character n */
182  find->y = 0;
183 
184  for(;;) {
185  nk_textedit_layout_row(&r, state, i, row_height, font);
186  if (n < i + r.num_chars) break;
187  prev_start = i;
188  i += r.num_chars;
189  find->y += r.baseline_y_delta;
190  }
191 
192  find->first_char = first = i;
193  find->length = r.num_chars;
194  find->height = r.ymax - r.ymin;
195  find->prev_first = prev_start;
196 
197  /* now scan to find xpos */
198  find->x = r.x0;
199  for (i=0; first+i < n; ++i)
200  find->x += nk_textedit_get_width(state, first, i, font);
201 }
202 NK_INTERN void
203 nk_textedit_clamp(struct nk_text_edit *state)
204 {
205  /* make the selection/cursor state valid if client altered the string */
206  int n = state->string.len;
207  if (NK_TEXT_HAS_SELECTION(state)) {
208  if (state->select_start > n) state->select_start = n;
209  if (state->select_end > n) state->select_end = n;
210  /* if clamping forced them to be equal, move the cursor to match */
211  if (state->select_start == state->select_end)
212  state->cursor = state->select_start;
213  }
214  if (state->cursor > n) state->cursor = n;
215 }
216 NK_API void
217 nk_textedit_delete(struct nk_text_edit *state, int where, int len)
218 {
219  /* delete characters while updating undo */
220  nk_textedit_makeundo_delete(state, where, len);
221  nk_str_delete_runes(&state->string, where, len);
222  state->has_preferred_x = 0;
223 }
224 NK_API void
225 nk_textedit_delete_selection(struct nk_text_edit *state)
226 {
227  /* delete the section */
228  nk_textedit_clamp(state);
229  if (NK_TEXT_HAS_SELECTION(state)) {
230  if (state->select_start < state->select_end) {
231  nk_textedit_delete(state, state->select_start,
232  state->select_end - state->select_start);
233  state->select_end = state->cursor = state->select_start;
234  } else {
235  nk_textedit_delete(state, state->select_end,
236  state->select_start - state->select_end);
237  state->select_start = state->cursor = state->select_end;
238  }
239  state->has_preferred_x = 0;
240  }
241 }
242 NK_INTERN void
243 nk_textedit_sortselection(struct nk_text_edit *state)
244 {
245  /* canonicalize the selection so start <= end */
246  if (state->select_end < state->select_start) {
247  int temp = state->select_end;
248  state->select_end = state->select_start;
249  state->select_start = temp;
250  }
251 }
252 NK_INTERN void
253 nk_textedit_move_to_first(struct nk_text_edit *state)
254 {
255  /* move cursor to first character of selection */
256  if (NK_TEXT_HAS_SELECTION(state)) {
257  nk_textedit_sortselection(state);
258  state->cursor = state->select_start;
259  state->select_end = state->select_start;
260  state->has_preferred_x = 0;
261  }
262 }
263 NK_INTERN void
264 nk_textedit_move_to_last(struct nk_text_edit *state)
265 {
266  /* move cursor to last character of selection */
267  if (NK_TEXT_HAS_SELECTION(state)) {
268  nk_textedit_sortselection(state);
269  nk_textedit_clamp(state);
270  state->cursor = state->select_end;
271  state->select_start = state->select_end;
272  state->has_preferred_x = 0;
273  }
274 }
275 NK_INTERN int
276 nk_is_word_boundary( struct nk_text_edit *state, int idx)
277 {
278  int len;
279  nk_rune c;
280  if (idx <= 0) return 1;
281  if (!nk_str_at_rune(&state->string, idx, &c, &len)) return 1;
282  return (c == ' ' || c == '\t' ||c == 0x3000 || c == ',' || c == ';' ||
283  c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' ||
284  c == '|');
285 }
286 NK_INTERN int
287 nk_textedit_move_to_word_previous(struct nk_text_edit *state)
288 {
289  int c = state->cursor - 1;
290  while( c >= 0 && !nk_is_word_boundary(state, c))
291  --c;
292 
293  if( c < 0 )
294  c = 0;
295 
296  return c;
297 }
298 NK_INTERN int
299 nk_textedit_move_to_word_next(struct nk_text_edit *state)
300 {
301  const int len = state->string.len;
302  int c = state->cursor+1;
303  while( c < len && !nk_is_word_boundary(state, c))
304  ++c;
305 
306  if( c > len )
307  c = len;
308 
309  return c;
310 }
311 NK_INTERN void
312 nk_textedit_prep_selection_at_cursor(struct nk_text_edit *state)
313 {
314  /* update selection and cursor to match each other */
315  if (!NK_TEXT_HAS_SELECTION(state))
316  state->select_start = state->select_end = state->cursor;
317  else state->cursor = state->select_end;
318 }
319 NK_API nk_bool
320 nk_textedit_cut(struct nk_text_edit *state)
321 {
322  /* API cut: delete selection */
323  if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
324  return 0;
325  if (NK_TEXT_HAS_SELECTION(state)) {
326  nk_textedit_delete_selection(state); /* implicitly clamps */
327  state->has_preferred_x = 0;
328  return 1;
329  }
330  return 0;
331 }
332 NK_API nk_bool
333 nk_textedit_paste(struct nk_text_edit *state, char const *ctext, int len)
334 {
335  /* API paste: replace existing selection with passed-in text */
336  int glyphs;
337  const char *text = (const char *) ctext;
338  if (state->mode == NK_TEXT_EDIT_MODE_VIEW) return 0;
339 
340  /* if there's a selection, the paste should delete it */
341  nk_textedit_clamp(state);
342  nk_textedit_delete_selection(state);
343 
344  /* try to insert the characters */
345  glyphs = nk_utf_len(ctext, len);
346  if (nk_str_insert_text_char(&state->string, state->cursor, text, len)) {
347  nk_textedit_makeundo_insert(state, state->cursor, glyphs);
348  state->cursor += len;
349  state->has_preferred_x = 0;
350  return 1;
351  }
352  /* remove the undo since we didn't actually insert the characters */
353  if (state->undo.undo_point)
354  --state->undo.undo_point;
355  return 0;
356 }
357 NK_API void
358 nk_textedit_text(struct nk_text_edit *state, const char *text, int total_len)
359 {
360  nk_rune unicode;
361  int glyph_len;
362  int text_len = 0;
363 
364  NK_ASSERT(state);
365  NK_ASSERT(text);
366  if (!text || !total_len || state->mode == NK_TEXT_EDIT_MODE_VIEW) return;
367 
368  glyph_len = nk_utf_decode(text, &unicode, total_len);
369  while ((text_len < total_len) && glyph_len)
370  {
371  /* don't insert a backward delete, just process the event */
372  if (unicode == 127) goto next;
373  /* can't add newline in single-line mode */
374  if (unicode == '\n' && state->single_line) goto next;
375  /* filter incoming text */
376  if (state->filter && !state->filter(state, unicode)) goto next;
377 
378  if (!NK_TEXT_HAS_SELECTION(state) &&
379  state->cursor < state->string.len)
380  {
381  if (state->mode == NK_TEXT_EDIT_MODE_REPLACE) {
382  nk_textedit_makeundo_replace(state, state->cursor, 1, 1);
383  nk_str_delete_runes(&state->string, state->cursor, 1);
384  }
385  if (nk_str_insert_text_utf8(&state->string, state->cursor,
386  text+text_len, 1))
387  {
388  ++state->cursor;
389  state->has_preferred_x = 0;
390  }
391  } else {
392  nk_textedit_delete_selection(state); /* implicitly clamps */
393  if (nk_str_insert_text_utf8(&state->string, state->cursor,
394  text+text_len, 1))
395  {
396  nk_textedit_makeundo_insert(state, state->cursor, 1);
397  state->cursor = NK_MIN(state->cursor + 1, state->string.len);
398  state->has_preferred_x = 0;
399  }
400  }
401  next:
402  text_len += glyph_len;
403  glyph_len = nk_utf_decode(text + text_len, &unicode, total_len-text_len);
404  }
405 }
406 NK_LIB void
407 nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod,
408  const struct nk_user_font *font, float row_height)
409 {
410 retry:
411  switch (key)
412  {
413  case NK_KEY_NONE:
414  case NK_KEY_CTRL:
415  case NK_KEY_ENTER:
416  case NK_KEY_SHIFT:
417  case NK_KEY_TAB:
418  case NK_KEY_COPY:
419  case NK_KEY_CUT:
420  case NK_KEY_PASTE:
421  case NK_KEY_MAX:
422  default: break;
423  case NK_KEY_TEXT_UNDO:
424  nk_textedit_undo(state);
425  state->has_preferred_x = 0;
426  break;
427 
428  case NK_KEY_TEXT_REDO:
429  nk_textedit_redo(state);
430  state->has_preferred_x = 0;
431  break;
432 
433  case NK_KEY_TEXT_SELECT_ALL:
434  nk_textedit_select_all(state);
435  state->has_preferred_x = 0;
436  break;
437 
438  case NK_KEY_TEXT_INSERT_MODE:
439  if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
440  state->mode = NK_TEXT_EDIT_MODE_INSERT;
441  break;
442  case NK_KEY_TEXT_REPLACE_MODE:
443  if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
444  state->mode = NK_TEXT_EDIT_MODE_REPLACE;
445  break;
446  case NK_KEY_TEXT_RESET_MODE:
447  if (state->mode == NK_TEXT_EDIT_MODE_INSERT ||
448  state->mode == NK_TEXT_EDIT_MODE_REPLACE)
449  state->mode = NK_TEXT_EDIT_MODE_VIEW;
450  break;
451 
452  case NK_KEY_LEFT:
453  if (shift_mod) {
454  nk_textedit_clamp(state);
455  nk_textedit_prep_selection_at_cursor(state);
456  /* move selection left */
457  if (state->select_end > 0)
458  --state->select_end;
459  state->cursor = state->select_end;
460  state->has_preferred_x = 0;
461  } else {
462  /* if currently there's a selection,
463  * move cursor to start of selection */
464  if (NK_TEXT_HAS_SELECTION(state))
465  nk_textedit_move_to_first(state);
466  else if (state->cursor > 0)
467  --state->cursor;
468  state->has_preferred_x = 0;
469  } break;
470 
471  case NK_KEY_RIGHT:
472  if (shift_mod) {
473  nk_textedit_prep_selection_at_cursor(state);
474  /* move selection right */
475  ++state->select_end;
476  nk_textedit_clamp(state);
477  state->cursor = state->select_end;
478  state->has_preferred_x = 0;
479  } else {
480  /* if currently there's a selection,
481  * move cursor to end of selection */
482  if (NK_TEXT_HAS_SELECTION(state))
483  nk_textedit_move_to_last(state);
484  else ++state->cursor;
485  nk_textedit_clamp(state);
486  state->has_preferred_x = 0;
487  } break;
488 
489  case NK_KEY_TEXT_WORD_LEFT:
490  if (shift_mod) {
491  if( !NK_TEXT_HAS_SELECTION( state ) )
492  nk_textedit_prep_selection_at_cursor(state);
493  state->cursor = nk_textedit_move_to_word_previous(state);
494  state->select_end = state->cursor;
495  nk_textedit_clamp(state );
496  } else {
497  if (NK_TEXT_HAS_SELECTION(state))
498  nk_textedit_move_to_first(state);
499  else {
500  state->cursor = nk_textedit_move_to_word_previous(state);
501  nk_textedit_clamp(state );
502  }
503  } break;
504 
505  case NK_KEY_TEXT_WORD_RIGHT:
506  if (shift_mod) {
507  if( !NK_TEXT_HAS_SELECTION( state ) )
508  nk_textedit_prep_selection_at_cursor(state);
509  state->cursor = nk_textedit_move_to_word_next(state);
510  state->select_end = state->cursor;
511  nk_textedit_clamp(state);
512  } else {
513  if (NK_TEXT_HAS_SELECTION(state))
514  nk_textedit_move_to_last(state);
515  else {
516  state->cursor = nk_textedit_move_to_word_next(state);
517  nk_textedit_clamp(state );
518  }
519  } break;
520 
521  case NK_KEY_DOWN: {
522  struct nk_text_find find;
523  struct nk_text_edit_row row;
524  int i, sel = shift_mod;
525 
526  if (state->single_line) {
527  /* on windows, up&down in single-line behave like left&right */
528  key = NK_KEY_RIGHT;
529  goto retry;
530  }
531 
532  if (sel)
533  nk_textedit_prep_selection_at_cursor(state);
534  else if (NK_TEXT_HAS_SELECTION(state))
535  nk_textedit_move_to_last(state);
536 
537  /* compute current position of cursor point */
538  nk_textedit_clamp(state);
539  nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
540  font, row_height);
541 
542  /* now find character position down a row */
543  if (find.length)
544  {
545  float x;
546  float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
547  int start = find.first_char + find.length;
548 
549  state->cursor = start;
550  nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
551  x = row.x0;
552 
553  for (i=0; i < row.num_chars && x < row.x1; ++i) {
554  float dx = nk_textedit_get_width(state, start, i, font);
555  x += dx;
556  if (x > goal_x)
557  break;
558  ++state->cursor;
559  }
560  nk_textedit_clamp(state);
561 
562  state->has_preferred_x = 1;
563  state->preferred_x = goal_x;
564  if (sel)
565  state->select_end = state->cursor;
566  }
567  } break;
568 
569  case NK_KEY_UP: {
570  struct nk_text_find find;
571  struct nk_text_edit_row row;
572  int i, sel = shift_mod;
573 
574  if (state->single_line) {
575  /* on windows, up&down become left&right */
576  key = NK_KEY_LEFT;
577  goto retry;
578  }
579 
580  if (sel)
581  nk_textedit_prep_selection_at_cursor(state);
582  else if (NK_TEXT_HAS_SELECTION(state))
583  nk_textedit_move_to_first(state);
584 
585  /* compute current position of cursor point */
586  nk_textedit_clamp(state);
587  nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
588  font, row_height);
589 
590  /* can only go up if there's a previous row */
591  if (find.prev_first != find.first_char) {
592  /* now find character position up a row */
593  float x;
594  float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
595 
596  state->cursor = find.prev_first;
597  nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
598  x = row.x0;
599 
600  for (i=0; i < row.num_chars && x < row.x1; ++i) {
601  float dx = nk_textedit_get_width(state, find.prev_first, i, font);
602  x += dx;
603  if (x > goal_x)
604  break;
605  ++state->cursor;
606  }
607  nk_textedit_clamp(state);
608 
609  state->has_preferred_x = 1;
610  state->preferred_x = goal_x;
611  if (sel) state->select_end = state->cursor;
612  }
613  } break;
614 
615  case NK_KEY_DEL:
616  if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
617  break;
618  if (NK_TEXT_HAS_SELECTION(state))
619  nk_textedit_delete_selection(state);
620  else {
621  int n = state->string.len;
622  if (state->cursor < n)
623  nk_textedit_delete(state, state->cursor, 1);
624  }
625  state->has_preferred_x = 0;
626  break;
627 
628  case NK_KEY_BACKSPACE:
629  if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
630  break;
631  if (NK_TEXT_HAS_SELECTION(state))
632  nk_textedit_delete_selection(state);
633  else {
634  nk_textedit_clamp(state);
635  if (state->cursor > 0) {
636  nk_textedit_delete(state, state->cursor-1, 1);
637  --state->cursor;
638  }
639  }
640  state->has_preferred_x = 0;
641  break;
642 
643  case NK_KEY_TEXT_START:
644  if (shift_mod) {
645  nk_textedit_prep_selection_at_cursor(state);
646  state->cursor = state->select_end = 0;
647  state->has_preferred_x = 0;
648  } else {
649  state->cursor = state->select_start = state->select_end = 0;
650  state->has_preferred_x = 0;
651  }
652  break;
653 
654  case NK_KEY_TEXT_END:
655  if (shift_mod) {
656  nk_textedit_prep_selection_at_cursor(state);
657  state->cursor = state->select_end = state->string.len;
658  state->has_preferred_x = 0;
659  } else {
660  state->cursor = state->string.len;
661  state->select_start = state->select_end = 0;
662  state->has_preferred_x = 0;
663  }
664  break;
665 
666  case NK_KEY_TEXT_LINE_START: {
667  if (shift_mod) {
668  struct nk_text_find find;
669  nk_textedit_clamp(state);
670  nk_textedit_prep_selection_at_cursor(state);
671  if (state->string.len && state->cursor == state->string.len)
672  --state->cursor;
673  nk_textedit_find_charpos(&find, state,state->cursor, state->single_line,
674  font, row_height);
675  state->cursor = state->select_end = find.first_char;
676  state->has_preferred_x = 0;
677  } else {
678  struct nk_text_find find;
679  if (state->string.len && state->cursor == state->string.len)
680  --state->cursor;
681  nk_textedit_clamp(state);
682  nk_textedit_move_to_first(state);
683  nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
684  font, row_height);
685  state->cursor = find.first_char;
686  state->has_preferred_x = 0;
687  }
688  } break;
689 
690  case NK_KEY_TEXT_LINE_END: {
691  if (shift_mod) {
692  struct nk_text_find find;
693  nk_textedit_clamp(state);
694  nk_textedit_prep_selection_at_cursor(state);
695  nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
696  font, row_height);
697  state->has_preferred_x = 0;
698  state->cursor = find.first_char + find.length;
699  if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
700  --state->cursor;
701  state->select_end = state->cursor;
702  } else {
703  struct nk_text_find find;
704  nk_textedit_clamp(state);
705  nk_textedit_move_to_first(state);
706  nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
707  font, row_height);
708 
709  state->has_preferred_x = 0;
710  state->cursor = find.first_char + find.length;
711  if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
712  --state->cursor;
713  }} break;
714  }
715 }
716 NK_INTERN void
717 nk_textedit_flush_redo(struct nk_text_undo_state *state)
718 {
719  state->redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
720  state->redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
721 }
722 NK_INTERN void
723 nk_textedit_discard_undo(struct nk_text_undo_state *state)
724 {
725  /* discard the oldest entry in the undo list */
726  if (state->undo_point > 0) {
727  /* if the 0th undo state has characters, clean those up */
728  if (state->undo_rec[0].char_storage >= 0) {
729  int n = state->undo_rec[0].insert_length, i;
730  /* delete n characters from all other records */
731  state->undo_char_point = (short)(state->undo_char_point - n);
732  NK_MEMCPY(state->undo_char, state->undo_char + n,
733  (nk_size)state->undo_char_point*sizeof(nk_rune));
734  for (i=0; i < state->undo_point; ++i) {
735  if (state->undo_rec[i].char_storage >= 0)
736  state->undo_rec[i].char_storage = (short)
737  (state->undo_rec[i].char_storage - n);
738  }
739  }
740  --state->undo_point;
741  NK_MEMCPY(state->undo_rec, state->undo_rec+1,
742  (nk_size)((nk_size)state->undo_point * sizeof(state->undo_rec[0])));
743  }
744 }
745 NK_INTERN void
746 nk_textedit_discard_redo(struct nk_text_undo_state *state)
747 {
748 /* discard the oldest entry in the redo list--it's bad if this
749  ever happens, but because undo & redo have to store the actual
750  characters in different cases, the redo character buffer can
751  fill up even though the undo buffer didn't */
752  nk_size num;
753  int k = NK_TEXTEDIT_UNDOSTATECOUNT-1;
754  if (state->redo_point <= k) {
755  /* if the k'th undo state has characters, clean those up */
756  if (state->undo_rec[k].char_storage >= 0) {
757  int n = state->undo_rec[k].insert_length, i;
758  /* delete n characters from all other records */
759  state->redo_char_point = (short)(state->redo_char_point + n);
760  num = (nk_size)(NK_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point);
761  NK_MEMCPY(state->undo_char + state->redo_char_point,
762  state->undo_char + state->redo_char_point-n, num * sizeof(char));
763  for (i = state->redo_point; i < k; ++i) {
764  if (state->undo_rec[i].char_storage >= 0) {
765  state->undo_rec[i].char_storage = (short)
766  (state->undo_rec[i].char_storage + n);
767  }
768  }
769  }
770  ++state->redo_point;
771  num = (nk_size)(NK_TEXTEDIT_UNDOSTATECOUNT - state->redo_point);
772  if (num) NK_MEMCPY(state->undo_rec + state->redo_point-1,
773  state->undo_rec + state->redo_point, num * sizeof(state->undo_rec[0]));
774  }
775 }
776 NK_INTERN struct nk_text_undo_record*
777 nk_textedit_create_undo_record(struct nk_text_undo_state *state, int numchars)
778 {
779  /* any time we create a new undo record, we discard redo*/
780  nk_textedit_flush_redo(state);
781 
782  /* if we have no free records, we have to make room,
783  * by sliding the existing records down */
784  if (state->undo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
785  nk_textedit_discard_undo(state);
786 
787  /* if the characters to store won't possibly fit in the buffer,
788  * we can't undo */
789  if (numchars > NK_TEXTEDIT_UNDOCHARCOUNT) {
790  state->undo_point = 0;
791  state->undo_char_point = 0;
792  return 0;
793  }
794 
795  /* if we don't have enough free characters in the buffer,
796  * we have to make room */
797  while (state->undo_char_point + numchars > NK_TEXTEDIT_UNDOCHARCOUNT)
798  nk_textedit_discard_undo(state);
799  return &state->undo_rec[state->undo_point++];
800 }
801 NK_INTERN nk_rune*
802 nk_textedit_createundo(struct nk_text_undo_state *state, int pos,
803  int insert_len, int delete_len)
804 {
805  struct nk_text_undo_record *r = nk_textedit_create_undo_record(state, insert_len);
806  if (r == 0)
807  return 0;
808 
809  r->where = pos;
810  r->insert_length = (short) insert_len;
811  r->delete_length = (short) delete_len;
812 
813  if (insert_len == 0) {
814  r->char_storage = -1;
815  return 0;
816  } else {
817  r->char_storage = state->undo_char_point;
818  state->undo_char_point = (short)(state->undo_char_point + insert_len);
819  return &state->undo_char[r->char_storage];
820  }
821 }
822 NK_API void
823 nk_textedit_undo(struct nk_text_edit *state)
824 {
825  struct nk_text_undo_state *s = &state->undo;
826  struct nk_text_undo_record u, *r;
827  if (s->undo_point == 0)
828  return;
829 
830  /* we need to do two things: apply the undo record, and create a redo record */
831  u = s->undo_rec[s->undo_point-1];
832  r = &s->undo_rec[s->redo_point-1];
833  r->char_storage = -1;
834 
835  r->insert_length = u.delete_length;
836  r->delete_length = u.insert_length;
837  r->where = u.where;
838 
839  if (u.delete_length)
840  {
841  /* if the undo record says to delete characters, then the redo record will
842  need to re-insert the characters that get deleted, so we need to store
843  them.
844  there are three cases:
845  - there's enough room to store the characters
846  - characters stored for *redoing* don't leave room for redo
847  - characters stored for *undoing* don't leave room for redo
848  if the last is true, we have to bail */
849  if (s->undo_char_point + u.delete_length >= NK_TEXTEDIT_UNDOCHARCOUNT) {
850  /* the undo records take up too much character space; there's no space
851  * to store the redo characters */
852  r->insert_length = 0;
853  } else {
854  int i;
855  /* there's definitely room to store the characters eventually */
856  while (s->undo_char_point + u.delete_length > s->redo_char_point) {
857  /* there's currently not enough room, so discard a redo record */
858  nk_textedit_discard_redo(s);
859  /* should never happen: */
860  if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
861  return;
862  }
863 
864  r = &s->undo_rec[s->redo_point-1];
865  r->char_storage = (short)(s->redo_char_point - u.delete_length);
866  s->redo_char_point = (short)(s->redo_char_point - u.delete_length);
867 
868  /* now save the characters */
869  for (i=0; i < u.delete_length; ++i)
870  s->undo_char[r->char_storage + i] =
871  nk_str_rune_at(&state->string, u.where + i);
872  }
873  /* now we can carry out the deletion */
874  nk_str_delete_runes(&state->string, u.where, u.delete_length);
875  }
876 
877  /* check type of recorded action: */
878  if (u.insert_length) {
879  /* easy case: was a deletion, so we need to insert n characters */
880  nk_str_insert_text_runes(&state->string, u.where,
881  &s->undo_char[u.char_storage], u.insert_length);
882  s->undo_char_point = (short)(s->undo_char_point - u.insert_length);
883  }
884  state->cursor = (short)(u.where + u.insert_length);
885 
886  s->undo_point--;
887  s->redo_point--;
888 }
889 NK_API void
890 nk_textedit_redo(struct nk_text_edit *state)
891 {
892  struct nk_text_undo_state *s = &state->undo;
893  struct nk_text_undo_record *u, r;
894  if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
895  return;
896 
897  /* we need to do two things: apply the redo record, and create an undo record */
898  u = &s->undo_rec[s->undo_point];
899  r = s->undo_rec[s->redo_point];
900 
901  /* we KNOW there must be room for the undo record, because the redo record
902  was derived from an undo record */
903  u->delete_length = r.insert_length;
904  u->insert_length = r.delete_length;
905  u->where = r.where;
906  u->char_storage = -1;
907 
908  if (r.delete_length) {
909  /* the redo record requires us to delete characters, so the undo record
910  needs to store the characters */
911  if (s->undo_char_point + u->insert_length > s->redo_char_point) {
912  u->insert_length = 0;
913  u->delete_length = 0;
914  } else {
915  int i;
916  u->char_storage = s->undo_char_point;
917  s->undo_char_point = (short)(s->undo_char_point + u->insert_length);
918 
919  /* now save the characters */
920  for (i=0; i < u->insert_length; ++i) {
921  s->undo_char[u->char_storage + i] =
922  nk_str_rune_at(&state->string, u->where + i);
923  }
924  }
925  nk_str_delete_runes(&state->string, r.where, r.delete_length);
926  }
927 
928  if (r.insert_length) {
929  /* easy case: need to insert n characters */
930  nk_str_insert_text_runes(&state->string, r.where,
931  &s->undo_char[r.char_storage], r.insert_length);
932  }
933  state->cursor = r.where + r.insert_length;
934 
935  s->undo_point++;
936  s->redo_point++;
937 }
938 NK_INTERN void
939 nk_textedit_makeundo_insert(struct nk_text_edit *state, int where, int length)
940 {
941  nk_textedit_createundo(&state->undo, where, 0, length);
942 }
943 NK_INTERN void
944 nk_textedit_makeundo_delete(struct nk_text_edit *state, int where, int length)
945 {
946  int i;
947  nk_rune *p = nk_textedit_createundo(&state->undo, where, length, 0);
948  if (p) {
949  for (i=0; i < length; ++i)
950  p[i] = nk_str_rune_at(&state->string, where+i);
951  }
952 }
953 NK_INTERN void
954 nk_textedit_makeundo_replace(struct nk_text_edit *state, int where,
955  int old_length, int new_length)
956 {
957  int i;
958  nk_rune *p = nk_textedit_createundo(&state->undo, where, old_length, new_length);
959  if (p) {
960  for (i=0; i < old_length; ++i)
961  p[i] = nk_str_rune_at(&state->string, where+i);
962  }
963 }
964 NK_LIB void
965 nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type,
966  nk_plugin_filter filter)
967 {
968  /* reset the state to default */
969  state->undo.undo_point = 0;
970  state->undo.undo_char_point = 0;
971  state->undo.redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
972  state->undo.redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
973  state->select_end = state->select_start = 0;
974  state->cursor = 0;
975  state->has_preferred_x = 0;
976  state->preferred_x = 0;
977  state->cursor_at_end_of_line = 0;
978  state->initialized = 1;
979  state->single_line = (unsigned char)(type == NK_TEXT_EDIT_SINGLE_LINE);
980  state->mode = NK_TEXT_EDIT_MODE_VIEW;
981  state->filter = filter;
982  state->scrollbar = nk_vec2(0,0);
983 }
984 NK_API void
985 nk_textedit_init_fixed(struct nk_text_edit *state, void *memory, nk_size size)
986 {
987  NK_ASSERT(state);
988  NK_ASSERT(memory);
989  if (!state || !memory || !size) return;
990  NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
991  nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
992  nk_str_init_fixed(&state->string, memory, size);
993 }
994 NK_API void
995 nk_textedit_init(struct nk_text_edit *state, const struct nk_allocator *alloc, nk_size size)
996 {
997  NK_ASSERT(state);
998  NK_ASSERT(alloc);
999  if (!state || !alloc) return;
1000  NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
1001  nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
1002  nk_str_init(&state->string, alloc, size);
1003 }
1004 #ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
1005 NK_API void
1006 nk_textedit_init_default(struct nk_text_edit *state)
1007 {
1008  NK_ASSERT(state);
1009  if (!state) return;
1010  NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
1011  nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
1012  nk_str_init_default(&state->string);
1013 }
1014 #endif
1015 NK_API void
1016 nk_textedit_select_all(struct nk_text_edit *state)
1017 {
1018  NK_ASSERT(state);
1019  state->select_start = 0;
1020  state->select_end = state->string.len;
1021 }
1022 NK_API void
1023 nk_textedit_free(struct nk_text_edit *state)
1024 {
1025  NK_ASSERT(state);
1026  if (!state) return;
1027  nk_str_free(&state->string);
1028 }
1029 
main API and documentation file
NK_API void nk_textedit_init(struct nk_text_edit *, const struct nk_allocator *, nk_size size)
text editor
nk_text_width_f width
!< max height of the font
Definition: nuklear.h:4009
float height
!< user provided font handle
Definition: nuklear.h:4008