]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/rect_layer.c
Merge pull request #1093 from tsoding/1092
[nothing.git] / src / game / level / level_editor / rect_layer.c
index 8f3468920eb2fe77025555fb5e970a6ed817cfe5..3c73e7f4571bcd541b9de970702f72d1e3532948 100644 (file)
@@ -17,7 +17,7 @@
 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
 #define CREATE_AREA_THRESHOLD 10.0
 
-// TODO(#1051): RectLayer does not support copy-pasting
+
 
 static int clipboard = 0;
 static Rect clipboard_rect;
@@ -40,45 +40,109 @@ struct RectLayer {
     Dynarray *rects;
     Dynarray *colors;
     ColorPicker color_picker;
-    Vec create_begin;
-    Vec create_end;
+    Vec2f create_begin;
+    Vec2f create_end;
     int selection;
-    Vec move_anchor;
+    Vec2f move_anchor;
     Edit_field *id_edit_field;
     Color inter_color;
     Rect inter_rect;
+    int id_name_counter;
+    const char *id_name_prefix;
 };
 
 typedef enum {
     UNDO_ADD,
     UNDO_DELETE,
-    UNDO_UPDATE
+    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_context(RectLayer *rect_layer, UndoType type)
+UndoContext create_undo_add_context(RectLayer *layer, size_t index)
 {
-    trace_assert(rect_layer);
+    trace_assert(layer);
+    trace_assert(index < dynarray_count(layer->rects));
 
-    size_t index = type == UNDO_ADD ? dynarray_count(rect_layer->rects) - 1 : (size_t) rect_layer->selection;
+    UndoContext undo_context;
+    undo_context.add.type = UNDO_ADD;
+    undo_context.add.layer = layer;
+    undo_context.add.index = index;
+    return undo_context;
+}
+
+static
+UndoContext create_undo_element_context(RectLayer *layer)
+{
+    trace_assert(layer);
+    size_t index = (size_t) layer->selection;
+    trace_assert(index < dynarray_count(layer->rects));
 
     UndoContext undo_context;
-    undo_context.type = type;
-    undo_context.layer = rect_layer;
-    dynarray_copy_to(rect_layer->rects, &undo_context.rect, index);
-    dynarray_copy_to(rect_layer->colors, &undo_context.color, index);
-    dynarray_copy_to(rect_layer->ids, undo_context.id, index);
-    undo_context.index = index;
+    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;
+}
+
+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;
 }
 
@@ -89,34 +153,43 @@ void rect_layer_undo(void *context, size_t context_size)
     trace_assert(sizeof(UndoContext) == context_size);
 
     UndoContext *undo_context = context;
-    RectLayer *rect_layer = undo_context->layer;
 
     switch (undo_context->type) {
     case UNDO_ADD: {
-        dynarray_delete_at(rect_layer->rects, undo_context->index);
-        dynarray_delete_at(rect_layer->colors, undo_context->index);
-        dynarray_delete_at(rect_layer->ids, undo_context->index);
-        rect_layer->selection = -1;
+        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: {
-        dynarray_insert_before(rect_layer->rects, undo_context->index, &undo_context->rect);
-        dynarray_insert_before(rect_layer->colors, undo_context->index, &undo_context->color);
-        dynarray_insert_before(rect_layer->ids, undo_context->index, &undo_context->id);
-        rect_layer->selection = -1;
+        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: {
-        dynarray_replace_at(rect_layer->rects, undo_context->index, &undo_context->rect);
-        dynarray_replace_at(rect_layer->colors, undo_context->index, &undo_context->color);
-        dynarray_replace_at(rect_layer->ids, undo_context->index, &undo_context->id);
+        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(LAYER, HISTORY, UNDO_TYPE)                            \
+#define UNDO_PUSH(HISTORY, CONTEXT)                                     \
     do {                                                                \
-        UndoContext context = create_undo_context(LAYER, UNDO_TYPE);    \
+        UndoContext context = (CONTEXT);                                \
         undo_history_push(                                              \
             HISTORY,                                                    \
             rect_layer_undo,                                            \
@@ -124,7 +197,6 @@ void rect_layer_undo(void *context, size_t context_size)
             sizeof(context));                                           \
     } while(0)
 
-
 static int rect_layer_add_rect(RectLayer *layer,
                                Rect rect,
                                Color color,
@@ -141,29 +213,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_PUSH(layer, undo_history, UNDO_ADD);
+    UNDO_PUSH(
+        undo_history,
+        create_undo_add_context(
+            layer,
+            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)
+static int rect_layer_rect_at(RectLayer *layer, Vec2f 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;
         }
@@ -172,6 +245,20 @@ static int rect_layer_rect_at(RectLayer *layer, Vec position)
     return -1;
 }
 
+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)
 {
     const Rect overlay_rect =
@@ -192,7 +279,7 @@ static int rect_layer_delete_rect_at(RectLayer *layer,
 {
     trace_assert(layer);
 
-    UNDO_PUSH(layer, undo_history, UNDO_DELETE);
+    UNDO_PUSH(undo_history, create_undo_delete_context(layer));
 
     dynarray_delete_at(layer->rects, i);
     dynarray_delete_at(layer->colors, i);
@@ -227,7 +314,7 @@ static int rect_layer_event_idle(RectLayer *layer,
     case SDL_MOUSEBUTTONDOWN: {
         switch (event->button.button) {
         case SDL_BUTTON_LEFT: {
-            Point position = camera_map_screen(
+            Vec2f position = camera_map_screen(
                 camera,
                 event->button.x,
                 event->button.y);
@@ -237,7 +324,16 @@ static int rect_layer_event_idle(RectLayer *layer,
             Rect *rects = dynarray_data(layer->rects);
             Color *colors = dynarray_data(layer->colors);
 
-            if (rect_at_position >= 0) {
+            if (layer->selection >= 0 && rect_contains_point(
+                    rect_layer_resize_anchor(
+                        camera,
+                        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 if (rect_at_position >= 0) {
                 layer->selection = rect_at_position;
                 layer->state = RECT_LAYER_MOVE;
                 layer->move_anchor =
@@ -250,15 +346,6 @@ static int rect_layer_event_idle(RectLayer *layer,
                     create_color_picker_from_rgba(colors[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(
-                               camera,
-                               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;
 
@@ -274,6 +361,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);
@@ -311,7 +424,7 @@ static int rect_layer_event_idle(RectLayer *layer,
             if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
                 int x, y;
                 SDL_GetMouseState(&x, &y);
-                Point position = camera_map_screen(camera, x, y);
+                Vec2f position = camera_map_screen(camera, x, y);
 
                 rect_layer_add_rect(
                     layer,
@@ -396,7 +509,7 @@ static int rect_layer_event_resize(RectLayer *layer,
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-        UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
+        UNDO_PUSH(undo_history, create_undo_update_context(layer));
         dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
     } break;
     }
@@ -414,9 +527,11 @@ static int rect_layer_event_move(RectLayer *layer,
     trace_assert(camera);
     trace_assert(layer->selection >= 0);
 
+    Rect *rects = dynarray_data(layer->rects);
+
     switch (event->type) {
     case SDL_MOUSEMOTION: {
-        Point position = vec_sub(
+        Vec2f position = vec_sub(
             camera_map_screen(
                 camera,
                 event->button.x,
@@ -431,8 +546,15 @@ static int rect_layer_event_move(RectLayer *layer,
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-        UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
-        dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
+
+        float distance = vec_length(
+            vec_sub(rect_position(layer->inter_rect),
+                    rect_position(rects[layer->selection])));
+
+        if (distance > 1e-6) {
+            UNDO_PUSH(undo_history, create_undo_update_context(layer));
+            dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
+        }
     } break;
     }
     return 0;
@@ -452,7 +574,7 @@ static int rect_layer_event_id_rename(RectLayer *layer,
     case SDL_KEYDOWN: {
         switch (event->key.keysym.sym) {
         case SDLK_RETURN: {
-            UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
+            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);
@@ -481,7 +603,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();
 
@@ -527,15 +649,16 @@ RectLayer *create_rect_layer(void)
 
     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;
     }
@@ -712,7 +835,7 @@ int rect_layer_event_recolor(RectLayer *layer,
         layer->inter_color = color_picker_rgba(&layer->color_picker);
 
         if (!color_picker_drag(&layer->color_picker)) {
-            UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
+            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;
         }