]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/label_layer.c
(#1108) add development ding
[nothing.git] / src / game / level / level_editor / label_layer.c
index 8e9dcfd2b267d959c0947f5893174d01b173b66d..3b10ba3a959dc5bd8e0f90f2ff7a655699fbb1af 100644 (file)
 #include "system/lt.h"
 #include "system/str.h"
 #include "system/log.h"
-#include "math/point.h"
+#include "math/vec.h"
 #include "label_layer.h"
 #include "dynarray.h"
 #include "color.h"
 #include "game/camera.h"
+#include "color_picker.h"
+#include "ui/edit_field.h"
+#include "math/extrema.h"
 
-#define LABEL_LAYER_ID_MAX_SIZE 36
+#define LABEL_LAYER_SELECTION_THICCNESS 5.0f
+
+
+typedef enum {
+    LABEL_LAYER_IDLE = 0,
+    LABEL_LAYER_MOVE,
+    LABEL_LAYER_EDIT_TEXT,
+    LABEL_LAYER_EDIT_ID,
+    LABEL_LAYER_RECOLOR
+} LabelLayerState;
+
+static int clipboard;
+static char clipboard_text[LABEL_LAYER_TEXT_MAX_SIZE];
+static Color clipboard_color;
 
 struct LabelLayer {
     Lt *lt;
+    LabelLayerState state;
     Dynarray *ids;
     Dynarray *positions;
     Dynarray *colors;
     Dynarray *texts;
+    int selection;
+    ColorPicker color_picker;
+    Vec2f move_anchor;
+    Edit_field *edit_field;
+    Vec2f inter_position;
+    Color inter_color;
+    int id_name_counter;
+    const char *id_name_prefix;
 };
 
+typedef enum {
+    UNDO_ADD,
+    UNDO_DELETE,
+    UNDO_UPDATE,
+    UNDO_SWAP
+} UndoType;
+
+typedef struct {
+    UndoType type;
+    LabelLayer *layer;
+    char id[LABEL_LAYER_ID_MAX_SIZE];
+    Vec2f position;
+    Color color;
+    char text[LABEL_LAYER_TEXT_MAX_SIZE];
+    size_t index;
+    size_t index2;
+} UndoContext;
+
+static
+UndoContext create_undo_swap_context(LabelLayer *label_layer,
+                                     size_t index, size_t index2)
+{
+    trace_assert(label_layer);
+    trace_assert(index < dynarray_count(label_layer->positions));
+    trace_assert(index2 < dynarray_count(label_layer->positions));
+
+    UndoContext undo_context;
+    undo_context.type = UNDO_SWAP;
+    undo_context.layer = label_layer;
+    undo_context.index = index;
+    undo_context.index2 = index2;
+    return undo_context;
+}
+
+static
+UndoContext create_undo_context(LabelLayer *label_layer, UndoType type)
+{
+    trace_assert(label_layer);
+    trace_assert(type != UNDO_SWAP);
+
+    UndoContext undo_context;
+
+    size_t index = type == UNDO_ADD
+        ? dynarray_count(label_layer->positions) - 1
+        : (size_t)label_layer->selection;
+
+    undo_context.type = type;
+    undo_context.layer = label_layer;
+    dynarray_copy_to(label_layer->ids, &undo_context.id, index);
+    dynarray_copy_to(label_layer->positions, &undo_context.position, index);
+    dynarray_copy_to(label_layer->colors, &undo_context.color, index);
+    dynarray_copy_to(label_layer->texts, &undo_context.text, index);
+    undo_context.index = index;
+
+    return undo_context;
+}
+
+static
+void label_layer_undo(void *context, size_t context_size)
+{
+    trace_assert(context);
+    trace_assert(sizeof(UndoContext) == context_size);
+
+    UndoContext *undo_context = context;
+    LabelLayer *label_layer = undo_context->layer;
+
+    switch (undo_context->type) {
+    case UNDO_ADD: {
+        dynarray_delete_at(label_layer->ids, undo_context->index);
+        dynarray_delete_at(label_layer->positions, undo_context->index);
+        dynarray_delete_at(label_layer->colors, undo_context->index);
+        dynarray_delete_at(label_layer->texts, undo_context->index);
+    } break;
+
+    case UNDO_DELETE: {
+        dynarray_insert_before(label_layer->ids, undo_context->index, &undo_context->id);
+        dynarray_insert_before(label_layer->positions, undo_context->index, &undo_context->position);
+        dynarray_insert_before(label_layer->colors, undo_context->index, &undo_context->color);
+        dynarray_insert_before(label_layer->texts, undo_context->index, &undo_context->text);
+    } break;
+
+    case UNDO_UPDATE: {
+        dynarray_replace_at(label_layer->ids, undo_context->index, &undo_context->id);
+        dynarray_replace_at(label_layer->positions, undo_context->index, &undo_context->position);
+        dynarray_replace_at(label_layer->colors, undo_context->index, &undo_context->color);
+        dynarray_replace_at(label_layer->texts, undo_context->index, &undo_context->text);
+    } break;
+
+    case UNDO_SWAP: {
+        dynarray_swap(label_layer->ids, undo_context->index, undo_context->index2);
+        dynarray_swap(label_layer->positions, undo_context->index, undo_context->index2);
+        dynarray_swap(label_layer->colors, undo_context->index, undo_context->index2);
+        dynarray_swap(label_layer->texts, undo_context->index, undo_context->index2);
+    } break;
+    }
+}
+
+#define UNDO_PUSH(HISTORY, CONTEXT)                                     \
+    do {                                                                \
+        UndoContext context = (CONTEXT);                                \
+        undo_history_push(                                              \
+            HISTORY,                                                    \
+            label_layer_undo,                                           \
+            &context,                                                   \
+            sizeof(context));                                           \
+    } while(0)
+
+
 LayerPtr label_layer_as_layer(LabelLayer *label_layer)
 {
     LayerPtr layer = {
@@ -33,7 +166,7 @@ LayerPtr label_layer_as_layer(LabelLayer *label_layer)
     return layer;
 }
 
-LabelLayer *create_label_layer(void)
+LabelLayer *create_label_layer(const char *id_name_prefix)
 {
     Lt *lt = create_lt();
 
@@ -52,7 +185,7 @@ LabelLayer *create_label_layer(void)
         RETURN_LT(lt, NULL);
     }
 
-    label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray);
+    label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Vec2f)), destroy_dynarray);
     if (label_layer->positions == NULL) {
         RETURN_LT(lt, NULL);
     }
@@ -62,18 +195,34 @@ LabelLayer *create_label_layer(void)
         RETURN_LT(lt, NULL);
     }
 
-    label_layer->texts = PUSH_LT(lt, create_dynarray(sizeof(char*)), destroy_dynarray);
+    label_layer->texts = PUSH_LT(
+        lt,
+        create_dynarray(sizeof(char) * LABEL_LAYER_TEXT_MAX_SIZE),
+        destroy_dynarray);
     if (label_layer->texts == NULL) {
         RETURN_LT(lt, NULL);
     }
 
+    label_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
+    label_layer->selection = -1;
+
+    label_layer->edit_field = PUSH_LT(
+        lt,
+        create_edit_field(LABELS_SIZE, COLOR_RED),
+        destroy_edit_field);
+    if (label_layer->edit_field == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    label_layer->id_name_prefix = id_name_prefix;
+
     return label_layer;
 }
 
-LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
+LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix)
 {
     trace_assert(line_stream);
-    LabelLayer *label_layer = create_label_layer();
+    LabelLayer *label_layer = create_label_layer(id_name_prefix);
 
     if (label_layer == NULL) {
         RETURN_LT(label_layer->lt, NULL);
@@ -94,7 +243,7 @@ LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
     for (size_t i = 0; i < n; ++i) {
         char hex[7];
         char id[LABEL_LAYER_ID_MAX_SIZE];
-        Point position;
+        Vec2f position;
 
         line = line_stream_next(line_stream);
         if (line == NULL) {
@@ -121,7 +270,8 @@ LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
             log_fail("Could not read label text\n");
         }
 
-        char *label_text = PUSH_LT(label_layer->lt, string_duplicate(line, NULL), free);
+        char label_text[LABEL_LAYER_TEXT_MAX_SIZE] = {0};
+        memcpy(label_text, line, LABEL_LAYER_TEXT_MAX_SIZE - 1);
         trim_endline(label_text);
         dynarray_push(label_layer->texts, &label_text);
     }
@@ -136,28 +286,544 @@ void destroy_label_layer(LabelLayer *label_layer)
 }
 
 int label_layer_render(const LabelLayer *label_layer,
-                       Camera *camera,
-                       float fa)
+                       const Camera *camera,
+                       int active)
 {
     trace_assert(label_layer);
     trace_assert(camera);
 
+    if (active && color_picker_render(&label_layer->color_picker, camera) < 0) {
+        return -1;
+    }
+
     size_t n = dynarray_count(label_layer->ids);
-    Point *positions = dynarray_data(label_layer->positions);
+    char *ids = dynarray_data(label_layer->ids);
+    Vec2f *positions = dynarray_data(label_layer->positions);
     Color *colors = dynarray_data(label_layer->colors);
-    char **texts = dynarray_data(label_layer->texts);
+    char *texts = dynarray_data(label_layer->texts);
 
     /* TODO(#891): LabelLayer doesn't show the final position of Label after the animation */
     for (size_t i = 0; i < n; ++i) {
-        if (camera_render_text(
+        const Color color = label_layer->state == LABEL_LAYER_RECOLOR && label_layer->selection == (int) i
+            ? label_layer->inter_color
+            : colors[i];
+
+        const Vec2f position =
+            label_layer->state == LABEL_LAYER_MOVE && label_layer->selection == (int) i
+            ? label_layer->inter_position
+            : positions[i];
+
+        // Label Text
+        if (label_layer->state == LABEL_LAYER_EDIT_TEXT && label_layer->selection == (int) i) {
+            if (edit_field_render_world(
+                    label_layer->edit_field,
+                    camera,
+                    position) < 0) {
+                return -1;
+            }
+        } else {
+            if (camera_render_text(
+                    camera,
+                    texts + i * LABEL_LAYER_TEXT_MAX_SIZE,
+                    LABELS_SIZE,
+                    color_scale(
+                        color,
+                        rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
+                    position) < 0) {
+                return -1;
+            }
+        }
+
+        // Label ID
+        if (label_layer->state == LABEL_LAYER_EDIT_ID && label_layer->selection == (int)i) {
+            if (edit_field_render_world(
+                    label_layer->edit_field,
+                    camera,
+                    vec_sub(
+                        position,
+                        vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
+                return -1;
+            }
+        } else {
+            if (camera_render_text(
+                    camera,
+                    ids + i * LABEL_LAYER_ID_MAX_SIZE,
+                    vec(1.0f, 1.0f),
+                    color_scale(
+                        color_invert(color),
+                        rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
+                    vec_sub(position, vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
+                return -1;
+            }
+        }
+
+        // Label Selection
+        if (active && label_layer->selection == (int) i) {
+            Rect selection =
+                rect_scale(
+                    camera_rect(
+                        camera,
+                        rect_boundary2(
+                            sprite_font_boundary_box(
+                                camera_font(camera),
+                                position,
+                                LABELS_SIZE,
+                                texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE),
+                            sprite_font_boundary_box(
+                                camera_font(camera),
+                                vec_sub(
+                                    position,
+                                    vec(0.0f, FONT_CHAR_HEIGHT)),
+                                vec(1.0f, 1.0f),
+                                ids + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE))),
+                    LABEL_LAYER_SELECTION_THICCNESS * 0.5f);
+
+
+            if (camera_draw_thicc_rect_screen(
+                    camera,
+                    selection,
+                    color,
+                    LABEL_LAYER_SELECTION_THICCNESS) < 0) {
+                return -1;
+            }
+        }
+    }
+
+
+    return 0;
+}
+
+static
+int label_layer_element_at(LabelLayer *label_layer,
+                           const Sprite_font *font,
+                           Vec2f position)
+{
+    trace_assert(label_layer);
+
+    const int n = (int) dynarray_count(label_layer->texts);
+    char *ids = dynarray_data(label_layer->ids);
+    char *texts = dynarray_data(label_layer->texts);
+    Vec2f *positions = dynarray_data(label_layer->positions);
+
+    for (int i = n - 1; i >= 0; --i) {
+        Rect boundary = rect_boundary2(
+            sprite_font_boundary_box(
+                font,
+                positions[i],
+                LABELS_SIZE,
+                texts + i * LABEL_LAYER_TEXT_MAX_SIZE),
+            sprite_font_boundary_box(
+                font,
+                vec_sub(
+                    positions[i],
+                    vec(0.0f, FONT_CHAR_HEIGHT)),
+                vec(1.0f, 1.0f),
+                ids + i * LABEL_LAYER_ID_MAX_SIZE));
+
+        if (rect_contains_point(boundary, position)) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+static
+void label_layer_delete_selected_label(LabelLayer *label_layer,
+                                       UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(label_layer->selection >= 0);
+
+    UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_DELETE));
+
+    dynarray_delete_at(label_layer->ids, (size_t)label_layer->selection);
+    dynarray_delete_at(label_layer->positions, (size_t)label_layer->selection);
+    dynarray_delete_at(label_layer->colors, (size_t)label_layer->selection);
+    dynarray_delete_at(label_layer->texts, (size_t)label_layer->selection);
+
+    label_layer->selection = -1;
+}
+
+static
+int label_layer_add_label(LabelLayer *label_layer,
+                          Vec2f position,
+                          Color color,
+                          const char *text,
+                          UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+
+    // TODO(#982): id generation code is duplicated in label_layer, point_layer and rect_layer
+    char id[LABEL_LAYER_ID_MAX_SIZE];
+    snprintf(id, LABEL_LAYER_ID_MAX_SIZE, "%s_%d",
+             label_layer->id_name_prefix,
+             label_layer->id_name_counter++);
+
+    size_t n = dynarray_count(label_layer->ids);
+
+    dynarray_push(label_layer->ids, id);
+    dynarray_push(label_layer->positions, &position);
+    dynarray_push(label_layer->colors, &color);
+    dynarray_push_empty(label_layer->texts);
+    memcpy(
+        dynarray_pointer_at(label_layer->texts, n),
+        text,
+        min_size_t(LABEL_LAYER_ID_MAX_SIZE - 1, strlen(text)));
+
+    UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_ADD));
+
+    return (int) n;
+}
+
+static
+void label_layer_swap_elements(LabelLayer *label_layer,
+                               size_t a, size_t b,
+                               UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(undo_history);
+    trace_assert(a < dynarray_count(label_layer->positions));
+    trace_assert(b < dynarray_count(label_layer->positions));
+
+    dynarray_swap(label_layer->ids, a, b);
+    dynarray_swap(label_layer->positions, a, b);
+    dynarray_swap(label_layer->colors, a, b);
+    dynarray_swap(label_layer->texts, a, b);
+
+    UNDO_PUSH(undo_history, create_undo_swap_context(label_layer, a, b));
+}
+
+static
+int label_layer_idle_event(LabelLayer *label_layer,
+                           const SDL_Event *event,
+                           const Camera *camera,
+                           UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+
+
+    int changed = 0;
+    if (color_picker_event(
+            &label_layer->color_picker,
+            event,
+            camera,
+            &changed) < 0) {
+        return -1;
+    }
+
+    if (changed) {
+        if (label_layer->selection >= 0) {
+            label_layer->state = LABEL_LAYER_RECOLOR;
+            label_layer->inter_color = color_picker_rgba(&label_layer->color_picker);
+        }
+        return 0;
+    }
+
+    Color *colors = dynarray_data(label_layer->colors);
+    Vec2f *positions = dynarray_data(label_layer->positions);
+    char *ids = dynarray_data(label_layer->ids);
+    char *texts = dynarray_data(label_layer->texts);
+
+    switch (event->type) {
+    case SDL_MOUSEBUTTONDOWN: {
+        switch (event->button.button) {
+        case SDL_BUTTON_LEFT: {
+            const Vec2f position = camera_map_screen(
+                camera,
+                event->button.x,
+                event->button.y);
+
+            const int element = label_layer_element_at(
+                label_layer,
+                camera_font(camera),
+                position);
+
+            if (element >= 0) {
+                label_layer->move_anchor = vec_sub(position, positions[element]);
+                label_layer->selection = element;
+                label_layer->state = LABEL_LAYER_MOVE;
+                label_layer->inter_position = positions[element];
+
+                label_layer->color_picker =
+                    create_color_picker_from_rgba(colors[element]);
+            } else {
+                label_layer->selection = label_layer_add_label(
+                    label_layer,
+                    position,
+                    color_picker_rgba(
+                        &label_layer->color_picker),
+                    "",
+                    undo_history);
+                label_layer->state = LABEL_LAYER_EDIT_TEXT;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    LABELS_SIZE,
+                    colors[label_layer->selection]);
+                SDL_StartTextInput();
+            }
+        } break;
+        }
+    } break;
+
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_UP: {
+            if ((event->key.keysym.mod & KMOD_SHIFT)
+                && (label_layer->selection >= 0)
+                && ((size_t)(label_layer->selection + 1) < dynarray_count(label_layer->positions))) {
+                label_layer_swap_elements(
+                    label_layer,
+                    (size_t) label_layer->selection,
+                    (size_t) label_layer->selection + 1,
+                    undo_history);
+                label_layer->selection++;
+            }
+        } break;
+
+        case SDLK_DOWN: {
+            if ((event->key.keysym.mod & KMOD_SHIFT)
+                && (label_layer->selection > 0)
+                && ((size_t) label_layer->selection < dynarray_count(label_layer->positions))) {
+                label_layer_swap_elements(
+                    label_layer,
+                    (size_t) label_layer->selection,
+                    (size_t) label_layer->selection - 1,
+                    undo_history);
+                label_layer->selection--;
+            }
+        } break;
+
+        case SDLK_F2: {
+            if (label_layer->selection >= 0) {
+                label_layer->state = LABEL_LAYER_EDIT_TEXT;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    LABELS_SIZE,
+                    colors[label_layer->selection]);
+                SDL_StartTextInput();
+            }
+        } break;
+
+        case SDLK_F3: {
+            if (label_layer->selection >= 0) {
+                label_layer->state = LABEL_LAYER_EDIT_ID;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    ids + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    vec(1.0f, 1.0f),
+                    color_invert(colors[label_layer->selection]));
+                SDL_StartTextInput();
+            }
+        } break;
+
+        case SDLK_DELETE: {
+            if (label_layer->selection >= 0) {
+                label_layer_delete_selected_label(
+                    label_layer,
+                    undo_history);
+                label_layer->selection = -1;
+            }
+        } break;
+
+        case SDLK_c: {
+            if ((event->key.keysym.mod & KMOD_LCTRL) && label_layer->selection >= 0) {
+                clipboard = 1;
+                dynarray_copy_to(label_layer->texts, clipboard_text, (size_t)label_layer->selection);
+                dynarray_copy_to(label_layer->colors, &clipboard_color, (size_t)label_layer->selection);
+            }
+        } break;
+
+        case SDLK_v: {
+            if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
+                int x, y;
+                SDL_GetMouseState(&x, &y);
+                Vec2f position = camera_map_screen(camera, x, y);
+
+                label_layer_add_label(
+                    label_layer,
+                    position,
+                    clipboard_color,
+                    clipboard_text,
+                    undo_history);
+            }
+        } break;
+        }
+    } break;
+    }
+
+    return 0;
+}
+
+static
+int label_layer_move_event(LabelLayer *label_layer,
+                           const SDL_Event *event,
+                           const Camera *camera,
+                           UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(label_layer->selection >= 0);
+
+    Vec2f *positions = dynarray_data(label_layer->positions);
+
+    switch (event->type) {
+    case SDL_MOUSEMOTION: {
+        label_layer->inter_position = vec_sub(
+            camera_map_screen(
                 camera,
-                texts[i],
-                vec(2.0f, 2.0f),
-                color_scale(
-                    colors[i],
-                    rgba(1.0f, 1.0f, 1.0f, fa)),
-                positions[i]) < 0) {
-            return -1;
+                event->motion.x,
+                event->motion.y),
+            label_layer->move_anchor);
+    } break;
+
+    case SDL_MOUSEBUTTONUP: {
+        switch (event->button.button) {
+        case SDL_BUTTON_LEFT: {
+            const float distance = vec_length(
+                vec_sub(label_layer->inter_position,
+                        positions[label_layer->selection]));
+
+            if (distance > 1e-6) {
+                UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
+                dynarray_replace_at(
+                    label_layer->positions,
+                    (size_t)label_layer->selection,
+                    &label_layer->inter_position);
+            }
+
+            label_layer->state = LABEL_LAYER_IDLE;
+        } break;
+        }
+    } break;
+    }
+
+    return 0;
+}
+
+static
+int label_layer_edit_text_event(LabelLayer *label_layer,
+                                const SDL_Event *event,
+                                const Camera *camera,
+                                UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(label_layer->selection >= 0);
+
+    switch (event->type) {
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_RETURN: {
+            UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
+            char *text =
+                (char*)dynarray_data(label_layer->texts) + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE;
+            memset(text, 0, LABEL_LAYER_TEXT_MAX_SIZE);
+            memcpy(text, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_TEXT_MAX_SIZE - 1);
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+
+        case SDLK_ESCAPE: {
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+        }
+    } break;
+    }
+
+    return edit_field_event(label_layer->edit_field, event);
+}
+
+static
+int label_layer_edit_id_event(LabelLayer *label_layer,
+                              const SDL_Event *event,
+                              const Camera *camera,
+                              UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(undo_history);
+    trace_assert(label_layer->selection >= 0);
+
+    switch (event->type) {
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_RETURN: {
+            UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
+            char *id =
+                (char*)dynarray_data(label_layer->ids) + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE;
+            memset(id, 0, LABEL_LAYER_ID_MAX_SIZE);
+            memcpy(id, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_ID_MAX_SIZE - 1);
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+
+        case SDLK_ESCAPE: {
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+        }
+    } break;
+    }
+
+    return edit_field_event(label_layer->edit_field, event);
+}
+
+static
+int label_layer_recolor_event(LabelLayer *label_layer,
+                              const SDL_Event *event,
+                              const Camera *camera,
+                              UndoHistory *undo_history)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(undo_history);
+    trace_assert(label_layer->selection >= 0);
+
+    int changed = 0;
+
+    if (color_picker_event(
+            &label_layer->color_picker,
+            event,
+            camera,
+            &changed) < 0) {
+        return -1;
+    }
+
+    if (changed) {
+        label_layer->inter_color =
+            color_picker_rgba(&label_layer->color_picker);
+
+        if (!color_picker_drag(&label_layer->color_picker)) {
+            UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
+            dynarray_replace_at(
+                label_layer->colors,
+                (size_t) label_layer->selection,
+                &label_layer->inter_color);
+            label_layer->state = LABEL_LAYER_IDLE;
         }
     }
 
@@ -166,12 +832,31 @@ int label_layer_render(const LabelLayer *label_layer,
 
 int label_layer_event(LabelLayer *label_layer,
                       const SDL_Event *event,
-                      const Camera *camera)
+                      const Camera *camera,
+                      UndoHistory *undo_history)
 {
     trace_assert(label_layer);
     trace_assert(event);
     trace_assert(camera);
-    /* TODO(#892): LabelLayer doesn't allow to modify and add labels */
+    trace_assert(undo_history);
+
+    switch (label_layer->state) {
+    case LABEL_LAYER_IDLE:
+        return label_layer_idle_event(label_layer, event, camera, undo_history);
+
+    case LABEL_LAYER_MOVE:
+        return label_layer_move_event(label_layer, event, camera, undo_history);
+
+    case LABEL_LAYER_EDIT_TEXT:
+        return label_layer_edit_text_event(label_layer, event, camera, undo_history);
+
+    case LABEL_LAYER_EDIT_ID:
+        return label_layer_edit_id_event(label_layer, event, camera, undo_history);
+
+    case LABEL_LAYER_RECOLOR:
+        return label_layer_recolor_event(label_layer, event, camera, undo_history);
+    }
+
     return 0;
 }
 
@@ -185,7 +870,7 @@ char *label_layer_ids(const LabelLayer *label_layer)
     return dynarray_data(label_layer->ids);
 }
 
-Point *label_layer_positions(const LabelLayer *label_layer)
+Vec2f *label_layer_positions(const LabelLayer *label_layer)
 {
     return dynarray_data(label_layer->positions);
 }
@@ -195,7 +880,7 @@ Color *label_layer_colors(const LabelLayer *label_layer)
     return dynarray_data(label_layer->colors);
 }
 
-char **labels_layer_texts(const LabelLayer *label_layer)
+char *labels_layer_texts(const LabelLayer *label_layer)
 {
     return dynarray_data(label_layer->texts);
 }
@@ -207,9 +892,9 @@ int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump)
 
     size_t n = dynarray_count(label_layer->ids);
     char *ids = dynarray_data(label_layer->ids);
-    Point *positions = dynarray_data(label_layer->positions);
+    Vec2f *positions = dynarray_data(label_layer->positions);
     Color *colors = dynarray_data(label_layer->colors);
-    char **texts = dynarray_data(label_layer->texts);
+    char *texts = dynarray_data(label_layer->texts);
 
     fprintf(filedump, "%zd\n", n);
     for (size_t i = 0; i < n; ++i) {
@@ -217,7 +902,7 @@ int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump)
                 ids + LABEL_LAYER_ID_MAX_SIZE * i,
                 positions[i].x, positions[i].y);
         color_hex_to_stream(colors[i], filedump);
-        fprintf(filedump, "\n%s\n", texts[i]);
+        fprintf(filedump, "\n%s\n", texts + i * LABEL_LAYER_TEXT_MAX_SIZE);
     }
 
     return 0;