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"
13 static void edit_field_insert_char(Edit_field *edit_field, char c);
15 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
16 // For an explanation of the naming terminology for these helper methods
17 static bool is_emacs_word(char c);
18 static void forward_char(Edit_field *edit_field);
19 static void backward_char(Edit_field *edit_field);
20 static void move_beginning_of_line(Edit_field *edit_field);
21 static void move_end_of_line(Edit_field *edit_field);
22 static void forward_word(Edit_field *edit_field);
23 static void backward_word(Edit_field *edit_field);
24 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end);
25 static void delete_char(Edit_field *edit_field);
26 static void delete_backward_char(Edit_field *edit_field);
27 static void kill_word(Edit_field *edit_field);
28 static void backward_kill_word(Edit_field *edit_field);
29 static void kill_to_end_of_line(Edit_field *edit_field);
30 static void field_buffer_cut(Edit_field *edit_field);
31 static void field_buffer_copy(Edit_field *edit_field);
32 static void field_buffer_paste(Edit_field *edit_field);
34 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event);
35 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event);
36 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event);
38 static void edit_field_insert_char(Edit_field *edit_field, char c)
40 if (edit_field->buffer_size >= EDIT_FIELD_CAPACITY) {
44 char *dest = edit_field->buffer + edit_field->cursor + 1;
45 memmove(dest, dest - 1, edit_field->buffer_size - edit_field->cursor);
47 edit_field->buffer[edit_field->cursor++] = c;
48 edit_field->buffer[++edit_field->buffer_size] = 0;
52 void edit_field_insert_string(Edit_field *edit_field, const char *text)
54 size_t n = strlen(text);
55 for (size_t i = 0; i < n; ++i) {
56 edit_field_insert_char(edit_field, text[i]);
60 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
61 // For an explanation of the naming terminology for these helper methods
63 static bool is_emacs_word(char c)
65 // Word syntax table retrieved from Fundamental Mode, "C-h s"
66 // (This is not the complete syntax table)
67 return (c >= '$' && c <= '%')
68 || (c >= '0' && c <= '9')
69 || (c >= 'A' && c <= 'Z')
70 || (c >= 'a' && c <= 'z');
73 static void forward_char(Edit_field *edit_field)
76 if (edit_field->cursor < edit_field->buffer_size) {
81 static void backward_char(Edit_field *edit_field)
84 if (edit_field->cursor > 0) {
89 static void move_beginning_of_line(Edit_field *edit_field)
92 edit_field->cursor = 0;
95 static void move_end_of_line(Edit_field *edit_field)
98 edit_field->cursor = edit_field->buffer_size;
101 static void forward_word(Edit_field *edit_field)
103 // "M-f" or "C-<RIGHT>" or "M-<RIGHT>"
105 forward_char(edit_field);
106 if (edit_field->cursor >= edit_field->buffer_size) {
110 char current = edit_field->buffer[edit_field->cursor];
111 char preceeding = edit_field->buffer[edit_field->cursor - 1];
112 if (!is_emacs_word(current) && is_emacs_word(preceeding)) {
113 // Reached the end of the current word
119 static void backward_word(Edit_field *edit_field)
121 // "M-b" or "C-<LEFT>" or "M-<LEFT>"
123 backward_char(edit_field);
124 if (edit_field->cursor == 0) {
128 char current = edit_field->buffer[edit_field->cursor];
129 char preceeding = edit_field->buffer[edit_field->cursor - 1];
130 if (is_emacs_word(current) && !is_emacs_word(preceeding)) {
131 // Reached the start of the current word
137 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end) {
138 trace_assert(end <= edit_field->buffer_size);
145 size_t to_delete = end - start;
146 size_t to_move = edit_field->buffer_size - end;
149 char *dest = edit_field->buffer + start;
150 memmove(dest, dest + to_delete, to_move);
153 edit_field->buffer[start + to_move] = 0;
154 edit_field->buffer_size -= to_delete;
156 edit_field->cursor = start;
159 static void delete_char(Edit_field *edit_field)
161 // "C-d" or "<Delete>"
162 if (edit_field->cursor >= edit_field->buffer_size) {
166 kill_region_and_move_cursor(edit_field, edit_field->cursor, edit_field->cursor + 1);
169 static void delete_backward_char(Edit_field *edit_field)
172 if (edit_field->cursor == 0) {
176 kill_region_and_move_cursor(edit_field, edit_field->cursor - 1, edit_field->cursor);
179 static void kill_word(Edit_field *edit_field)
181 // "M-d" or "C-<Delete>"
182 size_t start = edit_field->cursor;
183 forward_word(edit_field);
184 size_t end = edit_field->cursor;
186 kill_region_and_move_cursor(edit_field, start, end);
189 static void backward_kill_word(Edit_field *edit_field)
191 // "M-<BACKSPACE>" or "C-<BACKSPACE>" or "M-<Delete>"
192 size_t end = edit_field->cursor;
193 backward_word(edit_field);
194 size_t start = edit_field->cursor;
196 kill_region_and_move_cursor(edit_field, start, end);
199 static void kill_to_end_of_line(Edit_field *edit_field) {
201 kill_region_and_move_cursor(edit_field, edit_field->cursor,
202 edit_field->buffer_size);
205 static void field_buffer_cut(Edit_field *edit_field) {
207 SDL_SetClipboardText(edit_field_as_text(edit_field));
208 edit_field_clean(edit_field);
211 static void field_buffer_copy(Edit_field *edit_field) {
213 SDL_SetClipboardText(edit_field_as_text(edit_field));
216 static void field_buffer_paste(Edit_field *edit_field) {
218 char *text = SDL_GetClipboardText();
219 edit_field_insert_string(edit_field, text);
222 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event)
224 switch (event->key.keysym.sym) {
226 move_beginning_of_line(edit_field);
230 move_end_of_line(edit_field);
233 case SDLK_BACKSPACE: {
234 delete_backward_char(edit_field);
238 delete_char(edit_field);
242 forward_char(edit_field);
246 backward_char(edit_field);
251 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event)
253 switch (event->key.keysym.sym) {
256 backward_kill_word(edit_field);
261 forward_word(edit_field);
266 backward_word(edit_field);
270 kill_word(edit_field);
273 // TODO(#1220): edit_field doesn't support selections for copy/cut operations
275 field_buffer_copy(edit_field);
280 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event)
282 switch (event->key.keysym.sym) {
283 case SDLK_BACKSPACE: {
284 backward_kill_word(edit_field);
288 kill_word(edit_field);
292 forward_word(edit_field);
296 backward_word(edit_field);
300 move_beginning_of_line(edit_field);
304 move_end_of_line(edit_field);
308 forward_char(edit_field);
312 backward_char(edit_field);
316 delete_char(edit_field);
320 kill_to_end_of_line(edit_field);
325 field_buffer_cut(edit_field);
330 field_buffer_paste(edit_field);
334 field_buffer_copy(edit_field);
339 int edit_field_render_screen(const Edit_field *edit_field,
340 const Camera *camera,
341 Vec2f screen_position)
343 trace_assert(edit_field);
344 trace_assert(camera);
346 const float cursor_y_overflow = 10.0f;
347 const float cursor_width = 2.0f;
349 camera_render_text_screen(
352 edit_field->font_size,
353 edit_field->font_color,
356 /* TODO(#363): the size of the cursor does not correspond to font size */
357 if (camera_fill_rect_screen(
359 rect(screen_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
360 screen_position.y - cursor_y_overflow,
362 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
363 edit_field->font_color) < 0) {
370 int edit_field_render_world(const Edit_field *edit_field,
371 const Camera *camera,
372 Vec2f world_position)
374 trace_assert(edit_field);
375 trace_assert(camera);
377 const float cursor_y_overflow = 10.0f;
378 const float cursor_width = 2.0f;
380 if (camera_render_text(
383 edit_field->font_size,
384 edit_field->font_color,
385 world_position) < 0) {
389 if (camera_fill_rect(
391 rect(world_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
392 world_position.y - cursor_y_overflow,
394 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
395 edit_field->font_color) < 0) {
402 int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
404 trace_assert(edit_field);
407 switch (event->type) {
409 if (event->key.keysym.mod & KMOD_ALT) {
410 handle_keydown_alt(edit_field, event);
411 } else if (event->key.keysym.mod & KMOD_CTRL) {
412 handle_keydown_ctrl(edit_field, event);
414 handle_keydown(edit_field, event);
418 case SDL_TEXTINPUT: {
419 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
420 // Don't process text input if a modifier key is held
424 size_t n = strlen(event->text.text);
425 for (size_t i = 0; i < n; ++i) {
426 edit_field_insert_char(edit_field, event->text.text[i]);
434 const char *edit_field_as_text(const Edit_field *edit_field)
436 trace_assert(edit_field);
437 return edit_field->buffer;
440 void edit_field_replace(Edit_field *edit_field, const char *text)
442 trace_assert(edit_field);
444 edit_field_clean(edit_field);
450 // TODO(#983): edit_field_replace should probably use memcpy
451 size_t n = strlen(text);
452 for (size_t i = 0; i < n; ++i) {
453 edit_field_insert_char(edit_field, text[i]);
457 void edit_field_clean(Edit_field *edit_field)
459 trace_assert(edit_field);
461 edit_field->cursor = 0;
462 edit_field->buffer_size = 0;
463 edit_field->buffer[0] = 0;
466 void edit_field_restyle(Edit_field *edit_field,
470 trace_assert(edit_field);
471 edit_field->font_size = font_size;
472 edit_field->font_color = font_color;