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.
 
Loading...
Searching...
No Matches
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 */
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 */
28NK_INTERN void nk_textedit_makeundo_delete(struct nk_text_edit*, int, int);
29NK_INTERN void nk_textedit_makeundo_insert(struct nk_text_edit*, int, int);
30NK_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
33NK_INTERN float
34nk_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}
42NK_INTERN void
43nk_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}
63NK_INTERN int
64nk_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}
123NK_LIB void
124nk_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}
134NK_LIB void
135nk_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}
145NK_INTERN void
146nk_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}
202NK_INTERN void
203nk_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}
216NK_API void
217nk_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}
224NK_API void
225nk_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}
242NK_INTERN void
243nk_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}
252NK_INTERN void
253nk_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}
263NK_INTERN void
264nk_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}
275NK_INTERN int
276nk_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#ifndef NK_IS_WORD_BOUNDARY
283 return (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' ||
284 c == '\v' || c == 0x3000);
285#else
286 return NK_IS_WORD_BOUNDARY(c);
287#endif
288}
289NK_INTERN int
290nk_textedit_move_to_word_previous(struct nk_text_edit *state)
291{
292 int c = state->cursor - 1;
293 if (c > 0) {
294 if (nk_is_word_boundary(state, c)) {
295 while (c > 0 && nk_is_word_boundary(state, --c));
296 }
297 while (!nk_is_word_boundary(state, --c));
298 c++;
299 } else {
300 return 0;
301 }
302
303 return c;
304}
305NK_INTERN int
306nk_textedit_move_to_word_next(struct nk_text_edit *state)
307{
308 const int len = state->string.len;
309 int c = state->cursor;
310 if (c < len) {
311 if (!nk_is_word_boundary(state, c)) {
312 while (c < len && !nk_is_word_boundary(state, ++c));
313 }
314 while (c < len && nk_is_word_boundary(state, ++c));
315 } else {
316 return len;
317 }
318
319 return c;
320}
321NK_INTERN void
322nk_textedit_prep_selection_at_cursor(struct nk_text_edit *state)
323{
324 /* update selection and cursor to match each other */
325 if (!NK_TEXT_HAS_SELECTION(state))
326 state->select_start = state->select_end = state->cursor;
327 else state->cursor = state->select_end;
328}
329NK_API nk_bool
330nk_textedit_cut(struct nk_text_edit *state)
331{
332 /* API cut: delete selection */
333 if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
334 return 0;
335 if (NK_TEXT_HAS_SELECTION(state)) {
336 nk_textedit_delete_selection(state); /* implicitly clamps */
337 state->has_preferred_x = 0;
338 return 1;
339 }
340 return 0;
341}
342NK_API nk_bool
343nk_textedit_paste(struct nk_text_edit *state, char const *ctext, int len)
344{
345 /* API paste: replace existing selection with passed-in text */
346 int glyphs;
347 const char *text = (const char *) ctext;
348 if (state->mode == NK_TEXT_EDIT_MODE_VIEW) return 0;
349
350 /* if there's a selection, the paste should delete it */
351 nk_textedit_clamp(state);
352 nk_textedit_delete_selection(state);
353
354 /* try to insert the characters */
355 glyphs = nk_utf_len(ctext, len);
356 if (nk_str_insert_text_char(&state->string, state->cursor, text, len)) {
357 nk_textedit_makeundo_insert(state, state->cursor, glyphs);
358 state->cursor += len;
359 state->has_preferred_x = 0;
360 return 1;
361 }
362 /* remove the undo since we didn't actually insert the characters */
363 if (state->undo.undo_point)
364 --state->undo.undo_point;
365 return 0;
366}
367NK_API void
368nk_textedit_text(struct nk_text_edit *state, const char *text, int total_len)
369{
370 nk_rune unicode;
371 int glyph_len;
372 int text_len = 0;
373
374 NK_ASSERT(state);
375 NK_ASSERT(text);
376 if (!text || !total_len || state->mode == NK_TEXT_EDIT_MODE_VIEW) return;
377
378 glyph_len = nk_utf_decode(text, &unicode, total_len);
379 while ((text_len < total_len) && glyph_len)
380 {
381 /* don't insert a backward delete, just process the event */
382 if (unicode == 127) goto next;
383 /* can't add newline in single-line mode */
384 if (unicode == '\n' && state->single_line) goto next;
385 /* filter incoming text */
386 if (state->filter && !state->filter(state, unicode)) goto next;
387
388 if (!NK_TEXT_HAS_SELECTION(state) &&
389 state->cursor < state->string.len)
390 {
391 if (state->mode == NK_TEXT_EDIT_MODE_REPLACE) {
392 nk_textedit_makeundo_replace(state, state->cursor, 1, 1);
393 nk_str_delete_runes(&state->string, state->cursor, 1);
394 }
395 if (nk_str_insert_text_utf8(&state->string, state->cursor,
396 text+text_len, 1))
397 {
398 ++state->cursor;
399 state->has_preferred_x = 0;
400 }
401 } else {
402 nk_textedit_delete_selection(state); /* implicitly clamps */
403 if (nk_str_insert_text_utf8(&state->string, state->cursor,
404 text+text_len, 1))
405 {
406 nk_textedit_makeundo_insert(state, state->cursor, 1);
407 state->cursor = NK_MIN(state->cursor + 1, state->string.len);
408 state->has_preferred_x = 0;
409 }
410 }
411 next:
412 text_len += glyph_len;
413 glyph_len = nk_utf_decode(text + text_len, &unicode, total_len-text_len);
414 }
415}
416NK_LIB void
417nk_textedit_key(struct nk_text_edit *state, enum nk_keys key, int shift_mod,
418 const struct nk_user_font *font, float row_height)
419{
420retry:
421 switch (key)
422 {
423 case NK_KEY_NONE:
424 case NK_KEY_CTRL:
425 case NK_KEY_ENTER:
426 case NK_KEY_SHIFT:
427 case NK_KEY_TAB:
428 case NK_KEY_COPY:
429 case NK_KEY_CUT:
430 case NK_KEY_PASTE:
431 case NK_KEY_MAX:
432 default: break;
433 case NK_KEY_TEXT_UNDO:
434 nk_textedit_undo(state);
435 state->has_preferred_x = 0;
436 break;
437
438 case NK_KEY_TEXT_REDO:
439 nk_textedit_redo(state);
440 state->has_preferred_x = 0;
441 break;
442
443 case NK_KEY_TEXT_SELECT_ALL:
444 nk_textedit_select_all(state);
445 state->has_preferred_x = 0;
446 break;
447
448 case NK_KEY_TEXT_INSERT_MODE:
449 if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
450 state->mode = NK_TEXT_EDIT_MODE_INSERT;
451 break;
452 case NK_KEY_TEXT_REPLACE_MODE:
453 if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
454 state->mode = NK_TEXT_EDIT_MODE_REPLACE;
455 break;
456 case NK_KEY_TEXT_RESET_MODE:
457 if (state->mode == NK_TEXT_EDIT_MODE_INSERT ||
458 state->mode == NK_TEXT_EDIT_MODE_REPLACE)
459 state->mode = NK_TEXT_EDIT_MODE_VIEW;
460 break;
461
462 case NK_KEY_LEFT:
463 if (shift_mod) {
464 nk_textedit_clamp(state);
465 nk_textedit_prep_selection_at_cursor(state);
466 /* move selection left */
467 if (state->select_end > 0)
468 --state->select_end;
469 state->cursor = state->select_end;
470 state->has_preferred_x = 0;
471 } else {
472 /* if currently there's a selection,
473 * move cursor to start of selection */
474 if (NK_TEXT_HAS_SELECTION(state))
475 nk_textedit_move_to_first(state);
476 else if (state->cursor > 0)
477 --state->cursor;
478 state->has_preferred_x = 0;
479 } break;
480
481 case NK_KEY_RIGHT:
482 if (shift_mod) {
483 nk_textedit_prep_selection_at_cursor(state);
484 /* move selection right */
485 ++state->select_end;
486 nk_textedit_clamp(state);
487 state->cursor = state->select_end;
488 state->has_preferred_x = 0;
489 } else {
490 /* if currently there's a selection,
491 * move cursor to end of selection */
492 if (NK_TEXT_HAS_SELECTION(state))
493 nk_textedit_move_to_last(state);
494 else ++state->cursor;
495 nk_textedit_clamp(state);
496 state->has_preferred_x = 0;
497 } break;
498
499 case NK_KEY_TEXT_WORD_LEFT:
500 if (shift_mod) {
501 if( !NK_TEXT_HAS_SELECTION( state ) )
502 nk_textedit_prep_selection_at_cursor(state);
503 state->cursor = nk_textedit_move_to_word_previous(state);
504 state->select_end = state->cursor;
505 nk_textedit_clamp(state );
506 } else {
507 if (NK_TEXT_HAS_SELECTION(state))
508 nk_textedit_move_to_first(state);
509 else {
510 state->cursor = nk_textedit_move_to_word_previous(state);
511 nk_textedit_clamp(state );
512 }
513 } break;
514
515 case NK_KEY_TEXT_WORD_RIGHT:
516 if (shift_mod) {
517 if( !NK_TEXT_HAS_SELECTION( state ) )
518 nk_textedit_prep_selection_at_cursor(state);
519 state->cursor = nk_textedit_move_to_word_next(state);
520 state->select_end = state->cursor;
521 nk_textedit_clamp(state);
522 } else {
523 if (NK_TEXT_HAS_SELECTION(state))
524 nk_textedit_move_to_last(state);
525 else {
526 state->cursor = nk_textedit_move_to_word_next(state);
527 nk_textedit_clamp(state );
528 }
529 } break;
530
531 case NK_KEY_DOWN: {
532 struct nk_text_find find;
533 struct nk_text_edit_row row;
534 int i, sel = shift_mod;
535
536 if (state->single_line) {
537 /* on windows, up&down in single-line behave like left&right */
538 key = NK_KEY_RIGHT;
539 goto retry;
540 }
541
542 if (sel)
543 nk_textedit_prep_selection_at_cursor(state);
544 else if (NK_TEXT_HAS_SELECTION(state))
545 nk_textedit_move_to_last(state);
546
547 /* compute current position of cursor point */
548 nk_textedit_clamp(state);
549 nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
550 font, row_height);
551
552 /* now find character position down a row */
553 if (find.length)
554 {
555 float x;
556 float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
557 int start = find.first_char + find.length;
558
559 state->cursor = start;
560 nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
561 x = row.x0;
562
563 for (i=0; i < row.num_chars && x < row.x1; ++i) {
564 float dx = nk_textedit_get_width(state, start, i, font);
565 x += dx;
566 if (x > goal_x)
567 break;
568 ++state->cursor;
569 }
570 nk_textedit_clamp(state);
571
572 state->has_preferred_x = 1;
573 state->preferred_x = goal_x;
574 if (sel)
575 state->select_end = state->cursor;
576 }
577 } break;
578
579 case NK_KEY_UP: {
580 struct nk_text_find find;
581 struct nk_text_edit_row row;
582 int i, sel = shift_mod;
583
584 if (state->single_line) {
585 /* on windows, up&down become left&right */
586 key = NK_KEY_LEFT;
587 goto retry;
588 }
589
590 if (sel)
591 nk_textedit_prep_selection_at_cursor(state);
592 else if (NK_TEXT_HAS_SELECTION(state))
593 nk_textedit_move_to_first(state);
594
595 /* compute current position of cursor point */
596 nk_textedit_clamp(state);
597 nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
598 font, row_height);
599
600 /* can only go up if there's a previous row */
601 if (find.prev_first != find.first_char) {
602 /* now find character position up a row */
603 float x;
604 float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
605
606 state->cursor = find.prev_first;
607 nk_textedit_layout_row(&row, state, state->cursor, row_height, font);
608 x = row.x0;
609
610 for (i=0; i < row.num_chars && x < row.x1; ++i) {
611 float dx = nk_textedit_get_width(state, find.prev_first, i, font);
612 x += dx;
613 if (x > goal_x)
614 break;
615 ++state->cursor;
616 }
617 nk_textedit_clamp(state);
618
619 state->has_preferred_x = 1;
620 state->preferred_x = goal_x;
621 if (sel) state->select_end = state->cursor;
622 }
623 } break;
624
625 case NK_KEY_DEL:
626 if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
627 break;
628 if (NK_TEXT_HAS_SELECTION(state))
629 nk_textedit_delete_selection(state);
630 else {
631 int n = state->string.len;
632 if (state->cursor < n)
633 nk_textedit_delete(state, state->cursor, 1);
634 }
635 state->has_preferred_x = 0;
636 break;
637
638 case NK_KEY_BACKSPACE:
639 if (state->mode == NK_TEXT_EDIT_MODE_VIEW)
640 break;
641 if (NK_TEXT_HAS_SELECTION(state))
642 nk_textedit_delete_selection(state);
643 else {
644 nk_textedit_clamp(state);
645 if (state->cursor > 0) {
646 nk_textedit_delete(state, state->cursor-1, 1);
647 --state->cursor;
648 }
649 }
650 state->has_preferred_x = 0;
651 break;
652
653 case NK_KEY_TEXT_START:
654 if (shift_mod) {
655 nk_textedit_prep_selection_at_cursor(state);
656 state->cursor = state->select_end = 0;
657 state->has_preferred_x = 0;
658 } else {
659 state->cursor = state->select_start = state->select_end = 0;
660 state->has_preferred_x = 0;
661 }
662 break;
663
664 case NK_KEY_TEXT_END:
665 if (shift_mod) {
666 nk_textedit_prep_selection_at_cursor(state);
667 state->cursor = state->select_end = state->string.len;
668 state->has_preferred_x = 0;
669 } else {
670 state->cursor = state->string.len;
671 state->select_start = state->select_end = 0;
672 state->has_preferred_x = 0;
673 }
674 break;
675
676 case NK_KEY_TEXT_LINE_START: {
677 if (shift_mod) {
678 struct nk_text_find find;
679 nk_textedit_clamp(state);
680 nk_textedit_prep_selection_at_cursor(state);
681 if (state->string.len && state->cursor == state->string.len)
682 --state->cursor;
683 nk_textedit_find_charpos(&find, state,state->cursor, state->single_line,
684 font, row_height);
685 state->cursor = state->select_end = find.first_char;
686 state->has_preferred_x = 0;
687 } else {
688 struct nk_text_find find;
689 if (state->string.len && state->cursor == state->string.len)
690 --state->cursor;
691 nk_textedit_clamp(state);
692 nk_textedit_move_to_first(state);
693 nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
694 font, row_height);
695 state->cursor = find.first_char;
696 state->has_preferred_x = 0;
697 }
698 } break;
699
700 case NK_KEY_TEXT_LINE_END: {
701 if (shift_mod) {
702 struct nk_text_find find;
703 nk_textedit_clamp(state);
704 nk_textedit_prep_selection_at_cursor(state);
705 nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
706 font, row_height);
707 state->has_preferred_x = 0;
708 state->cursor = find.first_char + find.length;
709 if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
710 --state->cursor;
711 state->select_end = state->cursor;
712 } else {
713 struct nk_text_find find;
714 nk_textedit_clamp(state);
715 nk_textedit_move_to_first(state);
716 nk_textedit_find_charpos(&find, state, state->cursor, state->single_line,
717 font, row_height);
718
719 state->has_preferred_x = 0;
720 state->cursor = find.first_char + find.length;
721 if (find.length > 0 && nk_str_rune_at(&state->string, state->cursor-1) == '\n')
722 --state->cursor;
723 }} break;
724 }
725}
726NK_INTERN void
727nk_textedit_flush_redo(struct nk_text_undo_state *state)
728{
729 state->redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
730 state->redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
731}
732NK_INTERN void
733nk_textedit_discard_undo(struct nk_text_undo_state *state)
734{
735 /* discard the oldest entry in the undo list */
736 if (state->undo_point > 0) {
737 /* if the 0th undo state has characters, clean those up */
738 if (state->undo_rec[0].char_storage >= 0) {
739 int n = state->undo_rec[0].insert_length, i;
740 /* delete n characters from all other records */
741 state->undo_char_point = (short)(state->undo_char_point - n);
742 NK_MEMCPY(state->undo_char, state->undo_char + n,
743 (nk_size)state->undo_char_point*sizeof(nk_rune));
744 for (i=0; i < state->undo_point; ++i) {
745 if (state->undo_rec[i].char_storage >= 0)
746 state->undo_rec[i].char_storage = (short)
747 (state->undo_rec[i].char_storage - n);
748 }
749 }
750 --state->undo_point;
751 NK_MEMCPY(state->undo_rec, state->undo_rec+1,
752 (nk_size)((nk_size)state->undo_point * sizeof(state->undo_rec[0])));
753 }
754}
755NK_INTERN void
756nk_textedit_discard_redo(struct nk_text_undo_state *state)
757{
758/* discard the oldest entry in the redo list--it's bad if this
759 ever happens, but because undo & redo have to store the actual
760 characters in different cases, the redo character buffer can
761 fill up even though the undo buffer didn't */
762 nk_size num;
763 int k = NK_TEXTEDIT_UNDOSTATECOUNT-1;
764 if (state->redo_point <= k) {
765 /* if the k'th undo state has characters, clean those up */
766 if (state->undo_rec[k].char_storage >= 0) {
767 int n = state->undo_rec[k].insert_length, i;
768 /* delete n characters from all other records */
769 state->redo_char_point = (short)(state->redo_char_point + n);
770 num = (nk_size)(NK_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point);
771 NK_MEMCPY(state->undo_char + state->redo_char_point,
772 state->undo_char + state->redo_char_point-n, num * sizeof(char));
773 for (i = state->redo_point; i < k; ++i) {
774 if (state->undo_rec[i].char_storage >= 0) {
775 state->undo_rec[i].char_storage = (short)
776 (state->undo_rec[i].char_storage + n);
777 }
778 }
779 }
780 ++state->redo_point;
781 num = (nk_size)(NK_TEXTEDIT_UNDOSTATECOUNT - state->redo_point);
782 if (num) NK_MEMCPY(state->undo_rec + state->redo_point-1,
783 state->undo_rec + state->redo_point, num * sizeof(state->undo_rec[0]));
784 }
785}
786NK_INTERN struct nk_text_undo_record*
787nk_textedit_create_undo_record(struct nk_text_undo_state *state, int numchars)
788{
789 /* any time we create a new undo record, we discard redo*/
790 nk_textedit_flush_redo(state);
791
792 /* if we have no free records, we have to make room,
793 * by sliding the existing records down */
794 if (state->undo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
795 nk_textedit_discard_undo(state);
796
797 /* if the characters to store won't possibly fit in the buffer,
798 * we can't undo */
799 if (numchars > NK_TEXTEDIT_UNDOCHARCOUNT) {
800 state->undo_point = 0;
801 state->undo_char_point = 0;
802 return 0;
803 }
804
805 /* if we don't have enough free characters in the buffer,
806 * we have to make room */
807 while (state->undo_char_point + numchars > NK_TEXTEDIT_UNDOCHARCOUNT)
808 nk_textedit_discard_undo(state);
809 return &state->undo_rec[state->undo_point++];
810}
811NK_INTERN nk_rune*
812nk_textedit_createundo(struct nk_text_undo_state *state, int pos,
813 int insert_len, int delete_len)
814{
815 struct nk_text_undo_record *r = nk_textedit_create_undo_record(state, insert_len);
816 if (r == 0)
817 return 0;
818
819 r->where = pos;
820 r->insert_length = (short) insert_len;
821 r->delete_length = (short) delete_len;
822
823 if (insert_len == 0) {
824 r->char_storage = -1;
825 return 0;
826 } else {
827 r->char_storage = state->undo_char_point;
828 state->undo_char_point = (short)(state->undo_char_point + insert_len);
829 return &state->undo_char[r->char_storage];
830 }
831}
832NK_API void
833nk_textedit_undo(struct nk_text_edit *state)
834{
835 struct nk_text_undo_state *s = &state->undo;
836 struct nk_text_undo_record u, *r;
837 if (s->undo_point == 0)
838 return;
839
840 /* we need to do two things: apply the undo record, and create a redo record */
841 u = s->undo_rec[s->undo_point-1];
842 r = &s->undo_rec[s->redo_point-1];
843 r->char_storage = -1;
844
845 r->insert_length = u.delete_length;
846 r->delete_length = u.insert_length;
847 r->where = u.where;
848
849 if (u.delete_length)
850 {
851 /* if the undo record says to delete characters, then the redo record will
852 need to re-insert the characters that get deleted, so we need to store
853 them.
854 there are three cases:
855 - there's enough room to store the characters
856 - characters stored for *redoing* don't leave room for redo
857 - characters stored for *undoing* don't leave room for redo
858 if the last is true, we have to bail */
859 if (s->undo_char_point + u.delete_length >= NK_TEXTEDIT_UNDOCHARCOUNT) {
860 /* the undo records take up too much character space; there's no space
861 * to store the redo characters */
862 r->insert_length = 0;
863 } else {
864 int i;
865 /* there's definitely room to store the characters eventually */
866 while (s->undo_char_point + u.delete_length > s->redo_char_point) {
867 /* there's currently not enough room, so discard a redo record */
868 nk_textedit_discard_redo(s);
869 /* should never happen: */
870 if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
871 return;
872 }
873
874 r = &s->undo_rec[s->redo_point-1];
875 r->char_storage = (short)(s->redo_char_point - u.delete_length);
876 s->redo_char_point = (short)(s->redo_char_point - u.delete_length);
877
878 /* now save the characters */
879 for (i=0; i < u.delete_length; ++i)
880 s->undo_char[r->char_storage + i] =
881 nk_str_rune_at(&state->string, u.where + i);
882 }
883 /* now we can carry out the deletion */
884 nk_str_delete_runes(&state->string, u.where, u.delete_length);
885 }
886
887 /* check type of recorded action: */
888 if (u.insert_length) {
889 /* easy case: was a deletion, so we need to insert n characters */
890 nk_str_insert_text_runes(&state->string, u.where,
891 &s->undo_char[u.char_storage], u.insert_length);
892 s->undo_char_point = (short)(s->undo_char_point - u.insert_length);
893 }
894 state->cursor = (short)(u.where + u.insert_length);
895
896 s->undo_point--;
897 s->redo_point--;
898}
899NK_API void
900nk_textedit_redo(struct nk_text_edit *state)
901{
902 struct nk_text_undo_state *s = &state->undo;
903 struct nk_text_undo_record *u, r;
904 if (s->redo_point == NK_TEXTEDIT_UNDOSTATECOUNT)
905 return;
906
907 /* we need to do two things: apply the redo record, and create an undo record */
908 u = &s->undo_rec[s->undo_point];
909 r = s->undo_rec[s->redo_point];
910
911 /* we KNOW there must be room for the undo record, because the redo record
912 was derived from an undo record */
913 u->delete_length = r.insert_length;
914 u->insert_length = r.delete_length;
915 u->where = r.where;
916 u->char_storage = -1;
917
918 if (r.delete_length) {
919 /* the redo record requires us to delete characters, so the undo record
920 needs to store the characters */
921 if (s->undo_char_point + u->insert_length > s->redo_char_point) {
922 u->insert_length = 0;
923 u->delete_length = 0;
924 } else {
925 int i;
926 u->char_storage = s->undo_char_point;
927 s->undo_char_point = (short)(s->undo_char_point + u->insert_length);
928
929 /* now save the characters */
930 for (i=0; i < u->insert_length; ++i) {
931 s->undo_char[u->char_storage + i] =
932 nk_str_rune_at(&state->string, u->where + i);
933 }
934 }
935 nk_str_delete_runes(&state->string, r.where, r.delete_length);
936 }
937
938 if (r.insert_length) {
939 /* easy case: need to insert n characters */
940 nk_str_insert_text_runes(&state->string, r.where,
941 &s->undo_char[r.char_storage], r.insert_length);
942 }
943 state->cursor = r.where + r.insert_length;
944
945 s->undo_point++;
946 s->redo_point++;
947}
948NK_INTERN void
949nk_textedit_makeundo_insert(struct nk_text_edit *state, int where, int length)
950{
951 nk_textedit_createundo(&state->undo, where, 0, length);
952}
953NK_INTERN void
954nk_textedit_makeundo_delete(struct nk_text_edit *state, int where, int length)
955{
956 int i;
957 nk_rune *p = nk_textedit_createundo(&state->undo, where, length, 0);
958 if (p) {
959 for (i=0; i < length; ++i)
960 p[i] = nk_str_rune_at(&state->string, where+i);
961 }
962}
963NK_INTERN void
964nk_textedit_makeundo_replace(struct nk_text_edit *state, int where,
965 int old_length, int new_length)
966{
967 int i;
968 nk_rune *p = nk_textedit_createundo(&state->undo, where, old_length, new_length);
969 if (p) {
970 for (i=0; i < old_length; ++i)
971 p[i] = nk_str_rune_at(&state->string, where+i);
972 }
973}
974NK_LIB void
975nk_textedit_clear_state(struct nk_text_edit *state, enum nk_text_edit_type type,
976 nk_plugin_filter filter)
977{
978 /* reset the state to default */
979 state->undo.undo_point = 0;
980 state->undo.undo_char_point = 0;
981 state->undo.redo_point = NK_TEXTEDIT_UNDOSTATECOUNT;
982 state->undo.redo_char_point = NK_TEXTEDIT_UNDOCHARCOUNT;
983 state->select_end = state->select_start = 0;
984 state->cursor = 0;
985 state->has_preferred_x = 0;
986 state->preferred_x = 0;
987 state->cursor_at_end_of_line = 0;
988 state->initialized = 1;
989 state->single_line = (unsigned char)(type == NK_TEXT_EDIT_SINGLE_LINE);
990 state->mode = NK_TEXT_EDIT_MODE_VIEW;
991 state->filter = filter;
992 state->scrollbar = nk_vec2(0,0);
993}
994NK_API void
995nk_textedit_init_fixed(struct nk_text_edit *state, void *memory, nk_size size)
996{
997 NK_ASSERT(state);
998 NK_ASSERT(memory);
999 if (!state || !memory || !size) 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_fixed(&state->string, memory, size);
1003}
1004NK_API void
1005nk_textedit_init(struct nk_text_edit *state, const struct nk_allocator *alloc, nk_size size)
1006{
1007 NK_ASSERT(state);
1008 NK_ASSERT(alloc);
1009 if (!state || !alloc) 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(&state->string, alloc, size);
1013}
1014#ifdef NK_INCLUDE_DEFAULT_ALLOCATOR
1015NK_API void
1016nk_textedit_init_default(struct nk_text_edit *state)
1017{
1018 NK_ASSERT(state);
1019 if (!state) return;
1020 NK_MEMSET(state, 0, sizeof(struct nk_text_edit));
1021 nk_textedit_clear_state(state, NK_TEXT_EDIT_SINGLE_LINE, 0);
1022 nk_str_init_default(&state->string);
1023}
1024#endif
1025NK_API void
1026nk_textedit_select_all(struct nk_text_edit *state)
1027{
1028 NK_ASSERT(state);
1029 state->select_start = 0;
1030 state->select_end = state->string.len;
1031}
1032NK_API void
1033nk_textedit_free(struct nk_text_edit *state)
1034{
1035 NK_ASSERT(state);
1036 if (!state) return;
1037 nk_str_free(&state->string);
1038}
1039
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