X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fgame%2Flevel%2Flevel_editor%2Frect_layer.c;h=b80fd2b765bb8fcf71acd77764c2a6c533f4c283;hb=73145fa2f0f7263c275b3ed313ff5f367df83f85;hp=be6cb0db777ba5f817dcf55a2715f94e1bf3fdf1;hpb=04fb545803235526927120982b22cd157fb9506a;p=nothing.git diff --git a/src/game/level/level_editor/rect_layer.c b/src/game/level/level_editor/rect_layer.c index be6cb0db..b80fd2b7 100644 --- a/src/game/level/level_editor/rect_layer.c +++ b/src/game/level/level_editor/rect_layer.c @@ -1,7 +1,7 @@ +#include #include #include "game/camera.h" -#include "system/lt.h" #include "system/stacktrace.h" #include "system/nth_alloc.h" #include "system/log.h" @@ -9,68 +9,46 @@ #include "color.h" #include "rect_layer.h" #include "dynarray.h" -#include "system/line_stream.h" -#include "color_picker.h" #include "system/str.h" -#include "ui/edit_field.h" #include "undo_history.h" #include "game/level/action.h" -#include "action_picker.h" +#include "game.h" +#include "math/extrema.h" #define RECT_LAYER_SELECTION_THICCNESS 15.0f #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f) #define CREATE_AREA_THRESHOLD 10.0 -#define RECT_LAYER_GRID_ROWS 3 -#define RECT_LAYER_GRID_COLUMNS 4 -static int clipboard = 0; -static Rect clipboard_rect; -static Color clipboard_color; - -typedef enum { - RECT_LAYER_IDLE = 0, - RECT_LAYER_CREATE, - // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with - // TODO(#1129): different cursor image in resize mode - RECT_LAYER_RESIZE, - RECT_LAYER_MOVE, - RECT_LAYER_ID_RENAME, - RECT_LAYER_RECOLOR -} RectLayerState; - -struct RectLayer { - Lt *lt; - RectLayerState state; - int resize_mask; - Dynarray *ids; - Dynarray *rects; - Dynarray *colors; - Dynarray *actions; - ColorPicker color_picker; - ActionPicker action_picker; - Vec2f create_begin; - Vec2f create_end; - int selection; - 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; +static int rect_clipboard = 0; +static Rect rect_clipboard_rect; +static Color rect_clipboard_color; + +static Cursor_Style resize_styles[1 << RECT_SIDE_N] = { + 0, // [0] + CURSOR_STYLE_RESIZE_VERT, // [1] + CURSOR_STYLE_RESIZE_HORIS, // [2] + CURSOR_STYLE_RESIZE_DIAG1, // [3] + CURSOR_STYLE_RESIZE_VERT, // [4] + 0, // [5] + CURSOR_STYLE_RESIZE_DIAG2, // [6] + 0, // [7] + CURSOR_STYLE_RESIZE_HORIS, // [8] + CURSOR_STYLE_RESIZE_DIAG2, // [9] + 0, // [10] + 0, // [11] + CURSOR_STYLE_RESIZE_DIAG1 // [12] }; typedef enum { - UNDO_ADD, - UNDO_DELETE, - UNDO_UPDATE, - UNDO_SWAP -} UndoType; + RECT_UNDO_ADD, + RECT_UNDO_DELETE, + RECT_UNDO_UPDATE, + RECT_UNDO_SWAP +} RectUndoType; // Delete, Update typedef struct { - UndoType type; + RectUndoType type; RectLayer *layer; size_t index; Rect rect; @@ -81,77 +59,76 @@ typedef struct { // Add typedef struct { - UndoType type; + RectUndoType type; RectLayer *layer; size_t index; } UndoAddContext; // Swap typedef struct { - UndoType type; + RectUndoType type; RectLayer *layer; size_t index1; size_t index2; } UndoSwapContext; typedef union { - UndoType type; + RectUndoType type; UndoAddContext add; UndoElementContext element; UndoSwapContext swap; -} UndoContext; +} RectUndoContext; static -UndoContext create_undo_add_context(RectLayer *layer, size_t index) +RectUndoContext create_rect_undo_add_context(RectLayer *layer, size_t index) { trace_assert(layer); - trace_assert(index < dynarray_count(layer->rects)); + trace_assert(index < layer->rects.count); - UndoContext undo_context; - undo_context.add.type = UNDO_ADD; + RectUndoContext undo_context; + undo_context.add.type = RECT_UNDO_ADD; undo_context.add.layer = layer; undo_context.add.index = index; return undo_context; } static -UndoContext create_undo_element_context(RectLayer *layer) +RectUndoContext create_rect_undo_element_context(RectLayer *layer, size_t index) { trace_assert(layer); - size_t index = (size_t) layer->selection; - trace_assert(index < dynarray_count(layer->rects)); + trace_assert(index < layer->rects.count); - UndoContext undo_context; + RectUndoContext 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); - dynarray_copy_to(layer->actions, &undo_context.element.action, 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); + dynarray_copy_to(&layer->actions, &undo_context.element.action, index); return undo_context; } static -UndoContext create_undo_update_context(RectLayer *rect_layer) +RectUndoContext create_rect_undo_update_context(RectLayer *rect_layer, size_t index) { - UndoContext undo_context = create_undo_element_context(rect_layer); - undo_context.type = UNDO_UPDATE; + RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, index); + undo_context.type = RECT_UNDO_UPDATE; return undo_context; } static -UndoContext create_undo_delete_context(RectLayer *rect_layer) +RectUndoContext create_rect_undo_delete_context(RectLayer *rect_layer, size_t i) { - UndoContext undo_context = create_undo_element_context(rect_layer); - undo_context.type = UNDO_DELETE; + RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, i); + undo_context.type = RECT_UNDO_DELETE; return undo_context; } static -UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2) +RectUndoContext create_rect_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2) { - UndoContext undo_context; - undo_context.swap.type = UNDO_SWAP; + RectUndoContext undo_context; + undo_context.swap.type = RECT_UNDO_SWAP; undo_context.swap.layer = rect_layer; undo_context.swap.index1 = index1; undo_context.swap.index2 = index2; @@ -162,50 +139,50 @@ static void rect_layer_undo(void *context, size_t context_size) { trace_assert(context); - trace_assert(sizeof(UndoContext) == context_size); + trace_assert(sizeof(RectUndoContext) == context_size); - UndoContext *undo_context = context; + RectUndoContext *undo_context = context; switch (undo_context->type) { - case UNDO_ADD: { + case RECT_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); - dynarray_delete_at(layer->actions, undo_context->add.index); + 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; - case UNDO_DELETE: { + case RECT_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); - dynarray_insert_before(layer->actions, undo_context->element.index, &undo_context->element.action); + 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; - case UNDO_UPDATE: { + case RECT_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); - dynarray_replace_at(layer->actions, undo_context->element.index, &undo_context->element.action); + 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: { + case RECT_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); - dynarray_swap(layer->actions, undo_context->swap.index1, undo_context->swap.index2); + 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; } } -#define UNDO_PUSH(HISTORY, CONTEXT) \ +#define RECT_UNDO_PUSH(HISTORY, CONTEXT) \ do { \ - UndoContext context = (CONTEXT); \ + RectUndoContext context = (CONTEXT); \ undo_history_push( \ HISTORY, \ rect_layer_undo, \ @@ -220,29 +197,22 @@ static int rect_layer_add_rect(RectLayer *layer, { trace_assert(layer); - if (dynarray_push(layer->rects, &rect) < 0) { - return -1; - } - - if (dynarray_push(layer->colors, &color) < 0) { - return -1; - } + dynarray_push(&layer->rects, &rect); + dynarray_push(&layer->colors, &color); 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(&layer->ids, id); - dynarray_push_empty(layer->actions); + dynarray_push_empty(&layer->actions); - UNDO_PUSH( + RECT_UNDO_PUSH( undo_history, - create_undo_add_context( + create_rect_undo_add_context( layer, - dynarray_count(layer->rects) - 1)); + layer->rects.count - 1)); return 0; } @@ -251,8 +221,8 @@ static int rect_layer_rect_at(RectLayer *layer, Vec2f position) { trace_assert(layer); - int n = (int) dynarray_count(layer->rects); - Rect *rects = dynarray_data(layer->rects); + int n = (int) layer->rects.count; + Rect *rects = (Rect*)layer->rects.data; for (int i = n - 1; i >= 0; --i) { if (rect_contains_point(rects[i], position)) { @@ -267,29 +237,29 @@ 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)); + trace_assert(a < layer->rects.count); + trace_assert(b < layer->rects.count); - dynarray_swap(layer->rects, a, b); - dynarray_swap(layer->colors, a, b); - dynarray_swap(layer->ids, a, b); - dynarray_swap(layer->actions, a, 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)); + RECT_UNDO_PUSH(undo_history, create_rect_undo_swap_context(layer, a, b)); } -static int rect_layer_delete_rect_at(RectLayer *layer, +static int rect_layer_delete_rect_at_index(RectLayer *layer, size_t i, UndoHistory *undo_history) { trace_assert(layer); - UNDO_PUSH(undo_history, create_undo_delete_context(layer)); + RECT_UNDO_PUSH(undo_history, create_rect_undo_delete_context(layer, i)); - dynarray_delete_at(layer->rects, i); - dynarray_delete_at(layer->colors, i); - dynarray_delete_at(layer->ids, i); - dynarray_delete_at(layer->actions, i); + 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; } @@ -305,6 +275,32 @@ static int calc_resize_mask(Vec2f point, Rect rect) return mask; } +#define TOOL_BUTTON_WIDTH 50.0f +#define TOOL_BUTTON_HEIGHT 50.0f +#define TOOL_BAR_PADDING 20.0f + +static +Rect subtract_tool_button_rect(const Camera *camera) +{ + const Rect view_port = camera_view_port_screen(camera); + return rect( + TOOL_BAR_PADDING, + view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING, + TOOL_BUTTON_WIDTH, + TOOL_BUTTON_HEIGHT); +} + +static +Rect snapping_tool_button_rect(const Camera *camera) +{ + const Rect view_port = camera_view_port_screen(camera); + return rect( + TOOL_BAR_PADDING + TOOL_BUTTON_WIDTH + TOOL_BAR_PADDING, + view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING, + TOOL_BUTTON_WIDTH, + TOOL_BUTTON_HEIGHT); +} + static int rect_layer_event_idle(RectLayer *layer, const SDL_Event *event, const Camera *camera, @@ -321,16 +317,31 @@ static int rect_layer_event_idle(RectLayer *layer, if (color_changed) { if (layer->selection >= 0) { - dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection); + dynarray_copy_to(&layer->colors, &layer->inter_color, (size_t)layer->selection); layer->state = RECT_LAYER_RECOLOR; } return 0; } + Rect *rects = (Rect*)layer->rects.data; + switch (event->type) { case SDL_MOUSEBUTTONDOWN: { switch (event->button.button) { case SDL_BUTTON_LEFT: { + Vec2f screen_position = + vec((float) event->button.x, + (float) event->button.y); + if (rect_contains_point(subtract_tool_button_rect(camera), screen_position)) { + layer->subtract_enabled = !layer->subtract_enabled; + return 0; + } + + if (rect_contains_point(snapping_tool_button_rect(camera), screen_position)) { + layer->snapping_enabled = !layer->snapping_enabled; + return 0; + } + Vec2f position = camera_map_screen( camera, event->button.x, @@ -338,8 +349,8 @@ 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); + + Color *colors = (Color*)layer->colors.data; if (layer->selection >= 0 && layer->selection == rect_at_position && @@ -347,25 +358,25 @@ static int rect_layer_event_idle(RectLayer *layer, vec((float) event->button.x, (float)event->button.y), camera_rect(camera, rects[layer->selection])))) { layer->state = RECT_LAYER_RESIZE; - dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection); + 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 = - vec_sub( - position, - vec( - rects[layer->selection].x, - rects[layer->selection].y)); + layer->move_anchor = vec_sub( + position, + vec( + rects[layer->selection].x, + rects[layer->selection].y)); layer->color_picker = create_color_picker_from_rgba(colors[rect_at_position]); - - dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position); + dynarray_copy_to(&layer->rects, &layer->inter_rect, (size_t) rect_at_position); } else { layer->selection = rect_at_position; if (layer->selection < 0) { - layer->state = RECT_LAYER_CREATE; + layer->state = layer->subtract_enabled + ? RECT_LAYER_SUBTRACT + : RECT_LAYER_CREATE; layer->create_begin = position; layer->create_end = position; } @@ -374,12 +385,29 @@ static int rect_layer_event_idle(RectLayer *layer, } } break; + case SDL_MOUSEMOTION: { + int resize_mask = 0; + Vec2f position = camera_map_screen( + camera, + event->button.x, + event->button.y); + if (layer->selection >= 0 && + layer->selection == rect_layer_rect_at(layer, position) && + (resize_mask = calc_resize_mask( + vec((float) event->button.x, (float)event->button.y), + camera_rect(camera, rects[layer->selection])))) { + layer->cursor->style = resize_styles[resize_mask]; + } else { + layer->cursor->style = CURSOR_STYLE_POINTER; + } + } break; + 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))) { + && ((size_t)(layer->selection + 1) < layer->rects.count)) { rect_layer_swap_elements( layer, (size_t) layer->selection, @@ -392,7 +420,7 @@ static int rect_layer_event_idle(RectLayer *layer, case SDLK_DOWN: { if ((event->key.keysym.mod & KMOD_SHIFT) && (layer->selection > 0) - && ((size_t) layer->selection < dynarray_count(layer->rects))) { + && ((size_t) layer->selection < layer->rects.count)) { rect_layer_swap_elements( layer, (size_t) layer->selection, @@ -404,24 +432,32 @@ static int rect_layer_event_idle(RectLayer *layer, case SDLK_DELETE: { if (layer->selection >= 0) { - rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history); + rect_layer_delete_rect_at_index( + layer, + (size_t) layer->selection, + undo_history); layer->selection = -1; } } break; + case SDLK_q: { + // TODO(#1171): there is no UI indication that we are in the snapping mode + layer->snapping_enabled = !layer->snapping_enabled; + } break; + case SDLK_F2: { if (layer->selection >= 0) { - const char *ids = dynarray_data(layer->ids); - Color *colors = dynarray_data(layer->colors); + const char *ids = (char*)layer->ids.data; + Color *colors = (Color*)layer->colors.data; edit_field_restyle( - layer->id_edit_field, + &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, + &layer->id_edit_field, ids + layer->selection * ENTITY_MAX_ID_SIZE); SDL_StartTextInput(); } @@ -429,14 +465,14 @@ static int rect_layer_event_idle(RectLayer *layer, 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); + rect_clipboard = 1; + dynarray_copy_to(&layer->rects, &rect_clipboard_rect, (size_t)layer->selection); + dynarray_copy_to(&layer->colors, &rect_clipboard_color, (size_t)layer->selection); } } break; case SDLK_v: { - if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) { + if ((event->key.keysym.mod & KMOD_LCTRL) && rect_clipboard) { int x, y; SDL_GetMouseState(&x, &y); Vec2f position = camera_map_screen(camera, x, y); @@ -444,8 +480,8 @@ static int rect_layer_event_idle(RectLayer *layer, rect_layer_add_rect( layer, rect(position.x, position.y, - clipboard_rect.w, clipboard_rect.h), - clipboard_color, + rect_clipboard_rect.w, rect_clipboard_rect.h), + rect_clipboard_color, undo_history); } } break; @@ -456,6 +492,143 @@ static int rect_layer_event_idle(RectLayer *layer, return 0; } +#define GEOMETRY_CAPACITY 256 + +typedef struct { + size_t first; + size_t count; + Rect rects[GEOMETRY_CAPACITY]; + Color colors[GEOMETRY_CAPACITY]; +} Geometry; + +static +void push_geometry(Geometry *geometry, Rect rect, Color color) +{ + assert(geometry); + assert(geometry->count < GEOMETRY_CAPACITY); + + if ((rect.w * rect.h) > 1e-6f) { + size_t i = (geometry->first + geometry->count) % GEOMETRY_CAPACITY; + geometry->rects[i] = rect; + geometry->colors[i] = color; + geometry->count++; + } +} + +static +void subtract_rect_from_rect(Rect a, Color color_a, Rect c, Geometry *result) +{ + assert(result); + + Rect b = rects_overlap_area(a, c); + + if (b.w * b.h < 1e-6) { + push_geometry(result, a, color_a); + return; + } + + push_geometry(result, (Rect) {a.x, a.y, a.w, b.y - a.y}, color_a); + push_geometry(result, (Rect) {a.x, b.y, b.x - a.x, b.h}, color_a); + push_geometry(result, (Rect) { + b.x + b.w, + b.y, + a.w - (b.x - a.x) - b.w, + b.h + }, color_a); + push_geometry(result, (Rect) { + a.x, + b.y + b.h, + a.w, + a.h - (b.y - a.y) - b.h + }, color_a); +} + +static +void subtract_rect_from_geometry(Geometry *result, Rect b) +{ + assert(result); + + size_t count = result->count; + size_t first = result->first; + + for (size_t i = 0; i < count; ++i) { + result->first = (result->first + 1) % GEOMETRY_CAPACITY; + result->count -= 1; + subtract_rect_from_rect( + result->rects[(i + first) % GEOMETRY_CAPACITY], + result->colors[(i + first) % GEOMETRY_CAPACITY], + b, + result); + } +} + +static int rect_layer_event_subtract(RectLayer *layer, + const SDL_Event *event, + const Camera *camera, + UndoHistory *undo_history) +{ + trace_assert(layer); + trace_assert(event); + trace_assert(camera); + trace_assert(undo_history); + + Rect *rects = layer->rects.data; + Color *colors = layer->colors.data; + + switch (event->type) { + case SDL_MOUSEBUTTONUP: { + switch (event->button.button) { + case SDL_BUTTON_LEFT: { + const Rect real_rect = + rect_from_points( + layer->create_begin, + layer->create_end); + const float area = real_rect.w * real_rect.h; + + Geometry geometry = {0}; + + if (area >= CREATE_AREA_THRESHOLD) { + for (size_t i = 0; i < layer->rects.count;) { + Rect overlap_area = rects_overlap_area( + real_rect, + rects[i]); + if (overlap_area.w * overlap_area.h > 1e-6) { + push_geometry(&geometry, rects[i], colors[i]); + rect_layer_delete_rect_at_index(layer, i, undo_history); + } else { + i++; + } + } + + subtract_rect_from_geometry(&geometry, real_rect); + + for (size_t i = 0; i < geometry.count; ++i) { + size_t j = (i + geometry.first) % GEOMETRY_CAPACITY; + rect_layer_add_rect( + layer, + geometry.rects[j], + geometry.colors[j], + undo_history); + } + } else { + log_info("The area is too small %f. Such small box won't be cut.\n", area); + } + layer->state = RECT_LAYER_IDLE; + } break; + } + } break; + + case SDL_MOUSEMOTION: { + layer->create_end = camera_map_screen( + camera, + event->motion.x, + event->motion.y); + } break; + } + + return 0; +} + static int rect_layer_event_create(RectLayer *layer, const SDL_Event *event, const Camera *camera, @@ -499,6 +672,33 @@ static int rect_layer_event_create(RectLayer *layer, return 0; } +static +void snap_rect_resize_if_enabled(RectLayer *layer, Rect *a, float snapping_threshold) +{ + trace_assert(layer); + trace_assert(layer->selection >= 0); + trace_assert(a); + + if (!layer->snapping_enabled) return; + + Rect *rects = (Rect*)layer->rects.data; + size_t rects_size = layer->rects.count; + + for (size_t i = 0; i < rects_size; ++i) { + if (i == (size_t) layer->selection) continue; + + const Rect b = rects[i]; + + if (segment_overlap(vec(a->x, a->x + a->w), vec(b.x, b.x + b.w))) { + snap_var2seg(&a->y, b.y, 0, b.h, snapping_threshold); + } + + if (segment_overlap(vec(a->y, a->y + a->h), vec(b.y, b.y + b.h))) { + snap_var2seg(&a->x, b.x, 0, b.w, snapping_threshold); + } + } +} + static int rect_layer_event_resize(RectLayer *layer, const SDL_Event *event, const Camera *camera, @@ -509,7 +709,9 @@ static int rect_layer_event_resize(RectLayer *layer, trace_assert(camera); trace_assert(layer->selection >= 0); - Rect *rects = dynarray_data(layer->rects); + Rect *rects = (Rect*)layer->rects.data; + + float scaled_snap_threshold = SNAPPING_THRESHOLD / camera->scale; switch (event->type) { case SDL_MOUSEMOTION: { @@ -520,69 +722,161 @@ static int rect_layer_event_resize(RectLayer *layer, switch (layer->resize_mask) { case 1: { // TOP + Rect a = rect(rects[layer->selection].x, + position.y, + rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( - vec(rects[layer->selection].x, position.y), + vec(a.x, a.y), rect_position2(rects[layer->selection])); } break; case 2: { // LEFT + Rect a = rect(position.x, + rects[layer->selection].y, + rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( - vec(position.x, rects[layer->selection].y), + vec(a.x, a.y), rect_position2(rects[layer->selection])); } break; case 3: { // TOP,LEFT + Rect a = rect( + position.x, + position.y, + rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( - position, + vec(a.x, a.y), rect_position2(rects[layer->selection])); } break; case 4: { // BOTTOM + Rect a = rect(rects[layer->selection].x, + position.y, + rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( rect_position(rects[layer->selection]), vec(rects[layer->selection].x + rects[layer->selection].w, - position.y)); + a.y)); } break; case 6: { // BOTTOM,LEFT + Rect a = rect( + position.x, + position.y, + rects[layer->selection].w, + -rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( - vec(position.x, rects[layer->selection].y), + vec(a.x, rects[layer->selection].y), vec(rects[layer->selection].x + rects[layer->selection].w, - position.y)); + a.y)); } break; case 8: { // RIGHT + Rect a = rect(position.x, + rects[layer->selection].y, + rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( rect_position(rects[layer->selection]), - vec(position.x, - rects[layer->selection].y + rects[layer->selection].h)); + vec(a.x, rects[layer->selection].y + rects[layer->selection].h)); } break; case 9: { // TOP,RIGHT + Rect a = rect( + position.x, + position.y, + -rects[layer->selection].w, + rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( - vec(rects[layer->selection].x, position.y), - vec(position.x, + vec(rects[layer->selection].x, a.y), + vec(a.x, rects[layer->selection].y + rects[layer->selection].h)); } break; case 12: { // BOTTOM,RIGHT + Rect a = rect( + position.x, + position.y, + -rects[layer->selection].w, + -rects[layer->selection].h); + + snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold); + layer->inter_rect = rect_from_points( rect_position(rects[layer->selection]), - position); + vec(a.x, a.y)); } break; } + } 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); + RECT_UNDO_PUSH( + undo_history, + create_rect_undo_update_context( + layer, + (size_t) layer->selection)); + dynarray_replace_at(&layer->rects, (size_t) layer->selection, &layer->inter_rect); } break; } return 0; } +static +void snap_rect_move_if_enabled(RectLayer *layer, Rect *a, + float snapping_threshold) +{ + trace_assert(a); + trace_assert(layer); + trace_assert(layer->selection >= 0); + + if (!layer->snapping_enabled) return; + + Rect *rects = (Rect*)layer->rects.data; + size_t rects_size = layer->rects.count; + + for (size_t i = 0; i < rects_size; ++i) { + if (i == (size_t) layer->selection) continue; + + const Rect b = rects[i]; + + if (segment_overlap(vec(a->x, a->x + a->w), vec(b.x, b.x + b.w))) { + snap_seg2seg(&a->y, b.y, a->h, b.h, snapping_threshold); + } + + if (segment_overlap(vec(a->y, a->y + a->h), vec(b.y, b.y + b.h))) { + snap_seg2seg(&a->x, b.x, a->w, b.w, snapping_threshold); + } + } +} + static int rect_layer_event_move(RectLayer *layer, const SDL_Event *event, const Camera *camera, @@ -593,7 +887,7 @@ static int rect_layer_event_move(RectLayer *layer, trace_assert(camera); trace_assert(layer->selection >= 0); - Rect *rects = dynarray_data(layer->rects); + Rect *rects = (Rect*)layer->rects.data; switch (event->type) { case SDL_MOUSEMOTION: { @@ -622,6 +916,9 @@ static int rect_layer_event_move(RectLayer *layer, layer->inter_rect.y = mouse_pos.y; } } + + snap_rect_move_if_enabled(layer, &layer->inter_rect, + SNAPPING_THRESHOLD / camera->scale); } break; case SDL_MOUSEBUTTONUP: { @@ -632,8 +929,14 @@ static int rect_layer_event_move(RectLayer *layer, 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); + RECT_UNDO_PUSH( + undo_history, + create_rect_undo_update_context( + layer, (size_t) layer->selection)); + dynarray_replace_at( + &layer->rects, + (size_t) layer->selection, + &layer->inter_rect); } } break; } @@ -654,11 +957,15 @@ static int rect_layer_event_id_rename(RectLayer *layer, case SDL_KEYDOWN: { switch (event->key.keysym.sym) { case SDLK_RETURN: { - UNDO_PUSH(undo_history, create_undo_update_context(layer)); + RECT_UNDO_PUSH( + undo_history, + create_rect_undo_update_context( + layer, + (size_t) layer->selection)); - char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection); + char *id = dynarray_pointer_at(&layer->ids, (size_t)layer->selection); memset(id, 0, ENTITY_MAX_ID_SIZE); - memcpy(id, edit_field_as_text(layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1); + memcpy(id, edit_field_as_text(&layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1); layer->state = RECT_LAYER_IDLE; SDL_StopTextInput(); } break; @@ -671,7 +978,7 @@ static int rect_layer_event_id_rename(RectLayer *layer, } break; } - return edit_field_event(layer->id_edit_field, event); + return edit_field_event(&layer->id_edit_field, event); } LayerPtr rect_layer_as_layer(RectLayer *rect_layer) @@ -683,157 +990,86 @@ LayerPtr rect_layer_as_layer(RectLayer *rect_layer) return layer; } -RectLayer *create_rect_layer(const char *id_name_prefix) +RectLayer *create_rect_layer(Memory *memory, + const char *id_name_prefix, + Cursor *cursor) { - Lt *lt = create_lt(); - - RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free); - if (layer == NULL) { - RETURN_LT(lt, NULL); - } - layer->lt = lt; - - layer->ids = PUSH_LT( - lt, - create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE), - destroy_dynarray); - if (layer->ids == NULL) { - RETURN_LT(lt, NULL); - } - - layer->rects = PUSH_LT( - lt, - create_dynarray(sizeof(Rect)), - destroy_dynarray); - if (layer->rects == NULL) { - RETURN_LT(lt, NULL); - } - - layer->colors = PUSH_LT( - lt, - create_dynarray(sizeof(Color)), - destroy_dynarray); - if (layer->colors == NULL) { - 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( - RECT_LAYER_ID_LABEL_SIZE, - COLOR_BLACK), - destroy_edit_field); - if (layer->id_edit_field == NULL) { - 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; - - return layer; + trace_assert(memory); + trace_assert(id_name_prefix); + trace_assert(cursor); + + RectLayer *rect_layer = memory_alloc(memory, sizeof(RectLayer)); + + rect_layer->ids = create_dynarray(memory, sizeof(char) * ENTITY_MAX_ID_SIZE); + rect_layer->rects = create_dynarray(memory, sizeof(Rect)); + rect_layer->colors = create_dynarray(memory, sizeof(Color)); + rect_layer->actions = create_dynarray(memory, sizeof(Action)); + rect_layer->id_edit_field.font_size = RECT_LAYER_ID_LABEL_SIZE; + rect_layer->id_edit_field.font_color = COLOR_BLACK; + rect_layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f)); + rect_layer->selection = -1; + rect_layer->id_name_prefix = id_name_prefix; + rect_layer->cursor = cursor; + + return rect_layer; } -RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix) +void rect_layer_load(RectLayer *layer, Memory *memory, String *input) { - trace_assert(line_stream); - - RectLayer *layer = create_rect_layer(id_name_prefix); - if (layer == NULL) { - return NULL; - } - - const char *line = line_stream_next(line_stream); - if (line == NULL) { - RETURN_LT(layer->lt, NULL); - } - - size_t count = 0; - if (sscanf(line, "%zu", &count) < 0) { - RETURN_LT(layer->lt, NULL); - } - - for (size_t i = 0; i < count; ++i) { - line = line_stream_next(line_stream); - if (line == NULL) { - RETURN_LT(layer->lt, NULL); - } + trace_assert(layer); + trace_assert(memory); + trace_assert(input); - char hex[7]; + int n = atoi(string_to_cstr(memory, trim(chop_by_delim(input, '\n')))); + char id[ENTITY_MAX_ID_SIZE]; + for (int i = 0; i < n; ++i) { Rect rect; - char id[ENTITY_MAX_ID_SIZE]; - - int n = 0; - if (sscanf(line, - "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n", - id, - &rect.x, &rect.y, - &rect.w, &rect.h, - 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); + String line = trim(chop_by_delim(input, '\n')); + String string_id = trim(chop_word(&line)); + rect.x = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL); + rect.y = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL); + rect.w = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL); + rect.h = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL); + Color color = hexs(trim(chop_word(&line))); + + memset(id, 0, ENTITY_MAX_ID_SIZE); + memcpy( + id, + string_id.data, + min_size_t(ENTITY_MAX_ID_SIZE - 1, string_id.count)); + + dynarray_push(&layer->rects, &rect); + dynarray_push(&layer->colors, &color); + dynarray_push(&layer->ids, id); Action action = { .type = ACTION_NONE, .entity_id = {0} }; - if (sscanf(line, "%d%n", (int*)&action.type, &n) > 0) { - line += n; + String action_string = trim(chop_word(&line)); + if (action_string.count > 0) { + action.type = (ActionType)atol(string_to_cstr(memory, action_string)); 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); - } + String label_id = trim(chop_word(&line)); + trace_assert(label_id.count > 0); + memset(action.entity_id, 0, ENTITY_MAX_ID_SIZE); + memcpy(action.entity_id, + label_id.data, + min_size_t( + ENTITY_MAX_ID_SIZE - 1, + label_id.count)); } break; case ACTION_N: break; } } - dynarray_push(layer->actions, &action); + dynarray_push(&layer->actions, &action); } - - return layer; -} - -void destroy_rect_layer(RectLayer *layer) -{ - trace_assert(layer); - RETURN_LT0(layer->lt); } int rect_layer_render(const RectLayer *layer, const Camera *camera, int active) @@ -841,10 +1077,10 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active) trace_assert(layer); trace_assert(camera); - const size_t n = dynarray_count(layer->rects); - Rect *rects = dynarray_data(layer->rects); - Color *colors = dynarray_data(layer->colors); - const char *ids = dynarray_data(layer->ids); + const size_t n = layer->rects.count; + Rect *rects = (Rect *)layer->rects.data; + Color *colors = (Color *)layer->colors.data; + const char *ids = (const char *)layer->ids.data; // The Rectangles for (size_t i = 0; i < n; ++i) { @@ -910,7 +1146,7 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active) if (layer->state == RECT_LAYER_ID_RENAME) { // ID renaming Edit Field if (edit_field_render_world( - layer->id_edit_field, + &layer->id_edit_field, camera, rect_id_pos) < 0) { return -1; @@ -936,10 +1172,29 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active) } } + if (layer->state == RECT_LAYER_SUBTRACT) { + if (camera_draw_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) { + return -1; + } + } + if (active && color_picker_render(&layer->color_picker, camera) < 0) { return -1; } + // Tool bar + if (active) { + // TODO(#1251): subtract and snapping tools don't have any icons + camera_fill_rect_screen( + camera, + subtract_tool_button_rect(camera), + layer->subtract_enabled ? COLOR_RED : rgba(0.2f, 0.2f, 0.2f, 1.0f)); + camera_fill_rect_screen( + camera, + snapping_tool_button_rect(camera), + layer->snapping_enabled ? COLOR_RED : rgba(0.2f, 0.2f, 0.2f, 1.0f)); + } + return 0; } @@ -964,8 +1219,12 @@ 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(undo_history, create_undo_update_context(layer)); - dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color); + RECT_UNDO_PUSH( + undo_history, + create_rect_undo_update_context( + layer, + (size_t)layer->selection)); + dynarray_replace_at(&layer->colors, (size_t) layer->selection, &layer->inter_color); layer->state = RECT_LAYER_IDLE; } } @@ -982,18 +1241,6 @@ 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); @@ -1012,29 +1259,33 @@ int rect_layer_event(RectLayer *layer, case RECT_LAYER_RECOLOR: return rect_layer_event_recolor(layer, event, camera, undo_history); + + case RECT_LAYER_SUBTRACT: + return rect_layer_event_subtract(layer, event, camera, undo_history); } + return 0; } size_t rect_layer_count(const RectLayer *layer) { - return dynarray_count(layer->rects); + return layer->rects.count; } const Rect *rect_layer_rects(const RectLayer *layer) { - return dynarray_data(layer->rects); + return (const Rect *)layer->rects.data; } const Color *rect_layer_colors(const RectLayer *layer) { - return dynarray_data(layer->colors); + return (const Color *)layer->colors.data; } const char *rect_layer_ids(const RectLayer *layer) { - return dynarray_data(layer->ids); + return (const char *)layer->ids.data; } int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump) @@ -1042,11 +1293,11 @@ int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump) trace_assert(layer); trace_assert(filedump); - size_t n = dynarray_count(layer->ids); - char *ids = dynarray_data(layer->ids); - Rect *rects = dynarray_data(layer->rects); - Color *colors = dynarray_data(layer->colors); - Action *actions = dynarray_data(layer->actions); + size_t n = layer->ids.count; + char *ids = (char *)layer->ids.data; + Rect *rects = (Rect *)layer->rects.data; + Color *colors = (Color *)layer->colors.data; + Action *actions = (Action *)layer->actions.data; fprintf(filedump, "%zd\n", n); for (size_t i = 0; i < n; ++i) { @@ -1075,5 +1326,5 @@ int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump) const Action *rect_layer_actions(const RectLayer *layer) { - return dynarray_data(layer->actions); + return (const Action *)layer->actions.data; }