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);
290 edit_field_clean(edit_field);
295 Edit_field *create_edit_field(Vec font_size,
298 Lt *lt = create_lt();
300 Edit_field *const edit_field = PUSH_LT(lt, nth_calloc(1, sizeof(Edit_field)), free);
301 if (edit_field == NULL) {
306 edit_field->buffer = PUSH_LT(lt, nth_calloc(1, sizeof(char) * (BUFFER_CAPACITY + 10)), free);
307 if (edit_field->buffer == NULL) {
311 edit_field->buffer_size = 0;
312 edit_field->cursor = 0;
313 edit_field->font_size = font_size;
314 edit_field->font_color = font_color;
316 edit_field->buffer[edit_field->buffer_size] = 0;
321 void destroy_edit_field(Edit_field *edit_field)
323 trace_assert(edit_field);
324 RETURN_LT0(edit_field->lt);
327 int edit_field_render_screen(const Edit_field *edit_field,
329 Point screen_position)
331 trace_assert(edit_field);
332 trace_assert(camera);
334 const float cursor_y_overflow = 10.0f;
335 const float cursor_width = 2.0f;
337 if (camera_render_text_screen(
340 edit_field->font_size,
341 edit_field->font_color,
342 screen_position) < 0) {
346 /* TODO(#363): the size of the cursor does not correspond to font size */
347 if (camera_fill_rect_screen(
349 rect(screen_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
350 screen_position.y - cursor_y_overflow,
352 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
353 edit_field->font_color) < 0) {
360 int edit_field_render_world(const Edit_field *edit_field,
362 Point world_position)
364 trace_assert(edit_field);
365 trace_assert(camera);
367 const float cursor_y_overflow = 10.0f;
368 const float cursor_width = 2.0f;
370 if (camera_render_text(
373 edit_field->font_size,
374 edit_field->font_color,
375 world_position) < 0) {
379 if (camera_fill_rect(
381 rect(world_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
382 world_position.y - cursor_y_overflow,
384 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
385 edit_field->font_color) < 0) {
392 int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
394 trace_assert(edit_field);
397 switch (event->type) {
399 if (event->key.keysym.mod & KMOD_ALT) {
400 handle_keydown_alt(edit_field, event);
401 } else if (event->key.keysym.mod & KMOD_CTRL) {
402 handle_keydown_ctrl(edit_field, event);
404 handle_keydown(edit_field, event);
408 case SDL_TEXTINPUT: {
409 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
410 // Don't process text input if a modifier key is held
414 size_t n = strlen(event->text.text);
415 for (size_t i = 0; i < n; ++i) {
416 edit_field_insert_char(edit_field, event->text.text[i]);
424 const char *edit_field_as_text(const Edit_field *edit_field)
426 trace_assert(edit_field);
427 return edit_field->buffer;
430 void edit_field_replace(Edit_field *edit_field, const char *text)
432 trace_assert(edit_field);
434 edit_field_clean(edit_field);
440 // TODO(#983): edit_field_replace should probably use memcpy
441 size_t n = strlen(text);
442 for (size_t i = 0; i < n; ++i) {
443 edit_field_insert_char(edit_field, text[i]);
447 void edit_field_clean(Edit_field *edit_field)
449 trace_assert(edit_field);
451 edit_field->cursor = 0;
452 edit_field->buffer_size = 0;
453 edit_field->buffer[0] = 0;
456 void edit_field_restyle(Edit_field *edit_field,
460 trace_assert(edit_field);
461 edit_field->font_size = font_size;
462 edit_field->font_color = font_color;