3 #include "game/camera.h"
5 #include "system/stacktrace.h"
6 #include "system/nth_alloc.h"
7 #include "system/log.h"
10 #include "rect_layer.h"
12 #include "system/line_stream.h"
13 #include "color_picker.h"
14 #include "system/str.h"
15 #include "ui/edit_field.h"
16 #include "undo_history.h"
17 #include "game/level/action.h"
19 #define RECT_LAYER_SELECTION_THICCNESS 10.0f
20 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
21 #define CREATE_AREA_THRESHOLD 10.0
23 static int clipboard = 0;
24 static Rect clipboard_rect;
25 static Color clipboard_color;
30 // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
44 ColorPicker color_picker;
49 Edit_field *id_edit_field;
53 const char *id_name_prefix;
71 char id[ENTITY_MAX_ID_SIZE];
92 UndoElementContext element;
97 UndoContext create_undo_add_context(RectLayer *layer, size_t index)
100 trace_assert(index < dynarray_count(layer->rects));
102 UndoContext undo_context;
103 undo_context.add.type = UNDO_ADD;
104 undo_context.add.layer = layer;
105 undo_context.add.index = index;
110 UndoContext create_undo_element_context(RectLayer *layer)
113 size_t index = (size_t) layer->selection;
114 trace_assert(index < dynarray_count(layer->rects));
116 UndoContext undo_context;
117 undo_context.element.layer = layer;
118 undo_context.element.index = index;
119 dynarray_copy_to(layer->rects, &undo_context.element.rect, index);
120 dynarray_copy_to(layer->colors, &undo_context.element.color, index);
121 dynarray_copy_to(layer->ids, undo_context.element.id, index);
122 dynarray_copy_to(layer->actions, &undo_context.element.action, index);
127 UndoContext create_undo_update_context(RectLayer *rect_layer)
129 UndoContext undo_context = create_undo_element_context(rect_layer);
130 undo_context.type = UNDO_UPDATE;
135 UndoContext create_undo_delete_context(RectLayer *rect_layer)
137 UndoContext undo_context = create_undo_element_context(rect_layer);
138 undo_context.type = UNDO_DELETE;
143 UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
145 UndoContext undo_context;
146 undo_context.swap.type = UNDO_SWAP;
147 undo_context.swap.layer = rect_layer;
148 undo_context.swap.index1 = index1;
149 undo_context.swap.index2 = index2;
154 void rect_layer_undo(void *context, size_t context_size)
156 trace_assert(context);
157 trace_assert(sizeof(UndoContext) == context_size);
159 UndoContext *undo_context = context;
161 switch (undo_context->type) {
163 RectLayer *layer = undo_context->add.layer;
164 dynarray_delete_at(layer->rects, undo_context->add.index);
165 dynarray_delete_at(layer->colors, undo_context->add.index);
166 dynarray_delete_at(layer->ids, undo_context->add.index);
167 dynarray_delete_at(layer->actions, undo_context->add.index);
168 layer->selection = -1;
172 RectLayer *layer = undo_context->element.layer;
173 dynarray_insert_before(layer->rects, undo_context->element.index, &undo_context->element.rect);
174 dynarray_insert_before(layer->colors, undo_context->element.index, &undo_context->element.color);
175 dynarray_insert_before(layer->ids, undo_context->element.index, &undo_context->element.id);
176 dynarray_insert_before(layer->actions, undo_context->element.index, &undo_context->element.action);
177 layer->selection = -1;
181 RectLayer *layer = undo_context->element.layer;
182 dynarray_replace_at(layer->rects, undo_context->element.index, &undo_context->element.rect);
183 dynarray_replace_at(layer->colors, undo_context->element.index, &undo_context->element.color);
184 dynarray_replace_at(layer->ids, undo_context->element.index, &undo_context->element.id);
185 dynarray_replace_at(layer->actions, undo_context->element.index, &undo_context->element.action);
189 RectLayer *layer = undo_context->element.layer;
190 dynarray_swap(layer->rects, undo_context->swap.index1, undo_context->swap.index2);
191 dynarray_swap(layer->colors, undo_context->swap.index1, undo_context->swap.index2);
192 dynarray_swap(layer->ids, undo_context->swap.index1, undo_context->swap.index2);
193 dynarray_swap(layer->actions, undo_context->swap.index1, undo_context->swap.index2);
198 #define UNDO_PUSH(HISTORY, CONTEXT) \
200 UndoContext context = (CONTEXT); \
208 static int rect_layer_add_rect(RectLayer *layer,
211 UndoHistory *undo_history)
215 if (dynarray_push(layer->rects, &rect) < 0) {
219 if (dynarray_push(layer->colors, &color) < 0) {
223 char id[ENTITY_MAX_ID_SIZE];
224 snprintf(id, ENTITY_MAX_ID_SIZE, "%s_%d",
225 layer->id_name_prefix,
226 layer->id_name_counter++);
227 if (dynarray_push(layer->ids, id)) {
233 create_undo_add_context(
235 dynarray_count(layer->rects) - 1));
240 static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
244 int n = (int) dynarray_count(layer->rects);
245 Rect *rects = dynarray_data(layer->rects);
247 for (int i = n - 1; i >= 0; --i) {
248 if (rect_contains_point(rects[i], position)) {
256 static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
257 UndoHistory *undo_history)
260 trace_assert(a < dynarray_count(layer->rects));
261 trace_assert(b < dynarray_count(layer->rects));
263 dynarray_swap(layer->rects, a, b);
264 dynarray_swap(layer->colors, a, b);
265 dynarray_swap(layer->ids, a, b);
266 dynarray_swap(layer->actions, a, b);
268 UNDO_PUSH(undo_history, create_undo_swap_context(layer, a, b));
271 static Rect rect_layer_resize_anchor(const Camera *camera, Rect boundary_rect)
273 const Rect overlay_rect =
275 camera_rect(camera, boundary_rect),
276 RECT_LAYER_SELECTION_THICCNESS * 0.5f);
279 overlay_rect.x + overlay_rect.w,
280 overlay_rect.y + overlay_rect.h,
281 RECT_LAYER_SELECTION_THICCNESS * 2.0f,
282 RECT_LAYER_SELECTION_THICCNESS * 2.0f);
285 static int rect_layer_delete_rect_at(RectLayer *layer,
287 UndoHistory *undo_history)
291 UNDO_PUSH(undo_history, create_undo_delete_context(layer));
293 dynarray_delete_at(layer->rects, i);
294 dynarray_delete_at(layer->colors, i);
295 dynarray_delete_at(layer->ids, i);
296 dynarray_delete_at(layer->actions, i);
301 static int rect_layer_event_idle(RectLayer *layer,
302 const SDL_Event *event,
303 const Camera *camera,
304 UndoHistory *undo_history)
308 trace_assert(camera);
310 int color_changed = 0;
311 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
316 if (layer->selection >= 0) {
317 dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
318 layer->state = RECT_LAYER_RECOLOR;
323 switch (event->type) {
324 case SDL_MOUSEBUTTONDOWN: {
325 switch (event->button.button) {
326 case SDL_BUTTON_LEFT: {
327 Vec2f position = camera_map_screen(
331 int rect_at_position =
332 rect_layer_rect_at(layer, position);
334 Rect *rects = dynarray_data(layer->rects);
335 Color *colors = dynarray_data(layer->colors);
337 if (layer->selection >= 0 && rect_contains_point(
338 rect_layer_resize_anchor(
340 rects[layer->selection]),
342 (float) event->button.x,
343 (float) event->button.y))) {
344 layer->state = RECT_LAYER_RESIZE;
345 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
346 } else if (rect_at_position >= 0) {
347 layer->selection = rect_at_position;
348 layer->state = RECT_LAYER_MOVE;
353 rects[layer->selection].x,
354 rects[layer->selection].y));
355 layer->color_picker =
356 create_color_picker_from_rgba(colors[rect_at_position]);
358 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
360 layer->selection = rect_at_position;
362 if (layer->selection < 0) {
363 layer->state = RECT_LAYER_CREATE;
364 layer->create_begin = position;
365 layer->create_end = position;
373 switch (event->key.keysym.sym) {
375 if ((event->key.keysym.mod & KMOD_SHIFT)
376 && (layer->selection >= 0)
377 && ((size_t)(layer->selection + 1) < dynarray_count(layer->rects))) {
378 rect_layer_swap_elements(
380 (size_t) layer->selection,
381 (size_t) layer->selection + 1,
388 if ((event->key.keysym.mod & KMOD_SHIFT)
389 && (layer->selection > 0)
390 && ((size_t) layer->selection < dynarray_count(layer->rects))) {
391 rect_layer_swap_elements(
393 (size_t) layer->selection,
394 (size_t) layer->selection - 1,
401 if (layer->selection >= 0) {
402 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
403 layer->selection = -1;
408 if (layer->selection >= 0) {
409 const char *ids = dynarray_data(layer->ids);
410 Color *colors = dynarray_data(layer->colors);
413 layer->id_edit_field,
414 RECT_LAYER_ID_LABEL_SIZE,
415 color_invert(colors[layer->selection]));
417 layer->state = RECT_LAYER_ID_RENAME;
419 layer->id_edit_field,
420 ids + layer->selection * ENTITY_MAX_ID_SIZE);
421 SDL_StartTextInput();
426 if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
428 dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
429 dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
434 if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
436 SDL_GetMouseState(&x, &y);
437 Vec2f position = camera_map_screen(camera, x, y);
441 rect(position.x, position.y,
442 clipboard_rect.w, clipboard_rect.h),
454 static int rect_layer_event_create(RectLayer *layer,
455 const SDL_Event *event,
456 const Camera *camera,
457 UndoHistory *undo_history)
461 trace_assert(camera);
463 switch (event->type) {
464 case SDL_MOUSEBUTTONUP: {
465 switch (event->button.button) {
466 case SDL_BUTTON_LEFT: {
467 const Rect real_rect =
471 const float area = real_rect.w * real_rect.h;
473 if (area >= CREATE_AREA_THRESHOLD) {
477 color_picker_rgba(&layer->color_picker),
480 log_info("The area is too small %f. Such small box won't be created.\n", area);
482 layer->state = RECT_LAYER_IDLE;
487 case SDL_MOUSEMOTION: {
488 layer->create_end = camera_map_screen(
497 static int rect_layer_event_resize(RectLayer *layer,
498 const SDL_Event *event,
499 const Camera *camera,
500 UndoHistory *undo_history)
504 trace_assert(camera);
505 trace_assert(layer->selection >= 0);
507 switch (event->type) {
508 case SDL_MOUSEMOTION: {
509 layer->inter_rect = rect_from_points(
510 vec(layer->inter_rect.x, layer->inter_rect.y),
516 vec(RECT_LAYER_SELECTION_THICCNESS * -0.5f,
517 RECT_LAYER_SELECTION_THICCNESS * -0.5f)));
520 case SDL_MOUSEBUTTONUP: {
521 layer->state = RECT_LAYER_IDLE;
522 UNDO_PUSH(undo_history, create_undo_update_context(layer));
523 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
530 static int rect_layer_event_move(RectLayer *layer,
531 const SDL_Event *event,
532 const Camera *camera,
533 UndoHistory *undo_history)
537 trace_assert(camera);
538 trace_assert(layer->selection >= 0);
540 Rect *rects = dynarray_data(layer->rects);
542 switch (event->type) {
543 case SDL_MOUSEMOTION: {
544 Vec2f position = vec_sub(
551 trace_assert(layer->selection >= 0);
553 layer->inter_rect.x = position.x;
554 layer->inter_rect.y = position.y;
557 case SDL_MOUSEBUTTONUP: {
558 layer->state = RECT_LAYER_IDLE;
560 float distance = vec_length(
561 vec_sub(rect_position(layer->inter_rect),
562 rect_position(rects[layer->selection])));
564 if (distance > 1e-6) {
565 UNDO_PUSH(undo_history, create_undo_update_context(layer));
566 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
573 static int rect_layer_event_id_rename(RectLayer *layer,
574 const SDL_Event *event,
575 const Camera *camera,
576 UndoHistory *undo_history)
580 trace_assert(camera);
581 trace_assert(layer->selection >= 0);
583 switch (event->type) {
585 switch (event->key.keysym.sym) {
587 UNDO_PUSH(undo_history, create_undo_update_context(layer));
589 char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
590 memset(id, 0, ENTITY_MAX_ID_SIZE);
591 memcpy(id, edit_field_as_text(layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1);
592 layer->state = RECT_LAYER_IDLE;
597 layer->state = RECT_LAYER_IDLE;
604 return edit_field_event(layer->id_edit_field, event);
607 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
616 RectLayer *create_rect_layer(const char *id_name_prefix)
618 Lt *lt = create_lt();
620 RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
626 layer->ids = PUSH_LT(
628 create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE),
630 if (layer->ids == NULL) {
634 layer->rects = PUSH_LT(
636 create_dynarray(sizeof(Rect)),
638 if (layer->rects == NULL) {
642 layer->colors = PUSH_LT(
644 create_dynarray(sizeof(Color)),
646 if (layer->colors == NULL) {
650 layer->actions = PUSH_LT(
652 create_dynarray(sizeof(Action)),
654 if (layer->actions == NULL) {
658 layer->id_edit_field = PUSH_LT(
661 RECT_LAYER_ID_LABEL_SIZE,
664 if (layer->id_edit_field == NULL) {
668 layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
669 layer->selection = -1;
670 layer->id_name_prefix = id_name_prefix;
675 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix)
677 trace_assert(line_stream);
679 RectLayer *layer = create_rect_layer(id_name_prefix);
684 const char *line = line_stream_next(line_stream);
686 RETURN_LT(layer->lt, NULL);
690 if (sscanf(line, "%zu", &count) < 0) {
691 RETURN_LT(layer->lt, NULL);
694 for (size_t i = 0; i < count; ++i) {
695 line = line_stream_next(line_stream);
697 RETURN_LT(layer->lt, NULL);
702 char id[ENTITY_MAX_ID_SIZE];
706 "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n",
711 log_fail("%s\n", strerror(errno));
712 RETURN_LT(layer->lt, NULL);
716 Color color = hexstr(hex);
717 dynarray_push(layer->rects, &rect);
718 dynarray_push(layer->ids, id);
719 dynarray_push(layer->colors, &color);
723 if (sscanf(line, "%d%n", (int*)&action.type, &n) != EOF) {
725 switch (action.type) {
726 case ACTION_HIDE_LABEL: {
727 if (sscanf(line, "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s", action.label_id) == EOF) {
728 log_fail("%s\n", strerror(errno));
729 RETURN_LT(layer->lt, NULL);
736 dynarray_push(layer->actions, &action);
742 void destroy_rect_layer(RectLayer *layer)
745 RETURN_LT0(layer->lt);
748 int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
751 trace_assert(camera);
753 const size_t n = dynarray_count(layer->rects);
754 Rect *rects = dynarray_data(layer->rects);
755 Color *colors = dynarray_data(layer->colors);
756 const char *ids = dynarray_data(layer->ids);
759 for (size_t i = 0; i < n; ++i) {
760 Rect rect = rects[i];
761 Color color = colors[i];
763 if (layer->selection == (int) i) {
764 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
765 rect = layer->inter_rect;
768 if (layer->state == RECT_LAYER_RECOLOR) {
769 color = layer->inter_color;
773 if (camera_fill_rect(
778 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
783 if (active && (size_t) layer->selection == i) {
784 const Rect overlay_rect =
786 camera_rect(camera, rect),
787 RECT_LAYER_SELECTION_THICCNESS * 0.5f);
788 const Color overlay_color = color_invert(color);
791 if (camera_fill_rect(
796 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
800 if (camera_draw_thicc_rect_screen(
804 RECT_LAYER_SELECTION_THICCNESS) < 0) {
809 if (layer->state == RECT_LAYER_ID_RENAME) {
810 // ID renaming Edit Field
811 if (edit_field_render_world(
812 layer->id_edit_field,
814 rect_position(rect)) < 0) {
819 if (camera_render_text(
821 ids + layer->selection * ENTITY_MAX_ID_SIZE,
822 RECT_LAYER_ID_LABEL_SIZE,
824 rect_position(rect)) < 0) {
830 if (camera_fill_rect_screen(
832 rect_layer_resize_anchor(camera, rect),
833 overlay_color) < 0) {
840 const Color color = color_picker_rgba(&layer->color_picker);
841 if (layer->state == RECT_LAYER_CREATE) {
842 if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
847 if (active && color_picker_render(&layer->color_picker, camera) < 0) {
855 int rect_layer_event_recolor(RectLayer *layer,
856 const SDL_Event *event,
857 const Camera *camera,
858 UndoHistory *undo_history)
862 trace_assert(camera);
863 trace_assert(undo_history);
864 trace_assert(layer->selection >= 0);
866 int color_changed = 0;
867 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
872 layer->inter_color = color_picker_rgba(&layer->color_picker);
874 if (!color_picker_drag(&layer->color_picker)) {
875 UNDO_PUSH(undo_history, create_undo_update_context(layer));
876 dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
877 layer->state = RECT_LAYER_IDLE;
884 int rect_layer_event(RectLayer *layer,
885 const SDL_Event *event,
886 const Camera *camera,
887 UndoHistory *undo_history)
891 trace_assert(undo_history);
893 switch (layer->state) {
894 case RECT_LAYER_IDLE:
895 return rect_layer_event_idle(layer, event, camera, undo_history);
897 case RECT_LAYER_CREATE:
898 return rect_layer_event_create(layer, event, camera, undo_history);
900 case RECT_LAYER_RESIZE:
901 return rect_layer_event_resize(layer, event, camera, undo_history);
903 case RECT_LAYER_MOVE:
904 return rect_layer_event_move(layer, event, camera, undo_history);
906 case RECT_LAYER_ID_RENAME:
907 return rect_layer_event_id_rename(layer, event, camera, undo_history);
909 case RECT_LAYER_RECOLOR:
910 return rect_layer_event_recolor(layer, event, camera, undo_history);
916 size_t rect_layer_count(const RectLayer *layer)
918 return dynarray_count(layer->rects);
921 const Rect *rect_layer_rects(const RectLayer *layer)
923 return dynarray_data(layer->rects);
926 const Color *rect_layer_colors(const RectLayer *layer)
928 return dynarray_data(layer->colors);
931 const char *rect_layer_ids(const RectLayer *layer)
933 return dynarray_data(layer->ids);
936 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
939 trace_assert(filedump);
941 size_t n = dynarray_count(layer->ids);
942 char *ids = dynarray_data(layer->ids);
943 Rect *rects = dynarray_data(layer->rects);
944 Color *colors = dynarray_data(layer->colors);
945 Action *actions = dynarray_data(layer->actions);
947 fprintf(filedump, "%zd\n", n);
948 for (size_t i = 0; i < n; ++i) {
949 fprintf(filedump, "%s %f %f %f %f ",
950 ids + ENTITY_MAX_ID_SIZE * i,
951 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
952 color_hex_to_stream(colors[i], filedump);
954 switch (actions[i].type) {
955 case ACTION_HIDE_LABEL: {
956 fprintf(filedump, " %d %.*s ",
957 (int)actions[i].type,
958 ENTITY_MAX_ID_SIZE, actions[i].label_id);
963 fprintf(filedump, "\n");
969 const Action *rect_layer_actions(const RectLayer *layer)
971 return dynarray_data(layer->actions);