4 #include "edit_field.h"
5 #include "game/camera.h"
6 #include "game/sprite_font.h"
7 #include "sdl/renderer.h"
9 #include "system/nth_alloc.h"
10 #include "system/stacktrace.h"
12 #define BUFFER_CAPACITY 256
24 static void edit_field_insert_char(Edit_field *edit_field, char c);
26 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
27 // For an explanation of the naming terminology for these helper methods
28 static bool is_emacs_word(char c);
29 static void forward_char(Edit_field *edit_field);
30 static void backward_char(Edit_field *edit_field);
31 static void move_beginning_of_line(Edit_field *edit_field);
32 static void move_end_of_line(Edit_field *edit_field);
33 static void forward_word(Edit_field *edit_field);
34 static void backward_word(Edit_field *edit_field);
35 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end);
36 static void delete_char(Edit_field *edit_field);
37 static void delete_backward_char(Edit_field *edit_field);
38 static void kill_word(Edit_field *edit_field);
39 static void backward_kill_word(Edit_field *edit_field);
40 static void kill_to_end_of_line(Edit_field *edit_field);
41 static void field_buffer_cut(Edit_field *edit_field);
42 static void field_buffer_copy(Edit_field *edit_field);
43 static void field_buffer_paste(Edit_field *edit_field);
45 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event);
46 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event);
47 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event);
49 static void edit_field_insert_char(Edit_field *edit_field, char c)
51 if (edit_field->buffer_size >= BUFFER_CAPACITY) {
55 char *dest = edit_field->buffer + edit_field->cursor + 1;
56 memmove(dest, dest - 1, edit_field->buffer_size - edit_field->cursor);
58 edit_field->buffer[edit_field->cursor++] = c;
59 edit_field->buffer[++edit_field->buffer_size] = 0;
63 void edit_field_insert_string(Edit_field *edit_field, const char *text)
65 size_t n = strlen(text);
66 for (size_t i = 0; i < n; ++i) {
67 edit_field_insert_char(edit_field, text[i]);
71 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
72 // For an explanation of the naming terminology for these helper methods
74 static bool is_emacs_word(char c)
76 // Word syntax table retrieved from Fundamental Mode, "C-h s"
77 // (This is not the complete syntax table)
78 return (c >= '$' && c <= '%')
79 || (c >= '0' && c <= '9')
80 || (c >= 'A' && c <= 'Z')
81 || (c >= 'a' && c <= 'z');
84 static void forward_char(Edit_field *edit_field)
87 if (edit_field->cursor < edit_field->buffer_size) {
92 static void backward_char(Edit_field *edit_field)
95 if (edit_field->cursor > 0) {
100 static void move_beginning_of_line(Edit_field *edit_field)
103 edit_field->cursor = 0;
106 static void move_end_of_line(Edit_field *edit_field)
109 edit_field->cursor = edit_field->buffer_size;
112 static void forward_word(Edit_field *edit_field)
114 // "M-f" or "C-<RIGHT>" or "M-<RIGHT>"
116 forward_char(edit_field);
117 if (edit_field->cursor >= edit_field->buffer_size) {
121 char current = edit_field->buffer[edit_field->cursor];
122 char preceeding = edit_field->buffer[edit_field->cursor - 1];
123 if (!is_emacs_word(current) && is_emacs_word(preceeding)) {
124 // Reached the end of the current word
130 static void backward_word(Edit_field *edit_field)
132 // "M-b" or "C-<LEFT>" or "M-<LEFT>"
134 backward_char(edit_field);
135 if (edit_field->cursor == 0) {
139 char current = edit_field->buffer[edit_field->cursor];
140 char preceeding = edit_field->buffer[edit_field->cursor - 1];
141 if (is_emacs_word(current) && !is_emacs_word(preceeding)) {
142 // Reached the start of the current word
148 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end) {
149 trace_assert(end <= edit_field->buffer_size);
156 size_t to_delete = end - start;
157 size_t to_move = edit_field->buffer_size - end;
160 char *dest = edit_field->buffer + start;
161 memmove(dest, dest + to_delete, to_move);
164 edit_field->buffer[start + to_move] = 0;
165 edit_field->buffer_size -= to_delete;
167 edit_field->cursor = start;
170 static void delete_char(Edit_field *edit_field)
172 // "C-d" or "<Delete>"
173 if (edit_field->cursor >= edit_field->buffer_size) {
177 kill_region_and_move_cursor(edit_field, edit_field->cursor, edit_field->cursor + 1);
180 static void delete_backward_char(Edit_field *edit_field)
183 if (edit_field->cursor == 0) {
187 kill_region_and_move_cursor(edit_field, edit_field->cursor - 1, edit_field->cursor);
190 static void kill_word(Edit_field *edit_field)
192 // "M-d" or "C-<Delete>"
193 size_t start = edit_field->cursor;
194 forward_word(edit_field);
195 size_t end = edit_field->cursor;
197 kill_region_and_move_cursor(edit_field, start, end);
200 static void backward_kill_word(Edit_field *edit_field)
202 // "M-<BACKSPACE>" or "C-<BACKSPACE>" or "M-<Delete>"
203 size_t end = edit_field->cursor;
204 backward_word(edit_field);
205 size_t start = edit_field->cursor;
207 kill_region_and_move_cursor(edit_field, start, end);
210 static void kill_to_end_of_line(Edit_field *edit_field) {
212 kill_region_and_move_cursor(edit_field, edit_field->cursor,
213 edit_field->buffer_size);
216 static void field_buffer_cut(Edit_field *edit_field) {
218 SDL_SetClipboardText(edit_field_as_text(edit_field));
219 edit_field_clean(edit_field);
222 static void field_buffer_copy(Edit_field *edit_field) {
224 SDL_SetClipboardText(edit_field_as_text(edit_field));
227 static void field_buffer_paste(Edit_field *edit_field) {
229 char *text = SDL_GetClipboardText();
230 edit_field_insert_string(edit_field, text);
233 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event)
235 switch (event->key.keysym.sym) {
237 move_beginning_of_line(edit_field);
241 move_end_of_line(edit_field);
244 case SDLK_BACKSPACE: {
245 delete_backward_char(edit_field);
249 delete_char(edit_field);
253 forward_char(edit_field);
257 backward_char(edit_field);
262 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event)
264 switch (event->key.keysym.sym) {
267 backward_kill_word(edit_field);
272 forward_word(edit_field);
277 backward_word(edit_field);
281 kill_word(edit_field);
284 // TODO(#1220): edit_field doesn't support selections for copy/cut operations
286 field_buffer_copy(edit_field);
291 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event)
293 switch (event->key.keysym.sym) {
294 case SDLK_BACKSPACE: {
295 backward_kill_word(edit_field);
299 kill_word(edit_field);
303 forward_word(edit_field);
307 backward_word(edit_field);
311 move_beginning_of_line(edit_field);
315 move_end_of_line(edit_field);
319 forward_char(edit_field);
323 backward_char(edit_field);
327 delete_char(edit_field);
331 kill_to_end_of_line(edit_field);
336 field_buffer_cut(edit_field);
341 field_buffer_paste(edit_field);
345 field_buffer_copy(edit_field);
350 Edit_field *create_edit_field(Vec2f font_size,
353 Lt *lt = create_lt();
355 Edit_field *const edit_field = PUSH_LT(lt, nth_calloc(1, sizeof(Edit_field)), free);
356 if (edit_field == NULL) {
361 edit_field->buffer = PUSH_LT(lt, nth_calloc(1, sizeof(char) * (BUFFER_CAPACITY + 10)), free);
362 if (edit_field->buffer == NULL) {
366 edit_field->buffer_size = 0;
367 edit_field->cursor = 0;
368 edit_field->font_size = font_size;
369 edit_field->font_color = font_color;
371 edit_field->buffer[edit_field->buffer_size] = 0;
376 void destroy_edit_field(Edit_field *edit_field)
378 trace_assert(edit_field);
379 RETURN_LT0(edit_field->lt);
382 int edit_field_render_screen(const Edit_field *edit_field,
383 const Camera *camera,
384 Vec2f screen_position)
386 trace_assert(edit_field);
387 trace_assert(camera);
389 const float cursor_y_overflow = 10.0f;
390 const float cursor_width = 2.0f;
392 camera_render_text_screen(
395 edit_field->font_size,
396 edit_field->font_color,
399 /* TODO(#363): the size of the cursor does not correspond to font size */
400 if (camera_fill_rect_screen(
402 rect(screen_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
403 screen_position.y - cursor_y_overflow,
405 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
406 edit_field->font_color) < 0) {
413 int edit_field_render_world(const Edit_field *edit_field,
414 const Camera *camera,
415 Vec2f world_position)
417 trace_assert(edit_field);
418 trace_assert(camera);
420 const float cursor_y_overflow = 10.0f;
421 const float cursor_width = 2.0f;
423 if (camera_render_text(
426 edit_field->font_size,
427 edit_field->font_color,
428 world_position) < 0) {
432 if (camera_fill_rect(
434 rect(world_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
435 world_position.y - cursor_y_overflow,
437 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
438 edit_field->font_color) < 0) {
445 int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
447 trace_assert(edit_field);
450 switch (event->type) {
452 if (event->key.keysym.mod & KMOD_ALT) {
453 handle_keydown_alt(edit_field, event);
454 } else if (event->key.keysym.mod & KMOD_CTRL) {
455 handle_keydown_ctrl(edit_field, event);
457 handle_keydown(edit_field, event);
461 case SDL_TEXTINPUT: {
462 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
463 // Don't process text input if a modifier key is held
467 size_t n = strlen(event->text.text);
468 for (size_t i = 0; i < n; ++i) {
469 edit_field_insert_char(edit_field, event->text.text[i]);
477 const char *edit_field_as_text(const Edit_field *edit_field)
479 trace_assert(edit_field);
480 return edit_field->buffer;
483 void edit_field_replace(Edit_field *edit_field, const char *text)
485 trace_assert(edit_field);
487 edit_field_clean(edit_field);
493 // TODO(#983): edit_field_replace should probably use memcpy
494 size_t n = strlen(text);
495 for (size_t i = 0; i < n; ++i) {
496 edit_field_insert_char(edit_field, text[i]);
500 void edit_field_clean(Edit_field *edit_field)
502 trace_assert(edit_field);
504 edit_field->cursor = 0;
505 edit_field->buffer_size = 0;
506 edit_field->buffer[0] = 0;
509 void edit_field_restyle(Edit_field *edit_field,
513 trace_assert(edit_field);
514 edit_field->font_size = font_size;
515 edit_field->font_color = font_color;