#include <stdbool.h>
+#include <string.h>
#include "edit_field.h"
#include "game/camera.h"
char *buffer;
size_t buffer_size;
size_t cursor;
- Vec font_size;
+ Vec2f font_size;
Color font_color;
};
-static void edit_field_left(Edit_field *edit_field);
-static void edit_field_right(Edit_field *edit_field);
-static void edit_field_home(Edit_field *edit_field);
-static void edit_field_end(Edit_field *edit_field);
-static void edit_field_backspace(Edit_field *edit_field);
-static void edit_field_delete(Edit_field *edit_field);
static void edit_field_insert_char(Edit_field *edit_field, char c);
-Edit_field *create_edit_field(Vec font_size,
+// See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
+// For an explanation of the naming terminology for these helper methods
+static bool is_emacs_word(char c);
+static void forward_char(Edit_field *edit_field);
+static void backward_char(Edit_field *edit_field);
+static void move_beginning_of_line(Edit_field *edit_field);
+static void move_end_of_line(Edit_field *edit_field);
+static void forward_word(Edit_field *edit_field);
+static void backward_word(Edit_field *edit_field);
+static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end);
+static void delete_char(Edit_field *edit_field);
+static void delete_backward_char(Edit_field *edit_field);
+static void kill_word(Edit_field *edit_field);
+static void backward_kill_word(Edit_field *edit_field);
+static void kill_to_end_of_line(Edit_field *edit_field);
+
+static void handle_keydown(Edit_field *edit_field, const SDL_Event *event);
+static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event);
+static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event);
+
+static void edit_field_insert_char(Edit_field *edit_field, char c)
+{
+ if (edit_field->buffer_size >= BUFFER_CAPACITY) {
+ return;
+ }
+
+ char *dest = edit_field->buffer + edit_field->cursor + 1;
+ memmove(dest, dest - 1, edit_field->buffer_size - edit_field->cursor);
+
+ edit_field->buffer[edit_field->cursor++] = c;
+ edit_field->buffer[++edit_field->buffer_size] = 0;
+}
+
+// See: https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html
+// For an explanation of the naming terminology for these helper methods
+
+static bool is_emacs_word(char c)
+{
+ // Word syntax table retrieved from Fundamental Mode, "C-h s"
+ // (This is not the complete syntax table)
+ return (c >= '$' && c <= '%')
+ || (c >= '0' && c <= '9')
+ || (c >= 'A' && c <= 'Z')
+ || (c >= 'a' && c <= 'z');
+}
+
+static void forward_char(Edit_field *edit_field)
+{
+ // "C-f" or "<RIGHT>"
+ if (edit_field->cursor < edit_field->buffer_size) {
+ edit_field->cursor++;
+ }
+}
+
+static void backward_char(Edit_field *edit_field)
+{
+ // "C-b" or "<LEFT>"
+ if (edit_field->cursor > 0) {
+ edit_field->cursor--;
+ }
+}
+
+static void move_beginning_of_line(Edit_field *edit_field)
+{
+ // "C-a" or "<Home>"
+ edit_field->cursor = 0;
+}
+
+static void move_end_of_line(Edit_field *edit_field)
+{
+ // "C-e" or "<End>"
+ edit_field->cursor = edit_field->buffer_size;
+}
+
+static void forward_word(Edit_field *edit_field)
+{
+ // "M-f" or "C-<RIGHT>" or "M-<RIGHT>"
+ while (true) {
+ forward_char(edit_field);
+ if (edit_field->cursor >= edit_field->buffer_size) {
+ break;
+ }
+
+ char current = edit_field->buffer[edit_field->cursor];
+ char preceeding = edit_field->buffer[edit_field->cursor - 1];
+ if (!is_emacs_word(current) && is_emacs_word(preceeding)) {
+ // Reached the end of the current word
+ break;
+ }
+ }
+}
+
+static void backward_word(Edit_field *edit_field)
+{
+ // "M-b" or "C-<LEFT>" or "M-<LEFT>"
+ while (true) {
+ backward_char(edit_field);
+ if (edit_field->cursor == 0) {
+ break;
+ }
+
+ char current = edit_field->buffer[edit_field->cursor];
+ char preceeding = edit_field->buffer[edit_field->cursor - 1];
+ if (is_emacs_word(current) && !is_emacs_word(preceeding)) {
+ // Reached the start of the current word
+ break;
+ }
+ }
+}
+
+static void kill_region_and_move_cursor(Edit_field *edit_field, size_t start, size_t end) {
+ trace_assert(end <= edit_field->buffer_size);
+
+ if (end <= start) {
+ // Nothing to delete
+ return;
+ }
+
+ size_t to_delete = end - start;
+ size_t to_move = edit_field->buffer_size - end;
+
+ if (to_move > 0) {
+ char *dest = edit_field->buffer + start;
+ memmove(dest, dest + to_delete, to_move);
+ }
+
+ edit_field->buffer[start + to_move] = 0;
+ edit_field->buffer_size -= to_delete;
+
+ edit_field->cursor = start;
+}
+
+static void delete_char(Edit_field *edit_field)
+{
+ // "C-d" or "<Delete>"
+ if (edit_field->cursor >= edit_field->buffer_size) {
+ return;
+ }
+
+ kill_region_and_move_cursor(edit_field, edit_field->cursor, edit_field->cursor + 1);
+}
+
+static void delete_backward_char(Edit_field *edit_field)
+{
+ // "<BACKSPACE>"
+ if (edit_field->cursor == 0) {
+ return;
+ }
+
+ kill_region_and_move_cursor(edit_field, edit_field->cursor - 1, edit_field->cursor);
+}
+
+static void kill_word(Edit_field *edit_field)
+{
+ // "M-d" or "C-<Delete>"
+ size_t start = edit_field->cursor;
+ forward_word(edit_field);
+ size_t end = edit_field->cursor;
+
+ kill_region_and_move_cursor(edit_field, start, end);
+}
+
+static void backward_kill_word(Edit_field *edit_field)
+{
+ // "M-<BACKSPACE>" or "C-<BACKSPACE>" or "M-<Delete>"
+ size_t end = edit_field->cursor;
+ backward_word(edit_field);
+ size_t start = edit_field->cursor;
+
+ kill_region_and_move_cursor(edit_field, start, end);
+}
+
+static void kill_to_end_of_line(Edit_field *edit_field) {
+ // "C-k"
+ kill_region_and_move_cursor(edit_field, edit_field->cursor,
+ edit_field->buffer_size);
+}
+
+static void handle_keydown(Edit_field *edit_field, const SDL_Event *event)
+{
+ switch (event->key.keysym.sym) {
+ case SDLK_HOME: {
+ move_beginning_of_line(edit_field);
+ } break;
+
+ case SDLK_END: {
+ move_end_of_line(edit_field);
+ } break;
+
+ case SDLK_BACKSPACE: {
+ delete_backward_char(edit_field);
+ } break;
+
+ case SDLK_DELETE: {
+ delete_char(edit_field);
+ } break;
+
+ case SDLK_RIGHT: {
+ forward_char(edit_field);
+ } break;
+
+ case SDLK_LEFT: {
+ backward_char(edit_field);
+ } break;
+ }
+}
+
+static void handle_keydown_alt(Edit_field *edit_field, const SDL_Event *event)
+{
+ switch (event->key.keysym.sym) {
+ case SDLK_BACKSPACE:
+ case SDLK_DELETE: {
+ backward_kill_word(edit_field);
+ } break;
+
+ case SDLK_RIGHT:
+ case SDLK_f: {
+ forward_word(edit_field);
+ } break;
+
+ case SDLK_LEFT:
+ case SDLK_b: {
+ backward_word(edit_field);
+ } break;
+
+ case SDLK_d: {
+ kill_word(edit_field);
+ } break;
+ }
+}
+
+static void handle_keydown_ctrl(Edit_field *edit_field, const SDL_Event *event)
+{
+ switch (event->key.keysym.sym) {
+ case SDLK_BACKSPACE: {
+ backward_kill_word(edit_field);
+ } break;
+
+ case SDLK_DELETE: {
+ kill_word(edit_field);
+ } break;
+
+ case SDLK_RIGHT: {
+ forward_word(edit_field);
+ } break;
+
+ case SDLK_LEFT: {
+ backward_word(edit_field);
+ } break;
+
+ case SDLK_a: {
+ move_beginning_of_line(edit_field);
+ } break;
+
+ case SDLK_e: {
+ move_end_of_line(edit_field);
+ } break;
+
+ case SDLK_f: {
+ forward_char(edit_field);
+ } break;
+
+ case SDLK_b: {
+ backward_char(edit_field);
+ } break;
+
+ case SDLK_d: {
+ delete_char(edit_field);
+ } break;
+
+ case SDLK_k: {
+ kill_to_end_of_line(edit_field);
+ } break;
+ }
+}
+
+Edit_field *create_edit_field(Vec2f font_size,
Color font_color)
{
Lt *lt = create_lt();
}
int edit_field_render_screen(const Edit_field *edit_field,
- Camera *camera,
- Point screen_position)
+ const Camera *camera,
+ Vec2f screen_position)
{
trace_assert(edit_field);
trace_assert(camera);
const float cursor_y_overflow = 10.0f;
const float cursor_width = 2.0f;
- if (camera_render_text_screen(
- camera,
- edit_field->buffer,
- edit_field->font_size,
- edit_field->font_color,
- screen_position) < 0) {
- return -1;
- }
+ camera_render_text_screen(
+ camera,
+ edit_field->buffer,
+ edit_field->font_size,
+ edit_field->font_color,
+ screen_position);
/* TODO(#363): the size of the cursor does not correspond to font size */
if (camera_fill_rect_screen(
}
int edit_field_render_world(const Edit_field *edit_field,
- Camera *camera,
- Point world_position)
+ const Camera *camera,
+ Vec2f world_position)
{
trace_assert(edit_field);
trace_assert(camera);
return 0;
}
-const char *edit_field_as_text(const Edit_field *edit_field)
-{
- trace_assert(edit_field);
- return edit_field->buffer;
-}
-
-static void edit_field_left(Edit_field *edit_field)
+int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
{
trace_assert(edit_field);
- if (edit_field->cursor > 0) {
- edit_field->cursor--;
- }
-}
+ trace_assert(event);
-static void edit_field_home(Edit_field *edit_field)
-{
- trace_assert(edit_field);
- edit_field->cursor = 0;
-}
+ switch (event->type) {
+ case SDL_KEYDOWN: {
+ if (event->key.keysym.mod & KMOD_ALT) {
+ handle_keydown_alt(edit_field, event);
+ } else if (event->key.keysym.mod & KMOD_CTRL) {
+ handle_keydown_ctrl(edit_field, event);
+ } else {
+ handle_keydown(edit_field, event);
+ }
+ } break;
-static void edit_field_end(Edit_field *edit_field)
-{
- trace_assert(edit_field);
- edit_field->cursor = edit_field->buffer_size;
-}
+ case SDL_TEXTINPUT: {
+ if ((SDL_GetModState() & (KMOD_CTRL | KMOD_ALT))) {
+ // Don't process text input if a modifier key is held
+ break;
+ }
-static void edit_field_right(Edit_field *edit_field)
-{
- trace_assert(edit_field);
- if (edit_field->cursor < edit_field->buffer_size) {
- edit_field->cursor++;
+ size_t n = strlen(event->text.text);
+ for (size_t i = 0; i < n; ++i) {
+ edit_field_insert_char(edit_field, event->text.text[i]);
+ }
+ } break;
}
+
+ return 0;
}
-static void edit_field_backspace(Edit_field *edit_field)
+const char *edit_field_as_text(const Edit_field *edit_field)
{
trace_assert(edit_field);
-
- if (edit_field->cursor == 0) {
- return;
- }
-
- for (size_t i = edit_field->cursor; i < edit_field->buffer_size; ++i) {
- edit_field->buffer[i - 1] = edit_field->buffer[i];
- }
-
- edit_field->cursor--;
- edit_field->buffer[--edit_field->buffer_size] = 0;
+ return edit_field->buffer;
}
-static void edit_field_delete(Edit_field *edit_field)
+void edit_field_replace(Edit_field *edit_field, const char *text)
{
trace_assert(edit_field);
- if (edit_field->cursor >= edit_field->buffer_size) {
+ edit_field_clean(edit_field);
+
+ if (text == NULL) {
return;
}
- for (size_t i = edit_field->cursor; i < edit_field->buffer_size; ++i) {
- edit_field->buffer[i] = edit_field->buffer[i + 1];
+ // TODO(#983): edit_field_replace should probably use memcpy
+ size_t n = strlen(text);
+ for (size_t i = 0; i < n; ++i) {
+ edit_field_insert_char(edit_field, text[i]);
}
-
- edit_field->buffer[--edit_field->buffer_size] = 0;
}
-static void edit_field_insert_char(Edit_field *edit_field, char c)
+void edit_field_append(Edit_field *edit_field, const char *text)
{
- trace_assert(edit_field);
-
- if (edit_field->buffer_size >= BUFFER_CAPACITY) {
- return;
- }
-
- for (int64_t i = (int64_t) edit_field->buffer_size - 1; i >= (int64_t) edit_field->cursor; --i) {
- edit_field->buffer[i + 1] = edit_field->buffer[i];
- }
-
- edit_field->buffer[edit_field->cursor++] = c;
- edit_field->buffer[++edit_field->buffer_size] = 0;
+ size_t n = strlen(text);
+ edit_field->buffer = strcat(edit_field->buffer, text);
+ edit_field->cursor += n;
}
void edit_field_clean(Edit_field *edit_field)
edit_field->buffer[0] = 0;
}
-void edit_field_replace(Edit_field *edit_field, const char *text)
+void edit_field_restyle(Edit_field *edit_field,
+ Vec2f font_size,
+ Color font_color)
{
trace_assert(edit_field);
-
- edit_field_clean(edit_field);
-
- if (text == NULL) {
- return;
- }
-
- size_t n = strlen(text);
- for (size_t i = 0; i < n; ++i) {
- edit_field_insert_char(edit_field, text[i]);
- }
-}
-
-int edit_field_event(Edit_field *edit_field, const SDL_Event *event)
-{
- trace_assert(edit_field);
- trace_assert(event);
-
- switch (event->type) {
- case SDL_KEYDOWN: {
- switch (event->key.keysym.sym) {
- case SDLK_LEFT:
- edit_field_left(edit_field);
- break;
-
- case SDLK_RIGHT:
- edit_field_right(edit_field);
- break;
-
- case SDLK_HOME:
- edit_field_home(edit_field);
- break;
-
- case SDLK_END:
- edit_field_end(edit_field);
- break;
-
- case SDLK_BACKSPACE:
- edit_field_backspace(edit_field);
- break;
-
- case SDLK_DELETE:
- edit_field_delete(edit_field);
- break;
- }
- } break;
-
- case SDL_TEXTINPUT: {
- size_t n = strlen(event->text.text);
- for (size_t i = 0; i < n; ++i) {
- edit_field_insert_char(edit_field, event->text.text[i]);
- }
- } break;
- }
-
- return 0;
+ edit_field->font_size = font_size;
+ edit_field->font_color = font_color;
}