]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/rect_layer.c
(#1118) Try to fix warnings on MacOS build
[nothing.git] / src / game / level / level_editor / rect_layer.c
index 18b29a9114256caa2c89795268e3fd0260f69577..cdac135cbb8f48225ef0f14aaf1c4e0a22fee1c4 100644 (file)
@@ -1,3 +1,5 @@
+#include <errno.h>
+
 #include "game/camera.h"
 #include "system/lt.h"
 #include "system/stacktrace.h"
 #include "system/str.h"
 #include "ui/edit_field.h"
 #include "undo_history.h"
+#include "game/level/action.h"
+#include "action_picker.h"
 
 #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(#1075): there is no way to modify z order in Rect, Point and Label Layers
-
-// TODO(#1051): RectLayer does not support copy-pasting
+#define RECT_LAYER_GRID_ROWS 3
+#define RECT_LAYER_GRID_COLUMNS 4
 
 static int clipboard = 0;
 static Rect clipboard_rect;
@@ -41,16 +43,20 @@ struct RectLayer {
     Dynarray *ids;
     Dynarray *rects;
     Dynarray *colors;
+    Dynarray *actions;
     ColorPicker color_picker;
-    Vec create_begin;
-    Vec create_end;
+    ActionPicker action_picker;
+    Vec2f create_begin;
+    Vec2f create_end;
     int selection;
-    Vec move_anchor;
+    Vec2f move_anchor;          // The mouse offset from the left-top
+                                // corner of the rect during moving it
     Edit_field *id_edit_field;
     Color inter_color;
     Rect inter_rect;
     int id_name_counter;
     const char *id_name_prefix;
+    Grid *grid;
 };
 
 typedef enum {
@@ -67,7 +73,8 @@ typedef struct {
     size_t index;
     Rect rect;
     Color color;
-    char id[RECT_LAYER_ID_MAX_SIZE];
+    Action action;
+    char id[ENTITY_MAX_ID_SIZE];
 } UndoElementContext;
 
 // Add
@@ -118,6 +125,7 @@ UndoContext create_undo_element_context(RectLayer *layer)
     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);
+    dynarray_copy_to(layer->actions, &undo_context.element.action, index);
     return undo_context;
 }
 
@@ -162,6 +170,7 @@ void rect_layer_undo(void *context, size_t context_size)
         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);
+        dynarray_delete_at(layer->actions, undo_context->add.index);
         layer->selection = -1;
     } break;
 
@@ -170,6 +179,7 @@ void rect_layer_undo(void *context, size_t context_size)
         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);
+        dynarray_insert_before(layer->actions, undo_context->element.index, &undo_context->element.action);
         layer->selection = -1;
     } break;
 
@@ -178,6 +188,7 @@ void rect_layer_undo(void *context, size_t context_size)
         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);
+        dynarray_replace_at(layer->actions, undo_context->element.index, &undo_context->element.action);
     } break;
 
     case UNDO_SWAP: {
@@ -185,6 +196,7 @@ void rect_layer_undo(void *context, size_t context_size)
         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);
+        dynarray_swap(layer->actions, undo_context->swap.index1, undo_context->swap.index2);
     } break;
     }
 }
@@ -214,14 +226,16 @@ static int rect_layer_add_rect(RectLayer *layer,
         return -1;
     }
 
-    char id[RECT_LAYER_ID_MAX_SIZE];
-    snprintf(id, RECT_LAYER_ID_MAX_SIZE, "%s_%d",
+    char id[ENTITY_MAX_ID_SIZE];
+    snprintf(id, ENTITY_MAX_ID_SIZE, "%s_%d",
              layer->id_name_prefix,
              layer->id_name_counter++);
     if (dynarray_push(layer->ids, id)) {
         return -1;
     }
 
+    dynarray_push_empty(layer->actions);
+
     UNDO_PUSH(
         undo_history,
         create_undo_add_context(
@@ -231,7 +245,7 @@ static int rect_layer_add_rect(RectLayer *layer,
     return 0;
 }
 
-static int rect_layer_rect_at(RectLayer *layer, Vec position)
+static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
 {
     trace_assert(layer);
 
@@ -257,22 +271,19 @@ static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
     dynarray_swap(layer->rects, a, b);
     dynarray_swap(layer->colors, a, b);
     dynarray_swap(layer->ids, a, b);
+    dynarray_swap(layer->actions, 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 =
-        rect_scale(
-            camera_rect(camera, boundary_rect),
-            RECT_LAYER_SELECTION_THICCNESS * 0.5f);
-
+    const Rect overlay_rect = camera_rect(camera, boundary_rect);
     return rect(
         overlay_rect.x + overlay_rect.w,
         overlay_rect.y + overlay_rect.h,
-        RECT_LAYER_SELECTION_THICCNESS * 2.0f,
-        RECT_LAYER_SELECTION_THICCNESS * 2.0f);
+        RECT_LAYER_SELECTION_THICCNESS * 2.0,
+        RECT_LAYER_SELECTION_THICCNESS * 2.0);
 }
 
 static int rect_layer_delete_rect_at(RectLayer *layer,
@@ -286,6 +297,7 @@ static int rect_layer_delete_rect_at(RectLayer *layer,
     dynarray_delete_at(layer->rects, i);
     dynarray_delete_at(layer->colors, i);
     dynarray_delete_at(layer->ids, i);
+    dynarray_delete_at(layer->actions, i);
 
     return 0;
 }
@@ -316,7 +328,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);
@@ -326,7 +338,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 =
@@ -339,15 +360,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;
 
@@ -409,7 +421,7 @@ static int rect_layer_event_idle(RectLayer *layer,
                 layer->state = RECT_LAYER_ID_RENAME;
                 edit_field_replace(
                     layer->id_edit_field,
-                    ids + layer->selection * RECT_LAYER_ID_MAX_SIZE);
+                    ids + layer->selection * ENTITY_MAX_ID_SIZE);
                 SDL_StartTextInput();
             }
         } break;
@@ -426,7 +438,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,
@@ -529,25 +541,48 @@ 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(
+        const Uint8 *state = SDL_GetKeyboardState(NULL);
+        const Vec2f mouse_pos = vec_sub(
             camera_map_screen(
                 camera,
                 event->button.x,
                 event->button.y),
             layer->move_anchor);
 
-        trace_assert(layer->selection >= 0);
+        if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
+            layer->inter_rect.x = mouse_pos.x;
+            layer->inter_rect.y = mouse_pos.y;
+        } else {
+            const Vec2f rect_pos = rect_position(rects[layer->selection]);
+
+            const float dx = fabsf(rect_pos.x - mouse_pos.x);
+            const float dy = fabsf(rect_pos.y - mouse_pos.y);
 
-        layer->inter_rect.x = position.x;
-        layer->inter_rect.y = position.y;
+            if (dx > dy) {
+                layer->inter_rect.x = mouse_pos.x;
+                layer->inter_rect.y = rect_pos.y;
+            } else {
+                layer->inter_rect.x = rect_pos.x;
+                layer->inter_rect.y = mouse_pos.y;
+            }
+        }
     } break;
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-        UNDO_PUSH(undo_history, create_undo_update_context(layer));
-        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;
@@ -570,8 +605,8 @@ static int rect_layer_event_id_rename(RectLayer *layer,
             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);
+            memset(id, 0, ENTITY_MAX_ID_SIZE);
+            memcpy(id, edit_field_as_text(layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1);
             layer->state = RECT_LAYER_IDLE;
             SDL_StopTextInput();
         } break;
@@ -608,7 +643,7 @@ RectLayer *create_rect_layer(const char *id_name_prefix)
 
     layer->ids = PUSH_LT(
         lt,
-        create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
+        create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE),
         destroy_dynarray);
     if (layer->ids == NULL) {
         RETURN_LT(lt, NULL);
@@ -630,6 +665,14 @@ RectLayer *create_rect_layer(const char *id_name_prefix)
         RETURN_LT(lt, NULL);
     }
 
+    layer->actions = PUSH_LT(
+        lt,
+        create_dynarray(sizeof(Action)),
+        destroy_dynarray);
+    if (layer->actions == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
     layer->id_edit_field = PUSH_LT(
         lt,
         create_edit_field(
@@ -640,6 +683,20 @@ RectLayer *create_rect_layer(const char *id_name_prefix)
         RETURN_LT(lt, NULL);
     }
 
+    layer->grid =
+        PUSH_LT(
+            lt,
+            nth_calloc(
+                1,
+                sizeof(Grid) + sizeof(Widget*) * RECT_LAYER_GRID_ROWS * RECT_LAYER_GRID_COLUMNS),
+            free);
+    if (layer->grid == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+    layer->grid->rows = RECT_LAYER_GRID_ROWS;
+    layer->grid->columns = RECT_LAYER_GRID_COLUMNS;
+    grid_put_widget(layer->grid, &layer->action_picker.widget, 0, RECT_LAYER_GRID_COLUMNS - 1);
+
     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;
@@ -674,22 +731,48 @@ RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream, const cha
 
         char hex[7];
         Rect rect;
-        char id[RECT_LAYER_ID_MAX_SIZE];
+        char id[ENTITY_MAX_ID_SIZE];
 
+        int n = 0;
         if (sscanf(line,
-                   "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
+                   "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n",
                    id,
                    &rect.x, &rect.y,
                    &rect.w, &rect.h,
-                   hex) < 0) {
+                   hex, &n) <= 0) {
+            log_fail("%s\n", strerror(errno));
             RETURN_LT(layer->lt, NULL);
         }
+        line += n;
 
         Color color = hexstr(hex);
-
         dynarray_push(layer->rects, &rect);
         dynarray_push(layer->ids, id);
         dynarray_push(layer->colors, &color);
+
+        Action action = {
+            .type = ACTION_NONE,
+            .entity_id = {0}
+        };
+
+        if (sscanf(line, "%d%n", (int*)&action.type, &n) > 0) {
+            line += n;
+            switch (action.type) {
+            case ACTION_NONE: break;
+
+            case ACTION_TOGGLE_GOAL:
+            case ACTION_HIDE_LABEL: {
+                if (sscanf(line, "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s", action.entity_id) <= 0) {
+                    log_fail("%s\n", strerror(errno));
+                    RETURN_LT(layer->lt, NULL);
+                }
+            } break;
+
+            case ACTION_N: break;
+            }
+        }
+
+        dynarray_push(layer->actions, &action);
     }
 
     return layer;
@@ -726,6 +809,7 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
             }
         }
 
+        // Main Rectangle
         if (camera_fill_rect(
                 camera,
                 rect,
@@ -734,62 +818,70 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 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);
+    // Selection Overlay
+    if (active && layer->selection >= 0) {
+        Rect rect = rects[layer->selection];
+        Color color = colors[layer->selection];
 
-            // 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 (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
+            rect = layer->inter_rect;
+        }
+
+        if (layer->state == RECT_LAYER_RECOLOR) {
+            color = layer->inter_color;
+        }
+
+        const Rect overlay_rect =
+            rect_pad(
+                camera_rect(camera, rect),
+                -RECT_LAYER_SELECTION_THICCNESS * 0.5f);
+        const Color overlay_color = color_invert(color);
+
+        // Selection
+        if (camera_draw_thicc_rect_screen(
+                camera,
+                overlay_rect,
+                overlay_color,
+                RECT_LAYER_SELECTION_THICCNESS) < 0) {
+            return -1;
+        }
 
-            if (camera_draw_thicc_rect_screen(
+        const Vec2f rect_id_pos = vec_sub(
+            rect_position(rect),
+            vec_mult(
+                RECT_LAYER_ID_LABEL_SIZE,
+                vec(0.0f, FONT_CHAR_HEIGHT)));
+
+        // Rectangle Id
+        if (layer->state == RECT_LAYER_ID_RENAME) {
+            // ID renaming Edit Field
+            if (edit_field_render_world(
+                    layer->id_edit_field,
                     camera,
-                    overlay_rect,
-                    overlay_color,
-                    RECT_LAYER_SELECTION_THICCNESS) < 0) {
+                    rect_id_pos) < 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(
+        } else {
+            // Id text
+            if (camera_render_text(
                     camera,
-                    rect_layer_resize_anchor(camera, rect),
-                    overlay_color) < 0) {
+                    ids + layer->selection * ENTITY_MAX_ID_SIZE,
+                    RECT_LAYER_ID_LABEL_SIZE,
+                    color_invert(color),
+                    rect_id_pos) < 0) {
                 return -1;
             }
         }
+
+        // Resize Anchor
+        if (camera_fill_rect_screen(
+                camera,
+                rect_layer_resize_anchor(camera, rect),
+                overlay_color) < 0) {
+            return -1;
+        }
     }
 
     // Proto Rectangle
@@ -846,6 +938,18 @@ int rect_layer_event(RectLayer *layer,
     trace_assert(event);
     trace_assert(undo_history);
 
+    switch (event->type) {
+    case SDL_WINDOWEVENT: {
+        switch (event->window.event) {
+        case SDL_WINDOWEVENT_RESIZED: {
+            grid_relayout(layer->grid, rect(0.0f, 0.0f,
+                                            (float) event->window.data1,
+                                            (float) event->window.data2));
+        } break;
+        }
+    } break;
+    }
+
     switch (layer->state) {
     case RECT_LAYER_IDLE:
         return rect_layer_event_idle(layer, event, camera, undo_history);
@@ -898,15 +1002,34 @@ int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
     char *ids = dynarray_data(layer->ids);
     Rect *rects = dynarray_data(layer->rects);
     Color *colors = dynarray_data(layer->colors);
+    Action *actions = dynarray_data(layer->actions);
 
     fprintf(filedump, "%zd\n", n);
     for (size_t i = 0; i < n; ++i) {
         fprintf(filedump, "%s %f %f %f %f ",
-                ids + RECT_LAYER_ID_MAX_SIZE * i,
+                ids + ENTITY_MAX_ID_SIZE * i,
                 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
         color_hex_to_stream(colors[i], filedump);
+
+        switch (actions[i].type) {
+        case ACTION_NONE: {} break;
+
+        case ACTION_TOGGLE_GOAL:
+        case ACTION_HIDE_LABEL: {
+            fprintf(filedump, " %d %.*s",
+                    (int)actions[i].type,
+                    ENTITY_MAX_ID_SIZE, actions[i].entity_id);
+        } break;
+        case ACTION_N: break;
+        }
+
         fprintf(filedump, "\n");
     }
 
     return 0;
 }
+
+const Action *rect_layer_actions(const RectLayer *layer)
+{
+    return dynarray_data(layer->actions);
+}