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);
41 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event);
42 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event);
43 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event);
45 static void edit_field_insert_char(Edit_field *edit_field, char c)
47 if (edit_field->buffer_size >= BUFFER_CAPACITY) {
51 char *dest = edit_field->buffer + edit_field->cursor + 1;
52 memmove(dest, dest - 1, edit_field->buffer_size - edit_field->cursor);
54 edit_field->buffer[edit_field->cursor++] = c;
55 edit_field->buffer[++edit_field->buffer_size] = 0;
58 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
59 // For an explanation of the naming terminology for these helper methods
61 static bool is_emacs_word(char c)
63 // Word syntax table retrieved from Fundamental Mode, "C-h s"
64 // (This is not the complete syntax table)
65 return (c >= '$' && c <= '%')
66 || (c >= '0' && c <= '9')
67 || (c >= 'A' && c <= 'Z')
68 || (c >= 'a' && c <= 'z');
71 static void forward_char(Edit_field *edit_field)
74 if (edit_field->cursor < edit_field->buffer_size) {
79 static void backward_char(Edit_field *edit_field)
82 if (edit_field->cursor > 0) {
87 static void move_beginning_of_line(Edit_field *edit_field)
90 edit_field->cursor = 0;
93 static void move_end_of_line(Edit_field *edit_field)
96 edit_field->cursor = edit_field->buffer_size;
99 static void forward_word(Edit_field *edit_field)
101 // "M-f" or "C-<RIGHT>" or "M-<RIGHT>"
103 forward_char(edit_field);
104 if (edit_field->cursor >= edit_field->buffer_size) {
108 char current = edit_field->buffer[edit_field->cursor];
109 char preceeding = edit_field->buffer[edit_field->cursor - 1];
110 if (!is_emacs_word(current) && is_emacs_word(preceeding)) {
111 // Reached the end of the current word
117 static void backward_word(Edit_field *edit_field)
119 // "M-b" or "C-<LEFT>" or "M-<LEFT>"
121 backward_char(edit_field);
122 if (edit_field->cursor == 0) {
126 char current = edit_field->buffer[edit_field->cursor];
127 char preceeding = edit_field->buffer[edit_field->cursor - 1];
128 if (is_emacs_word(current) && !is_emacs_word(preceeding)) {
129 // Reached the start of the current word
135 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end) {
136 trace_assert(end <= edit_field->buffer_size);
143 size_t to_delete = end - start;
144 size_t to_move = edit_field->buffer_size - end;
147 char *dest = edit_field->buffer + start;
148 memmove(dest, dest + to_delete, to_move);
151 edit_field->buffer[start + to_move] = 0;
152 edit_field->buffer_size -= to_delete;
154 edit_field->cursor = start;
157 static void delete_char(Edit_field *edit_field)
159 // "C-d" or "<Delete>"
160 if (edit_field->cursor >= edit_field->buffer_size) {
164 kill_region_and_move_cursor(edit_field, edit_field->cursor, edit_field->cursor + 1);
167 static void delete_backward_char(Edit_field *edit_field)
170 if (edit_field->cursor == 0) {
174 kill_region_and_move_cursor(edit_field, edit_field->cursor - 1, edit_field->cursor);
177 static void kill_word(Edit_field *edit_field)
179 // "M-d" or "C-<Delete>"
180 size_t start = edit_field->cursor;
181 forward_word(edit_field);
182 size_t end = edit_field->cursor;
184 kill_region_and_move_cursor(edit_field, start, end);
187 static void backward_kill_word(Edit_field *edit_field)
189 // "M-<BACKSPACE>" or "C-<BACKSPACE>" or "M-<Delete>"
190 size_t end = edit_field->cursor;
191 backward_word(edit_field);
192 size_t start = edit_field->cursor;
194 kill_region_and_move_cursor(edit_field, start, end);
197 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event)
199 switch (event->key.keysym.sym) {
201 move_beginning_of_line(edit_field);
205 move_end_of_line(edit_field);
208 case SDLK_BACKSPACE: {
209 delete_backward_char(edit_field);
213 delete_char(edit_field);
217 forward_char(edit_field);
221 backward_char(edit_field);
226 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event)
228 switch (event->key.keysym.sym) {
231 backward_kill_word(edit_field);
236 forward_word(edit_field);
241 backward_word(edit_field);
245 kill_word(edit_field);
250 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event)
252 switch (event->key.keysym.sym) {
253 case SDLK_BACKSPACE: {
254 backward_kill_word(edit_field);
258 kill_word(edit_field);
262 forward_word(edit_field);
266 backward_word(edit_field);
270 move_beginning_of_line(edit_field);
274 move_end_of_line(edit_field);
278 forward_char(edit_field);
282 backward_char(edit_field);
286 delete_char(edit_field);
291 Edit_field *create_edit_field(Vec font_size,
294 Lt *lt = create_lt();
296 Edit_field *const edit_field = PUSH_LT(lt, nth_calloc(1, sizeof(Edit_field)), free);
297 if (edit_field == NULL) {
302 edit_field->buffer = PUSH_LT(lt, nth_calloc(1, sizeof(char) * (BUFFER_CAPACITY + 10)), free);
303 if (edit_field->buffer == NULL) {
307 edit_field->buffer_size = 0;
308 edit_field->cursor = 0;
309 edit_field->font_size = font_size;
310 edit_field->font_color = font_color;
312 edit_field->buffer[edit_field->buffer_size] = 0;
317 void destroy_edit_field(Edit_field *edit_field)
319 trace_assert(edit_field);
320 RETURN_LT0(edit_field->lt);
323 int edit_field_render_screen(const Edit_field *edit_field,
325 Point screen_position)
327 trace_assert(edit_field);
328 trace_assert(camera);
330 const float cursor_y_overflow = 10.0f;
331 const float cursor_width = 2.0f;
333 if (camera_render_text_screen(
336 edit_field->font_size,
337 edit_field->font_color,
338 screen_position) < 0) {
342 /* TODO(#363): the size of the cursor does not correspond to font size */
343 if (camera_fill_rect_screen(
345 rect(screen_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
346 screen_position.y - cursor_y_overflow,
348 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
349 edit_field->font_color) < 0) {
356 int edit_field_render_world(const Edit_field *edit_field,
358 Point world_position)
360 trace_assert(edit_field);
361 trace_assert(camera);
363 const float cursor_y_overflow = 10.0f;
364 const float cursor_width = 2.0f;
366 if (camera_render_text(
369 edit_field->font_size,
370 edit_field->font_color,
371 world_position) < 0) {
375 if (camera_fill_rect(
377 rect(world_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
378 world_position.y - cursor_y_overflow,
380 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
381 edit_field->font_color) < 0) {
388 int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
390 trace_assert(edit_field);
393 switch (event->type) {
395 if (event->key.keysym.mod & KMOD_ALT) {
396 handle_keydown_alt(edit_field, event);
397 } else if (event->key.keysym.mod & KMOD_CTRL) {
398 handle_keydown_ctrl(edit_field, event);
400 handle_keydown(edit_field, event);
404 case SDL_TEXTINPUT: {
405 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
406 // Don't process text input if a modifier key is held
410 size_t n = strlen(event->text.text);
411 for (size_t i = 0; i < n; ++i) {
412 edit_field_insert_char(edit_field, event->text.text[i]);
420 const char *edit_field_as_text(const Edit_field *edit_field)
422 trace_assert(edit_field);
423 return edit_field->buffer;
426 void edit_field_replace(Edit_field *edit_field, const char *text)
428 trace_assert(edit_field);
430 edit_field_clean(edit_field);
436 // TODO(#983): edit_field_replace should probably use memcpy
437 size_t n = strlen(text);
438 for (size_t i = 0; i < n; ++i) {
439 edit_field_insert_char(edit_field, text[i]);
443 void edit_field_clean(Edit_field *edit_field)
445 trace_assert(edit_field);
447 edit_field->cursor = 0;
448 edit_field->buffer_size = 0;
449 edit_field->buffer[0] = 0;
452 void edit_field_restyle(Edit_field *edit_field,
456 trace_assert(edit_field);
457 edit_field->font_size = font_size;
458 edit_field->font_color = font_color;