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);
42 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event);
43 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event);
44 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event);
46 static void edit_field_insert_char(Edit_field *edit_field, char c)
48 if (edit_field->buffer_size >= BUFFER_CAPACITY) {
52 char *dest = edit_field->buffer + edit_field->cursor + 1;
53 memmove(dest, dest - 1, edit_field->buffer_size - edit_field->cursor);
55 edit_field->buffer[edit_field->cursor++] = c;
56 edit_field->buffer[++edit_field->buffer_size] = 0;
59 // See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
60 // For an explanation of the naming terminology for these helper methods
62 static bool is_emacs_word(char c)
64 // Word syntax table retrieved from Fundamental Mode, "C-h s"
65 // (This is not the complete syntax table)
66 return (c >= '$' && c <= '%')
67 || (c >= '0' && c <= '9')
68 || (c >= 'A' && c <= 'Z')
69 || (c >= 'a' && c <= 'z');
72 static void forward_char(Edit_field *edit_field)
75 if (edit_field->cursor < edit_field->buffer_size) {
80 static void backward_char(Edit_field *edit_field)
83 if (edit_field->cursor > 0) {
88 static void move_beginning_of_line(Edit_field *edit_field)
91 edit_field->cursor = 0;
94 static void move_end_of_line(Edit_field *edit_field)
97 edit_field->cursor = edit_field->buffer_size;
100 static void forward_word(Edit_field *edit_field)
102 // "M-f" or "C-<RIGHT>" or "M-<RIGHT>"
104 forward_char(edit_field);
105 if (edit_field->cursor >= edit_field->buffer_size) {
109 char current = edit_field->buffer[edit_field->cursor];
110 char preceeding = edit_field->buffer[edit_field->cursor - 1];
111 if (!is_emacs_word(current) && is_emacs_word(preceeding)) {
112 // Reached the end of the current word
118 static void backward_word(Edit_field *edit_field)
120 // "M-b" or "C-<LEFT>" or "M-<LEFT>"
122 backward_char(edit_field);
123 if (edit_field->cursor == 0) {
127 char current = edit_field->buffer[edit_field->cursor];
128 char preceeding = edit_field->buffer[edit_field->cursor - 1];
129 if (is_emacs_word(current) && !is_emacs_word(preceeding)) {
130 // Reached the start of the current word
136 static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end) {
137 trace_assert(end <= edit_field->buffer_size);
144 size_t to_delete = end - start;
145 size_t to_move = edit_field->buffer_size - end;
148 char *dest = edit_field->buffer + start;
149 memmove(dest, dest + to_delete, to_move);
152 edit_field->buffer[start + to_move] = 0;
153 edit_field->buffer_size -= to_delete;
155 edit_field->cursor = start;
158 static void delete_char(Edit_field *edit_field)
160 // "C-d" or "<Delete>"
161 if (edit_field->cursor >= edit_field->buffer_size) {
165 kill_region_and_move_cursor(edit_field, edit_field->cursor, edit_field->cursor + 1);
168 static void delete_backward_char(Edit_field *edit_field)
171 if (edit_field->cursor == 0) {
175 kill_region_and_move_cursor(edit_field, edit_field->cursor - 1, edit_field->cursor);
178 static void kill_word(Edit_field *edit_field)
180 // "M-d" or "C-<Delete>"
181 size_t start = edit_field->cursor;
182 forward_word(edit_field);
183 size_t end = edit_field->cursor;
185 kill_region_and_move_cursor(edit_field, start, end);
188 static void backward_kill_word(Edit_field *edit_field)
190 // "M-<BACKSPACE>" or "C-<BACKSPACE>" or "M-<Delete>"
191 size_t end = edit_field->cursor;
192 backward_word(edit_field);
193 size_t start = edit_field->cursor;
195 kill_region_and_move_cursor(edit_field, start, end);
198 static void kill_to_end_of_line(Edit_field *edit_field) {
200 kill_region_and_move_cursor(edit_field, edit_field->cursor,
201 edit_field->buffer_size);
204 static void handle_keydown(Edit_field *edit_field, const SDL_Event *event)
206 switch (event->key.keysym.sym) {
208 move_beginning_of_line(edit_field);
212 move_end_of_line(edit_field);
215 case SDLK_BACKSPACE: {
216 delete_backward_char(edit_field);
220 delete_char(edit_field);
224 forward_char(edit_field);
228 backward_char(edit_field);
233 static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event)
235 switch (event->key.keysym.sym) {
238 backward_kill_word(edit_field);
243 forward_word(edit_field);
248 backward_word(edit_field);
252 kill_word(edit_field);
257 static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event)
259 switch (event->key.keysym.sym) {
260 case SDLK_BACKSPACE: {
261 backward_kill_word(edit_field);
265 kill_word(edit_field);
269 forward_word(edit_field);
273 backward_word(edit_field);
277 move_beginning_of_line(edit_field);
281 move_end_of_line(edit_field);
285 forward_char(edit_field);
289 backward_char(edit_field);
293 delete_char(edit_field);
297 kill_to_end_of_line(edit_field);
302 Edit_field *create_edit_field(Vec2f font_size,
305 Lt *lt = create_lt();
307 Edit_field *const edit_field = PUSH_LT(lt, nth_calloc(1, sizeof(Edit_field)), free);
308 if (edit_field == NULL) {
313 edit_field->buffer = PUSH_LT(lt, nth_calloc(1, sizeof(char) * (BUFFER_CAPACITY + 10)), free);
314 if (edit_field->buffer == NULL) {
318 edit_field->buffer_size = 0;
319 edit_field->cursor = 0;
320 edit_field->font_size = font_size;
321 edit_field->font_color = font_color;
323 edit_field->buffer[edit_field->buffer_size] = 0;
328 void destroy_edit_field(Edit_field *edit_field)
330 trace_assert(edit_field);
331 RETURN_LT0(edit_field->lt);
334 int edit_field_render_screen(const Edit_field *edit_field,
335 const Camera *camera,
336 Vec2f screen_position)
338 trace_assert(edit_field);
339 trace_assert(camera);
341 const float cursor_y_overflow = 10.0f;
342 const float cursor_width = 2.0f;
344 camera_render_text_screen(
347 edit_field->font_size,
348 edit_field->font_color,
351 /* TODO(#363): the size of the cursor does not correspond to font size */
352 if (camera_fill_rect_screen(
354 rect(screen_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
355 screen_position.y - cursor_y_overflow,
357 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
358 edit_field->font_color) < 0) {
365 int edit_field_render_world(const Edit_field *edit_field,
366 const Camera *camera,
367 Vec2f world_position)
369 trace_assert(edit_field);
370 trace_assert(camera);
372 const float cursor_y_overflow = 10.0f;
373 const float cursor_width = 2.0f;
375 if (camera_render_text(
378 edit_field->font_size,
379 edit_field->font_color,
380 world_position) < 0) {
384 if (camera_fill_rect(
386 rect(world_position.x + (float) edit_field->cursor * (float) FONT_CHAR_WIDTH * edit_field->font_size.x,
387 world_position.y - cursor_y_overflow,
389 FONT_CHAR_HEIGHT * edit_field->font_size.y + cursor_y_overflow * 2.0f),
390 edit_field->font_color) < 0) {
397 int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
399 trace_assert(edit_field);
402 switch (event->type) {
404 if (event->key.keysym.mod & KMOD_ALT) {
405 handle_keydown_alt(edit_field, event);
406 } else if (event->key.keysym.mod & KMOD_CTRL) {
407 handle_keydown_ctrl(edit_field, event);
409 handle_keydown(edit_field, event);
413 case SDL_TEXTINPUT: {
414 if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
415 // Don't process text input if a modifier key is held
419 size_t n = strlen(event->text.text);
420 for (size_t i = 0; i < n; ++i) {
421 edit_field_insert_char(edit_field, event->text.text[i]);
429 const char *edit_field_as_text(const Edit_field *edit_field)
431 trace_assert(edit_field);
432 return edit_field->buffer;
435 void edit_field_replace(Edit_field *edit_field, const char *text)
437 trace_assert(edit_field);
439 edit_field_clean(edit_field);
445 // TODO(#983): edit_field_replace should probably use memcpy
446 size_t n = strlen(text);
447 for (size_t i = 0; i < n; ++i) {
448 edit_field_insert_char(edit_field, text[i]);
452 void edit_field_clean(Edit_field *edit_field)
454 trace_assert(edit_field);
456 edit_field->cursor = 0;
457 edit_field->buffer_size = 0;
458 edit_field->buffer[0] = 0;
461 void edit_field_restyle(Edit_field *edit_field,
465 trace_assert(edit_field);
466 edit_field->font_size = font_size;
467 edit_field->font_color = font_color;