1 #include "game/camera.h"
3 #include "system/stacktrace.h"
4 #include "system/nth_alloc.h"
5 #include "system/log.h"
8 #include "rect_layer.h"
10 #include "system/line_stream.h"
11 #include "color_picker.h"
12 #include "system/str.h"
13 #include "ui/edit_field.h"
14 #include "undo_history.h"
16 #define RECT_LAYER_SELECTION_THICCNESS 10.0f
17 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
18 #define CREATE_AREA_THRESHOLD 10.0
20 // TODO(#1051): RectLayer does not support copy-pasting
22 static int clipboard = 0;
23 static Rect clipboard_rect;
24 static Color clipboard_color;
29 // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
42 ColorPicker color_picker;
47 Edit_field *id_edit_field;
63 char id[RECT_LAYER_ID_MAX_SIZE];
68 UndoContext create_undo_context(RectLayer *rect_layer, UndoType type)
70 trace_assert(rect_layer);
72 size_t index = type == UNDO_ADD ? dynarray_count(rect_layer->rects) - 1 : (size_t) rect_layer->selection;
74 UndoContext undo_context;
75 undo_context.type = type;
76 undo_context.layer = rect_layer;
77 dynarray_copy_to(rect_layer->rects, &undo_context.rect, index);
78 dynarray_copy_to(rect_layer->colors, &undo_context.color, index);
79 dynarray_copy_to(rect_layer->ids, undo_context.id, index);
80 undo_context.index = index;
86 void rect_layer_undo(void *context, size_t context_size)
88 trace_assert(context);
89 trace_assert(sizeof(UndoContext) == context_size);
91 UndoContext *undo_context = context;
92 RectLayer *rect_layer = undo_context->layer;
94 switch (undo_context->type) {
96 dynarray_delete_at(rect_layer->rects, undo_context->index);
97 dynarray_delete_at(rect_layer->colors, undo_context->index);
98 dynarray_delete_at(rect_layer->ids, undo_context->index);
99 rect_layer->selection = -1;
103 dynarray_insert_before(rect_layer->rects, undo_context->index, &undo_context->rect);
104 dynarray_insert_before(rect_layer->colors, undo_context->index, &undo_context->color);
105 dynarray_insert_before(rect_layer->ids, undo_context->index, &undo_context->id);
106 rect_layer->selection = -1;
110 dynarray_replace_at(rect_layer->rects, undo_context->index, &undo_context->rect);
111 dynarray_replace_at(rect_layer->colors, undo_context->index, &undo_context->color);
112 dynarray_replace_at(rect_layer->ids, undo_context->index, &undo_context->id);
117 #define UNDO_PUSH(LAYER, HISTORY, UNDO_TYPE) \
119 UndoContext context = create_undo_context(LAYER, UNDO_TYPE); \
128 static int rect_layer_add_rect(RectLayer *layer,
131 UndoHistory *undo_history)
135 if (dynarray_push(layer->rects, &rect) < 0) {
139 if (dynarray_push(layer->colors, &color) < 0) {
143 char id[RECT_LAYER_ID_MAX_SIZE];
144 for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
145 id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
147 id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
149 if (dynarray_push(layer->ids, id)) {
153 UNDO_PUSH(layer, undo_history, UNDO_ADD);
158 // TODO(#956): rect_layer_rect_at doesn't return rectangles according to some z-order
159 static int rect_layer_rect_at(RectLayer *layer, Vec position)
163 const size_t n = dynarray_count(layer->rects);
164 Rect *rects = dynarray_data(layer->rects);
166 for (size_t i = 0; i < n; ++i) {
167 if (rect_contains_point(rects[i], position)) {
175 static Rect rect_layer_resize_anchor(const Camera *camera, Rect boundary_rect)
177 const Rect overlay_rect =
179 camera_rect(camera, boundary_rect),
180 RECT_LAYER_SELECTION_THICCNESS * 0.5f);
183 overlay_rect.x + overlay_rect.w,
184 overlay_rect.y + overlay_rect.h,
185 RECT_LAYER_SELECTION_THICCNESS * 2.0f,
186 RECT_LAYER_SELECTION_THICCNESS * 2.0f);
189 static int rect_layer_delete_rect_at(RectLayer *layer,
191 UndoHistory *undo_history)
195 UNDO_PUSH(layer, undo_history, UNDO_DELETE);
197 dynarray_delete_at(layer->rects, i);
198 dynarray_delete_at(layer->colors, i);
199 dynarray_delete_at(layer->ids, i);
204 static int rect_layer_event_idle(RectLayer *layer,
205 const SDL_Event *event,
206 const Camera *camera,
207 UndoHistory *undo_history)
211 trace_assert(camera);
213 int color_changed = 0;
214 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
219 if (layer->selection >= 0) {
220 dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
221 layer->state = RECT_LAYER_RECOLOR;
226 switch (event->type) {
227 case SDL_MOUSEBUTTONDOWN: {
228 switch (event->button.button) {
229 case SDL_BUTTON_LEFT: {
230 Point position = camera_map_screen(
234 int rect_at_position =
235 rect_layer_rect_at(layer, position);
237 Rect *rects = dynarray_data(layer->rects);
238 Color *colors = dynarray_data(layer->colors);
240 if (rect_at_position >= 0) {
241 layer->selection = rect_at_position;
242 layer->state = RECT_LAYER_MOVE;
247 rects[layer->selection].x,
248 rects[layer->selection].y));
249 layer->color_picker =
250 create_color_picker_from_rgba(colors[rect_at_position]);
252 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
253 } else if (layer->selection >= 0 && rect_contains_point(
254 rect_layer_resize_anchor(
256 rects[layer->selection]),
258 (float) event->button.x,
259 (float) event->button.y))) {
260 layer->state = RECT_LAYER_RESIZE;
261 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
263 layer->selection = rect_at_position;
265 if (layer->selection < 0) {
266 layer->state = RECT_LAYER_CREATE;
267 layer->create_begin = position;
268 layer->create_end = position;
276 switch (event->key.keysym.sym) {
278 if (layer->selection >= 0) {
279 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
280 layer->selection = -1;
285 if (layer->selection >= 0) {
286 const char *ids = dynarray_data(layer->ids);
287 Color *colors = dynarray_data(layer->colors);
290 layer->id_edit_field,
291 RECT_LAYER_ID_LABEL_SIZE,
292 color_invert(colors[layer->selection]));
294 layer->state = RECT_LAYER_ID_RENAME;
296 layer->id_edit_field,
297 ids + layer->selection * RECT_LAYER_ID_MAX_SIZE);
298 SDL_StartTextInput();
303 if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
305 dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
306 dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
311 if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
313 SDL_GetMouseState(&x, &y);
314 Point position = camera_map_screen(camera, x, y);
318 rect(position.x, position.y,
319 clipboard_rect.w, clipboard_rect.h),
331 static int rect_layer_event_create(RectLayer *layer,
332 const SDL_Event *event,
333 const Camera *camera,
334 UndoHistory *undo_history)
338 trace_assert(camera);
340 switch (event->type) {
341 case SDL_MOUSEBUTTONUP: {
342 switch (event->button.button) {
343 case SDL_BUTTON_LEFT: {
344 const Rect real_rect =
348 const float area = real_rect.w * real_rect.h;
350 if (area >= CREATE_AREA_THRESHOLD) {
354 color_picker_rgba(&layer->color_picker),
357 log_info("The area is too small %f. Such small box won't be created.\n", area);
359 layer->state = RECT_LAYER_IDLE;
364 case SDL_MOUSEMOTION: {
365 layer->create_end = camera_map_screen(
374 static int rect_layer_event_resize(RectLayer *layer,
375 const SDL_Event *event,
376 const Camera *camera,
377 UndoHistory *undo_history)
381 trace_assert(camera);
382 trace_assert(layer->selection >= 0);
384 switch (event->type) {
385 case SDL_MOUSEMOTION: {
386 layer->inter_rect = rect_from_points(
387 vec(layer->inter_rect.x, layer->inter_rect.y),
393 vec(RECT_LAYER_SELECTION_THICCNESS * -0.5f,
394 RECT_LAYER_SELECTION_THICCNESS * -0.5f)));
397 case SDL_MOUSEBUTTONUP: {
398 layer->state = RECT_LAYER_IDLE;
399 UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
400 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
407 static int rect_layer_event_move(RectLayer *layer,
408 const SDL_Event *event,
409 const Camera *camera,
410 UndoHistory *undo_history)
414 trace_assert(camera);
415 trace_assert(layer->selection >= 0);
417 switch (event->type) {
418 case SDL_MOUSEMOTION: {
419 Point position = vec_sub(
426 trace_assert(layer->selection >= 0);
428 layer->inter_rect.x = position.x;
429 layer->inter_rect.y = position.y;
432 case SDL_MOUSEBUTTONUP: {
433 layer->state = RECT_LAYER_IDLE;
434 UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
435 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
441 static int rect_layer_event_id_rename(RectLayer *layer,
442 const SDL_Event *event,
443 const Camera *camera,
444 UndoHistory *undo_history)
448 trace_assert(camera);
449 trace_assert(layer->selection >= 0);
451 switch (event->type) {
453 switch (event->key.keysym.sym) {
455 UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
457 char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
458 memset(id, 0, RECT_LAYER_ID_MAX_SIZE);
459 memcpy(id, edit_field_as_text(layer->id_edit_field), RECT_LAYER_ID_MAX_SIZE - 1);
460 layer->state = RECT_LAYER_IDLE;
465 layer->state = RECT_LAYER_IDLE;
472 return edit_field_event(layer->id_edit_field, event);
475 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
484 RectLayer *create_rect_layer(void)
486 Lt *lt = create_lt();
488 RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
494 layer->ids = PUSH_LT(
496 create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
498 if (layer->ids == NULL) {
502 layer->rects = PUSH_LT(
504 create_dynarray(sizeof(Rect)),
506 if (layer->rects == NULL) {
510 layer->colors = PUSH_LT(
512 create_dynarray(sizeof(Color)),
514 if (layer->colors == NULL) {
518 layer->id_edit_field = PUSH_LT(
521 RECT_LAYER_ID_LABEL_SIZE,
524 if (layer->id_edit_field == NULL) {
528 layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
529 layer->selection = -1;
534 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
536 trace_assert(line_stream);
538 RectLayer *layer = create_rect_layer();
543 const char *line = line_stream_next(line_stream);
545 RETURN_LT(layer->lt, NULL);
549 if (sscanf(line, "%zu", &count) < 0) {
550 RETURN_LT(layer->lt, NULL);
553 for (size_t i = 0; i < count; ++i) {
554 line = line_stream_next(line_stream);
556 RETURN_LT(layer->lt, NULL);
561 char id[RECT_LAYER_ID_MAX_SIZE];
564 "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
569 RETURN_LT(layer->lt, NULL);
572 Color color = hexstr(hex);
574 dynarray_push(layer->rects, &rect);
575 dynarray_push(layer->ids, id);
576 dynarray_push(layer->colors, &color);
582 void destroy_rect_layer(RectLayer *layer)
585 RETURN_LT0(layer->lt);
588 int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
591 trace_assert(camera);
593 const size_t n = dynarray_count(layer->rects);
594 Rect *rects = dynarray_data(layer->rects);
595 Color *colors = dynarray_data(layer->colors);
596 const char *ids = dynarray_data(layer->ids);
599 for (size_t i = 0; i < n; ++i) {
600 Rect rect = rects[i];
601 Color color = colors[i];
603 if (layer->selection == (int) i) {
604 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
605 rect = layer->inter_rect;
608 if (layer->state == RECT_LAYER_RECOLOR) {
609 color = layer->inter_color;
613 if (camera_fill_rect(
618 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
623 if (active && (size_t) layer->selection == i) {
624 const Rect overlay_rect =
626 camera_rect(camera, rect),
627 RECT_LAYER_SELECTION_THICCNESS * 0.5f);
628 const Color overlay_color = color_invert(color);
631 if (camera_fill_rect(
636 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
640 if (camera_draw_thicc_rect_screen(
644 RECT_LAYER_SELECTION_THICCNESS) < 0) {
649 if (layer->state == RECT_LAYER_ID_RENAME) {
650 // ID renaming Edit Field
651 if (edit_field_render_world(
652 layer->id_edit_field,
654 rect_position(rect)) < 0) {
659 if (camera_render_text(
661 ids + layer->selection * RECT_LAYER_ID_MAX_SIZE,
662 RECT_LAYER_ID_LABEL_SIZE,
664 rect_position(rect)) < 0) {
670 if (camera_fill_rect_screen(
672 rect_layer_resize_anchor(camera, rect),
673 overlay_color) < 0) {
680 const Color color = color_picker_rgba(&layer->color_picker);
681 if (layer->state == RECT_LAYER_CREATE) {
682 if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
687 if (active && color_picker_render(&layer->color_picker, camera) < 0) {
695 int rect_layer_event_recolor(RectLayer *layer,
696 const SDL_Event *event,
697 const Camera *camera,
698 UndoHistory *undo_history)
702 trace_assert(camera);
703 trace_assert(undo_history);
704 trace_assert(layer->selection >= 0);
706 int color_changed = 0;
707 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
712 layer->inter_color = color_picker_rgba(&layer->color_picker);
714 if (!color_picker_drag(&layer->color_picker)) {
715 UNDO_PUSH(layer, undo_history, UNDO_UPDATE);
716 dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
717 layer->state = RECT_LAYER_IDLE;
724 int rect_layer_event(RectLayer *layer,
725 const SDL_Event *event,
726 const Camera *camera,
727 UndoHistory *undo_history)
731 trace_assert(undo_history);
733 switch (layer->state) {
734 case RECT_LAYER_IDLE:
735 return rect_layer_event_idle(layer, event, camera, undo_history);
737 case RECT_LAYER_CREATE:
738 return rect_layer_event_create(layer, event, camera, undo_history);
740 case RECT_LAYER_RESIZE:
741 return rect_layer_event_resize(layer, event, camera, undo_history);
743 case RECT_LAYER_MOVE:
744 return rect_layer_event_move(layer, event, camera, undo_history);
746 case RECT_LAYER_ID_RENAME:
747 return rect_layer_event_id_rename(layer, event, camera, undo_history);
749 case RECT_LAYER_RECOLOR:
750 return rect_layer_event_recolor(layer, event, camera, undo_history);
756 size_t rect_layer_count(const RectLayer *layer)
758 return dynarray_count(layer->rects);
761 const Rect *rect_layer_rects(const RectLayer *layer)
763 return dynarray_data(layer->rects);
766 const Color *rect_layer_colors(const RectLayer *layer)
768 return dynarray_data(layer->colors);
771 const char *rect_layer_ids(const RectLayer *layer)
773 return dynarray_data(layer->ids);
776 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
779 trace_assert(filedump);
781 size_t n = dynarray_count(layer->ids);
782 char *ids = dynarray_data(layer->ids);
783 Rect *rects = dynarray_data(layer->rects);
784 Color *colors = dynarray_data(layer->colors);
786 fprintf(filedump, "%zd\n", n);
787 for (size_t i = 0; i < n; ++i) {
788 fprintf(filedump, "%s %f %f %f %f ",
789 ids + RECT_LAYER_ID_MAX_SIZE * i,
790 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
791 color_hex_to_stream(colors[i], filedump);
792 fprintf(filedump, "\n");