]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/rect_layer.c
Remove TODO(#956)
[nothing.git] / src / game / level / level_editor / rect_layer.c
index 657a64611e568db608969ea1db0cbbc914da5320..18b29a9114256caa2c89795268e3fd0260f69577 100644 (file)
 #include "ui/edit_field.h"
 #include "undo_history.h"
 
-#define RECT_LAYER_ID_MAX_SIZE 36
 #define RECT_LAYER_SELECTION_THICCNESS 10.0f
+#define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
 #define CREATE_AREA_THRESHOLD 10.0
 
-// TODO: Can we use a single Context for everything in RectLayer
+// TODO(#1075): there is no way to modify z order in Rect, Point and Label Layers
+
+// TODO(#1051): RectLayer does not support copy-pasting
+
+static int clipboard = 0;
+static Rect clipboard_rect;
+static Color clipboard_color;
 
 typedef enum {
     RECT_LAYER_IDLE = 0,
@@ -25,8 +31,8 @@ typedef enum {
     // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
     RECT_LAYER_RESIZE,
     RECT_LAYER_MOVE,
-    // TODO: id renaming in RectLayer is ugly
     RECT_LAYER_ID_RENAME,
+    RECT_LAYER_RECOLOR
 } RectLayerState;
 
 struct RectLayer {
@@ -41,27 +47,158 @@ struct RectLayer {
     int selection;
     Vec move_anchor;
     Edit_field *id_edit_field;
-    Color prev_color;
-    Rect prev_rect;
+    Color inter_color;
+    Rect inter_rect;
+    int id_name_counter;
+    const char *id_name_prefix;
 };
 
-typedef int (*EventHandler)(RectLayer *layer, const SDL_Event *event, const Camera *camera);
+typedef enum {
+    UNDO_ADD,
+    UNDO_DELETE,
+    UNDO_UPDATE,
+    UNDO_SWAP
+} UndoType;
+
+// Delete, Update
+typedef struct {
+    UndoType type;
+    RectLayer *layer;
+    size_t index;
+    Rect rect;
+    Color color;
+    char id[RECT_LAYER_ID_MAX_SIZE];
+} UndoElementContext;
+
+// Add
+typedef struct {
+    UndoType type;
+    RectLayer *layer;
+    size_t index;
+} UndoAddContext;
+
+// Swap
+typedef struct {
+    UndoType type;
+    RectLayer *layer;
+    size_t index1;
+    size_t index2;
+} UndoSwapContext;
+
+typedef union {
+    UndoType type;
+    UndoAddContext add;
+    UndoElementContext element;
+    UndoSwapContext swap;
+} UndoContext;
+
+static
+UndoContext create_undo_add_context(RectLayer *layer, size_t index)
+{
+    trace_assert(layer);
+    trace_assert(index < dynarray_count(layer->rects));
+
+    UndoContext undo_context;
+    undo_context.add.type = UNDO_ADD;
+    undo_context.add.layer = layer;
+    undo_context.add.index = index;
+    return undo_context;
+}
 
 static
-void rect_layer_undo_add(void *layer, Context context)
+UndoContext create_undo_element_context(RectLayer *layer)
 {
     trace_assert(layer);
-    RectLayer *rect_layer = layer;
+    size_t index = (size_t) layer->selection;
+    trace_assert(index < dynarray_count(layer->rects));
+
+    UndoContext undo_context;
+    undo_context.element.layer = layer;
+    undo_context.element.index = index;
+    dynarray_copy_to(layer->rects, &undo_context.element.rect, index);
+    dynarray_copy_to(layer->colors, &undo_context.element.color, index);
+    dynarray_copy_to(layer->ids, undo_context.element.id, index);
+    return undo_context;
+}
+
+static
+UndoContext create_undo_update_context(RectLayer *rect_layer)
+{
+    UndoContext undo_context = create_undo_element_context(rect_layer);
+    undo_context.type = UNDO_UPDATE;
+    return undo_context;
+}
+
+static
+UndoContext create_undo_delete_context(RectLayer *rect_layer)
+{
+    UndoContext undo_context = create_undo_element_context(rect_layer);
+    undo_context.type = UNDO_DELETE;
+    return undo_context;
+}
 
-    trace_assert(sizeof(size_t) < CONTEXT_SIZE);
-    size_t *index = (size_t *)context.data;
+static
+UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
+{
+    UndoContext undo_context;
+    undo_context.swap.type = UNDO_SWAP;
+    undo_context.swap.layer = rect_layer;
+    undo_context.swap.index1 = index1;
+    undo_context.swap.index2 = index2;
+    return undo_context;
+}
 
-    trace_assert(*index < dynarray_count(rect_layer->rects));
-    dynarray_delete_at(rect_layer->rects, *index);
-    dynarray_delete_at(rect_layer->colors, *index);
-    dynarray_delete_at(rect_layer->ids, *index);
+static
+void rect_layer_undo(void *context, size_t context_size)
+{
+    trace_assert(context);
+    trace_assert(sizeof(UndoContext) == context_size);
+
+    UndoContext *undo_context = context;
+
+    switch (undo_context->type) {
+    case UNDO_ADD: {
+        RectLayer *layer = undo_context->add.layer;
+        dynarray_delete_at(layer->rects, undo_context->add.index);
+        dynarray_delete_at(layer->colors, undo_context->add.index);
+        dynarray_delete_at(layer->ids, undo_context->add.index);
+        layer->selection = -1;
+    } break;
+
+    case UNDO_DELETE: {
+        RectLayer *layer = undo_context->element.layer;
+        dynarray_insert_before(layer->rects, undo_context->element.index, &undo_context->element.rect);
+        dynarray_insert_before(layer->colors, undo_context->element.index, &undo_context->element.color);
+        dynarray_insert_before(layer->ids, undo_context->element.index, &undo_context->element.id);
+        layer->selection = -1;
+    } break;
+
+    case UNDO_UPDATE: {
+        RectLayer *layer = undo_context->element.layer;
+        dynarray_replace_at(layer->rects, undo_context->element.index, &undo_context->element.rect);
+        dynarray_replace_at(layer->colors, undo_context->element.index, &undo_context->element.color);
+        dynarray_replace_at(layer->ids, undo_context->element.index, &undo_context->element.id);
+    } break;
+
+    case UNDO_SWAP: {
+        RectLayer *layer = undo_context->element.layer;
+        dynarray_swap(layer->rects, undo_context->swap.index1, undo_context->swap.index2);
+        dynarray_swap(layer->colors, undo_context->swap.index1, undo_context->swap.index2);
+        dynarray_swap(layer->ids, undo_context->swap.index1, undo_context->swap.index2);
+    } break;
+    }
 }
 
+#define UNDO_PUSH(HISTORY, CONTEXT)                                     \
+    do {                                                                \
+        UndoContext context = (CONTEXT);                                \
+        undo_history_push(                                              \
+            HISTORY,                                                    \
+            rect_layer_undo,                                            \
+            &context,                                                   \
+            sizeof(context));                                           \
+    } while(0)
+
 static int rect_layer_add_rect(RectLayer *layer,
                                Rect rect,
                                Color color,
@@ -69,8 +206,6 @@ static int rect_layer_add_rect(RectLayer *layer,
 {
     trace_assert(layer);
 
-    size_t index = dynarray_count(layer->rects);
-
     if (dynarray_push(layer->rects, &rect) < 0) {
         return -1;
     }
@@ -80,34 +215,30 @@ static int rect_layer_add_rect(RectLayer *layer,
     }
 
     char id[RECT_LAYER_ID_MAX_SIZE];
-    for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
-        id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
-    }
-    id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
-
+    snprintf(id, RECT_LAYER_ID_MAX_SIZE, "%s_%d",
+             layer->id_name_prefix,
+             layer->id_name_counter++);
     if (dynarray_push(layer->ids, id)) {
         return -1;
     }
 
-    undo_history_push(
+    UNDO_PUSH(
         undo_history,
-        create_action(
+        create_undo_add_context(
             layer,
-            rect_layer_undo_add,
-            &index, sizeof(index)));
+            dynarray_count(layer->rects) - 1));
 
     return 0;
 }
 
-// TODO(#956): rect_layer_rect_at doesn't return rectangles according to some z-order
 static int rect_layer_rect_at(RectLayer *layer, Vec position)
 {
     trace_assert(layer);
 
-    const size_t n = dynarray_count(layer->rects);
+    int n = (int) dynarray_count(layer->rects);
     Rect *rects = dynarray_data(layer->rects);
 
-    for (size_t i = 0; i < n; ++i) {
+    for (int i = n - 1; i >= 0; --i) {
         if (rect_contains_point(rects[i], position)) {
             return (int) i;
         }
@@ -116,12 +247,25 @@ static int rect_layer_rect_at(RectLayer *layer, Vec position)
     return -1;
 }
 
-static Rect rect_layer_resize_anchor(const RectLayer *layer, const Camera *camera, size_t i)
+static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
+                                     UndoHistory *undo_history)
+{
+    trace_assert(layer);
+    trace_assert(a < dynarray_count(layer->rects));
+    trace_assert(b < dynarray_count(layer->rects));
+
+    dynarray_swap(layer->rects, a, b);
+    dynarray_swap(layer->colors, a, b);
+    dynarray_swap(layer->ids, a, b);
+
+    UNDO_PUSH(undo_history, create_undo_swap_context(layer, a, b));
+}
+
+static Rect rect_layer_resize_anchor(const Camera *camera, Rect boundary_rect)
 {
-    Rect *rects = dynarray_data(layer->rects);
     const Rect overlay_rect =
         rect_scale(
-            camera_rect(camera, rects[i]),
+            camera_rect(camera, boundary_rect),
             RECT_LAYER_SELECTION_THICCNESS * 0.5f);
 
     return rect(
@@ -131,62 +275,13 @@ static Rect rect_layer_resize_anchor(const RectLayer *layer, const Camera *camer
         RECT_LAYER_SELECTION_THICCNESS * 2.0f);
 }
 
-typedef struct {
-    Rect rect;
-    Color color;
-    char id[RECT_LAYER_ID_MAX_SIZE];
-    size_t index;
-} DeleteContext;
-
-static
-DeleteContext rect_layer_create_delete_context(RectLayer *layer, size_t index)
-{
-    trace_assert(layer);
-    trace_assert(index < dynarray_count(layer->rects));
-
-    DeleteContext context = {
-        .rect = *((Rect *)dynarray_pointer_at(layer->rects, index)),
-        .color = *((Color *)dynarray_pointer_at(layer->colors, index)),
-    };
-
-    memcpy(
-        context.id,
-        dynarray_pointer_at(layer->ids, index),
-        RECT_LAYER_ID_MAX_SIZE);
-
-    context.index = index;
-
-    return context;
-}
-
-static
-void rect_layer_undo_delete(void *layer, Context context)
-{
-    trace_assert(layer);
-    RectLayer *rect_layer = layer;
-
-    trace_assert(sizeof(DeleteContext) < CONTEXT_SIZE);
-    DeleteContext *delete_context = (DeleteContext *)context.data;
-
-    dynarray_insert_before(rect_layer->rects, delete_context->index, &delete_context->rect);
-    dynarray_insert_before(rect_layer->colors, delete_context->index, &delete_context->color);
-    dynarray_insert_before(rect_layer->ids, delete_context->index, &delete_context->id);
-}
-
 static int rect_layer_delete_rect_at(RectLayer *layer,
                                      size_t i,
                                      UndoHistory *undo_history)
 {
     trace_assert(layer);
 
-    DeleteContext context = rect_layer_create_delete_context(layer, i);
-
-    undo_history_push(
-        undo_history,
-        create_action(
-            layer,
-            rect_layer_undo_delete,
-            &context, sizeof(context)));
+    UNDO_PUSH(undo_history, create_undo_delete_context(layer));
 
     dynarray_delete_at(layer->rects, i);
     dynarray_delete_at(layer->colors, i);
@@ -204,6 +299,19 @@ static int rect_layer_event_idle(RectLayer *layer,
     trace_assert(event);
     trace_assert(camera);
 
+    int color_changed = 0;
+    if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
+        return -1;
+    }
+
+    if (color_changed) {
+        if (layer->selection >= 0) {
+            dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
+            layer->state = RECT_LAYER_RECOLOR;
+        }
+        return 0;
+    }
+
     switch (event->type) {
     case SDL_MOUSEBUTTONDOWN: {
         switch (event->button.button) {
@@ -215,9 +323,10 @@ static int rect_layer_event_idle(RectLayer *layer,
             int rect_at_position =
                 rect_layer_rect_at(layer, position);
 
+            Rect *rects = dynarray_data(layer->rects);
+            Color *colors = dynarray_data(layer->colors);
+
             if (rect_at_position >= 0) {
-                Rect *rects = dynarray_data(layer->rects);
-                Color *colors = dynarray_data(layer->colors);
                 layer->selection = rect_at_position;
                 layer->state = RECT_LAYER_MOVE;
                 layer->move_anchor =
@@ -228,17 +337,17 @@ static int rect_layer_event_idle(RectLayer *layer,
                             rects[layer->selection].y));
                 layer->color_picker =
                     create_color_picker_from_rgba(colors[rect_at_position]);
-                layer->prev_color = colors[rect_at_position];
-                layer->prev_rect = rects[rect_at_position];
+
+                dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
             } else if (layer->selection >= 0 && rect_contains_point(
                            rect_layer_resize_anchor(
-                               layer,
                                camera,
-                               (size_t)layer->selection),
+                               rects[layer->selection]),
                            vec(
                                (float) event->button.x,
                                (float) event->button.y))) {
                 layer->state = RECT_LAYER_RESIZE;
+                dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
             } else {
                 layer->selection = rect_at_position;
 
@@ -254,6 +363,32 @@ static int rect_layer_event_idle(RectLayer *layer,
 
     case SDL_KEYDOWN: {
         switch (event->key.keysym.sym) {
+        case SDLK_UP: {
+            if ((event->key.keysym.mod & KMOD_SHIFT)
+                && (layer->selection >= 0)
+                && ((size_t)(layer->selection + 1) < dynarray_count(layer->rects))) {
+                rect_layer_swap_elements(
+                    layer,
+                    (size_t) layer->selection,
+                    (size_t) layer->selection + 1,
+                    undo_history);
+                layer->selection++;
+            }
+        } break;
+
+        case SDLK_DOWN: {
+            if ((event->key.keysym.mod & KMOD_SHIFT)
+                && (layer->selection > 0)
+                && ((size_t) layer->selection < dynarray_count(layer->rects))) {
+                rect_layer_swap_elements(
+                    layer,
+                    (size_t) layer->selection,
+                    (size_t) layer->selection - 1,
+                    undo_history);
+                layer->selection--;
+            }
+        } break;
+
         case SDLK_DELETE: {
             if (layer->selection >= 0) {
                 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
@@ -264,6 +399,13 @@ static int rect_layer_event_idle(RectLayer *layer,
         case SDLK_F2: {
             if (layer->selection >= 0) {
                 const char *ids = dynarray_data(layer->ids);
+                Color *colors = dynarray_data(layer->colors);
+
+                edit_field_restyle(
+                    layer->id_edit_field,
+                    RECT_LAYER_ID_LABEL_SIZE,
+                    color_invert(colors[layer->selection]));
+
                 layer->state = RECT_LAYER_ID_RENAME;
                 edit_field_replace(
                     layer->id_edit_field,
@@ -271,6 +413,29 @@ static int rect_layer_event_idle(RectLayer *layer,
                 SDL_StartTextInput();
             }
         } break;
+
+        case SDLK_c: {
+            if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
+                clipboard = 1;
+                dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
+                dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
+            }
+        } break;
+
+        case SDLK_v: {
+            if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
+                int x, y;
+                SDL_GetMouseState(&x, &y);
+                Point position = camera_map_screen(camera, x, y);
+
+                rect_layer_add_rect(
+                    layer,
+                    rect(position.x, position.y,
+                         clipboard_rect.w, clipboard_rect.h),
+                    clipboard_color,
+                    undo_history);
+            }
+        } break;
         }
     } break;
     }
@@ -321,27 +486,6 @@ static int rect_layer_event_create(RectLayer *layer,
     return 0;
 }
 
-typedef struct {
-    size_t index;
-    Rect rect;
-} RectContext;
-
-static
-void rect_layer_undo_rect(void *layer, Context context)
-{
-    trace_assert(layer);
-    RectLayer *rect_layer = layer;
-
-    trace_assert(sizeof(RectContext) <= CONTEXT_SIZE);
-    RectContext *rect_context = (RectContext *)context.data;
-    trace_assert(rect_context->index < dynarray_count(rect_layer->rects));
-
-    dynarray_replace_at(
-        rect_layer->rects,
-        rect_context->index,
-        &rect_context->rect);
-}
-
 static int rect_layer_event_resize(RectLayer *layer,
                                    const SDL_Event *event,
                                    const Camera *camera,
@@ -350,14 +494,12 @@ static int rect_layer_event_resize(RectLayer *layer,
     trace_assert(layer);
     trace_assert(event);
     trace_assert(camera);
-
-    Rect *rects = dynarray_data(layer->rects);
+    trace_assert(layer->selection >= 0);
 
     switch (event->type) {
     case SDL_MOUSEMOTION: {
-        trace_assert(layer->selection >= 0);
-        rects[layer->selection] = rect_from_points(
-            vec(rects[layer->selection].x, rects[layer->selection].y),
+        layer->inter_rect = rect_from_points(
+            vec(layer->inter_rect.x, layer->inter_rect.y),
             vec_sum(
                 camera_map_screen(
                     camera,
@@ -369,20 +511,8 @@ static int rect_layer_event_resize(RectLayer *layer,
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-
-        RectContext context = {
-            .index = (size_t) layer->selection,
-            .rect = layer->prev_rect
-        };
-
-        undo_history_push(
-            undo_history,
-            create_action(
-                layer,
-                rect_layer_undo_rect,
-                &context, sizeof(context)));
-
-        layer->prev_rect = rects[layer->selection];
+        UNDO_PUSH(undo_history, create_undo_update_context(layer));
+        dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
     } break;
     }
 
@@ -397,8 +527,7 @@ static int rect_layer_event_move(RectLayer *layer,
     trace_assert(layer);
     trace_assert(event);
     trace_assert(camera);
-
-    Rect *rects = dynarray_data(layer->rects);
+    trace_assert(layer->selection >= 0);
 
     switch (event->type) {
     case SDL_MOUSEMOTION: {
@@ -411,50 +540,19 @@ static int rect_layer_event_move(RectLayer *layer,
 
         trace_assert(layer->selection >= 0);
 
-        rects[layer->selection].x = position.x;
-        rects[layer->selection].y = position.y;
+        layer->inter_rect.x = position.x;
+        layer->inter_rect.y = position.y;
     } break;
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-
-        RectContext context = {
-            .index = (size_t)layer->selection,
-            .rect = layer->prev_rect
-        };
-
-        undo_history_push(
-            undo_history,
-            create_action(
-                layer,
-                rect_layer_undo_rect,
-                &context, sizeof(context)));
-
-        layer->prev_rect = rects[layer->selection];
+        UNDO_PUSH(undo_history, create_undo_update_context(layer));
+        dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
     } break;
     }
     return 0;
 }
 
-typedef struct {
-    size_t index;
-    char id[RECT_LAYER_ID_MAX_SIZE];
-} RenameContext;
-
-static
-void rect_layer_undo_rename_id(void *layer, Context context)
-{
-    trace_assert(layer);
-    RectLayer *rect_layer = layer;
-
-    RenameContext *rename_context = (RenameContext *)context.data;
-
-    dynarray_replace_at(
-        rect_layer->ids,
-        rename_context->index,
-        rename_context->id);
-}
-
 static int rect_layer_event_id_rename(RectLayer *layer,
                                       const SDL_Event *event,
                                       const Camera *camera,
@@ -463,25 +561,15 @@ static int rect_layer_event_id_rename(RectLayer *layer,
     trace_assert(layer);
     trace_assert(event);
     trace_assert(camera);
+    trace_assert(layer->selection >= 0);
 
     switch (event->type) {
     case SDL_KEYDOWN: {
         switch (event->key.keysym.sym) {
         case SDLK_RETURN: {
-            char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
-
-            RenameContext context = {
-                .index = (size_t)layer->selection,
-            };
-            memcpy(context.id, id, RECT_LAYER_ID_MAX_SIZE);
-
-            undo_history_push(
-                undo_history,
-                create_action(
-                    layer,
-                    rect_layer_undo_rename_id,
-                    &context, sizeof(context)));
+            UNDO_PUSH(undo_history, create_undo_update_context(layer));
 
+            char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
             memset(id, 0, RECT_LAYER_ID_MAX_SIZE);
             memcpy(id, edit_field_as_text(layer->id_edit_field), RECT_LAYER_ID_MAX_SIZE - 1);
             layer->state = RECT_LAYER_IDLE;
@@ -508,7 +596,7 @@ LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
     return layer;
 }
 
-RectLayer *create_rect_layer(void)
+RectLayer *create_rect_layer(const char *id_name_prefix)
 {
     Lt *lt = create_lt();
 
@@ -545,26 +633,25 @@ RectLayer *create_rect_layer(void)
     layer->id_edit_field = PUSH_LT(
         lt,
         create_edit_field(
-            vec(3.0f, 3.0f),
+            RECT_LAYER_ID_LABEL_SIZE,
             COLOR_BLACK),
         destroy_edit_field);
     if (layer->id_edit_field == NULL) {
         RETURN_LT(lt, NULL);
     }
 
-    Color init_color = rgba(1.0f, 0.0f, 0.0f, 1.0f);
-    layer->color_picker = create_color_picker_from_rgba(init_color);
-    layer->prev_color = init_color;
+    layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
     layer->selection = -1;
+    layer->id_name_prefix = id_name_prefix;
 
     return layer;
 }
 
-RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
+RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix)
 {
     trace_assert(line_stream);
 
-    RectLayer *layer = create_rect_layer();
+    RectLayer *layer = create_rect_layer(id_name_prefix);
     if (layer == NULL) {
         return NULL;
     }
@@ -614,7 +701,7 @@ void destroy_rect_layer(RectLayer *layer)
     RETURN_LT0(layer->lt);
 }
 
-int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
+int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
 {
     trace_assert(layer);
     trace_assert(camera);
@@ -626,72 +713,89 @@ int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
 
     // The Rectangles
     for (size_t i = 0; i < n; ++i) {
-        if (camera_fill_rect(
-                camera,
-                rects[i],
-                color_scale(
-                    colors[i],
-                    rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
-            return -1;
-        }
-    }
+        Rect rect = rects[i];
+        Color color = colors[i];
 
-    // Proto Rectangle
-    const Color color = color_picker_rgba(&layer->color_picker);
-    if (layer->state == RECT_LAYER_CREATE) {
-        if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
-            return -1;
-        }
-    }
+        if (layer->selection == (int) i) {
+            if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
+                rect = layer->inter_rect;
+            }
 
-    // ID renaming Edit Field
-    if (layer->state == RECT_LAYER_ID_RENAME) {
-        if (edit_field_render_screen(layer->id_edit_field, camera, vec(400.0f, 400.0f)) < 0) {
-            return -1;
+            if (layer->state == RECT_LAYER_RECOLOR) {
+                color = layer->inter_color;
+            }
         }
-    }
-
-    // Selection Overlay
-    if (active && layer->selection >= 0) {
-        const Rect overlay_rect =
-            rect_scale(
-                camera_rect(camera, rects[layer->selection]),
-                RECT_LAYER_SELECTION_THICCNESS * 0.5f);
-        const Color overlay_color = color_invert(colors[layer->selection]);
 
-        // Main Rectangle
         if (camera_fill_rect(
                 camera,
-                rects[layer->selection],
+                rect,
                 color_scale(
-                    colors[layer->selection],
+                    color,
                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
             return -1;
         }
 
-        if (camera_draw_thicc_rect_screen(
-                camera,
-                overlay_rect,
-                overlay_color,
-                RECT_LAYER_SELECTION_THICCNESS) < 0) {
-            return -1;
-        }
+        // Selection Overlay
+        if (active && (size_t) layer->selection == i) {
+            const Rect overlay_rect =
+                rect_scale(
+                    camera_rect(camera, rect),
+                    RECT_LAYER_SELECTION_THICCNESS * 0.5f);
+            const Color overlay_color = color_invert(color);
 
-        // Rectangle Id
-        if (camera_render_text(
-                camera,
-                ids + layer->selection * RECT_LAYER_ID_MAX_SIZE,
-                vec(3.0f, 3.0f),
-                color_invert(colors[layer->selection]),
-                rect_position(rects[layer->selection])) < 0) {
-            return -1;
+            // Main Rectangle
+            if (camera_fill_rect(
+                    camera,
+                    rect,
+                    color_scale(
+                        color,
+                        rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
+                return -1;
+            }
+
+            if (camera_draw_thicc_rect_screen(
+                    camera,
+                    overlay_rect,
+                    overlay_color,
+                    RECT_LAYER_SELECTION_THICCNESS) < 0) {
+                return -1;
+            }
+
+            // Rectangle Id
+            if (layer->state == RECT_LAYER_ID_RENAME) {
+                // ID renaming Edit Field
+                if (edit_field_render_world(
+                        layer->id_edit_field,
+                        camera,
+                        rect_position(rect)) < 0) {
+                    return -1;
+                }
+            } else {
+                // Id text
+                if (camera_render_text(
+                        camera,
+                        ids + layer->selection * RECT_LAYER_ID_MAX_SIZE,
+                        RECT_LAYER_ID_LABEL_SIZE,
+                        color_invert(color),
+                        rect_position(rect)) < 0) {
+                    return -1;
+                }
+            }
+
+            // Resize Anchor
+            if (camera_fill_rect_screen(
+                    camera,
+                    rect_layer_resize_anchor(camera, rect),
+                    overlay_color) < 0) {
+                return -1;
+            }
         }
+    }
 
-        // Resize Anchor
-        if (camera_fill_rect_screen(
-                camera,
-                rect_layer_resize_anchor(layer, camera, (size_t) layer->selection),
-                overlay_color) < 0) {
+    // Proto Rectangle
+    const Color color = color_picker_rgba(&layer->color_picker);
+    if (layer->state == RECT_LAYER_CREATE) {
+        if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
             return -1;
         }
     }
@@ -703,25 +807,34 @@ int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
     return 0;
 }
 
-typedef struct {
-    Color color;
-    size_t index;
-} ColorContext;
-
 static
-void rect_layer_undo_color(void *layer, Context context)
+int rect_layer_event_recolor(RectLayer *layer,
+                             const SDL_Event *event,
+                             const Camera *camera,
+                             UndoHistory *undo_history)
 {
     trace_assert(layer);
-    RectLayer *rect_layer = layer;
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(undo_history);
+    trace_assert(layer->selection >= 0);
 
-    trace_assert(sizeof(ColorContext) < CONTEXT_SIZE);
-    ColorContext *color_context = (ColorContext *)context.data;
-    trace_assert(color_context->index < dynarray_count(rect_layer->rects));
+    int color_changed = 0;
+    if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
+        return -1;
+    }
 
-    dynarray_replace_at(
-        rect_layer->colors,
-        color_context->index,
-        &color_context->color);
+    if (color_changed) {
+        layer->inter_color = color_picker_rgba(&layer->color_picker);
+
+        if (!color_picker_drag(&layer->color_picker)) {
+            UNDO_PUSH(undo_history, create_undo_update_context(layer));
+            dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
+            layer->state = RECT_LAYER_IDLE;
+        }
+    }
+
+    return 0;
 }
 
 int rect_layer_event(RectLayer *layer,
@@ -733,36 +846,6 @@ int rect_layer_event(RectLayer *layer,
     trace_assert(event);
     trace_assert(undo_history);
 
-    int color_changed = 0;
-    if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
-        return -1;
-    }
-
-    if (color_changed) {
-        if (layer->selection >= 0) {
-            Color *colors = dynarray_data(layer->colors);
-            colors[layer->selection] = color_picker_rgba(&layer->color_picker);
-
-            if (!color_picker_drag(&layer->color_picker)) {
-                ColorContext context = {
-                    .color = layer->prev_color,
-                    .index = (size_t) layer->selection
-                };
-
-                undo_history_push(
-                    undo_history,
-                    create_action(
-                        layer,
-                        rect_layer_undo_color,
-                        &context,
-                        sizeof(context)));
-                layer->prev_color = colors[layer->selection];
-            }
-        }
-
-        return 0;
-    }
-
     switch (layer->state) {
     case RECT_LAYER_IDLE:
         return rect_layer_event_idle(layer, event, camera, undo_history);
@@ -778,6 +861,9 @@ int rect_layer_event(RectLayer *layer,
 
     case RECT_LAYER_ID_RENAME:
         return rect_layer_event_id_rename(layer, event, camera, undo_history);
+
+    case RECT_LAYER_RECOLOR:
+        return rect_layer_event_recolor(layer, event, camera, undo_history);
     }
 
     return 0;