4 #include "game/camera.h"
6 #include "system/stacktrace.h"
7 #include "system/nth_alloc.h"
8 #include "system/log.h"
11 #include "rect_layer.h"
13 #include "system/line_stream.h"
14 #include "color_picker.h"
15 #include "system/str.h"
16 #include "ui/edit_field.h"
17 #include "undo_history.h"
18 #include "game/level/action.h"
19 #include "action_picker.h"
22 #define RECT_LAYER_SELECTION_THICCNESS 15.0f
23 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
24 #define CREATE_AREA_THRESHOLD 10.0
25 #define RECT_LAYER_GRID_ROWS 3
26 #define RECT_LAYER_GRID_COLUMNS 4
27 #define SNAPPING_THRESHOLD 10.0f
29 static int clipboard = 0;
30 static Rect clipboard_rect;
31 static Color clipboard_color;
33 static Cursor_Style resize_styles[1 << RECT_SIDE_N] = {
35 CURSOR_STYLE_RESIZE_VERT, // [1]
36 CURSOR_STYLE_RESIZE_HORIS, // [2]
37 CURSOR_STYLE_RESIZE_DIAG1, // [3]
38 CURSOR_STYLE_RESIZE_VERT, // [4]
40 CURSOR_STYLE_RESIZE_DIAG2, // [6]
42 CURSOR_STYLE_RESIZE_HORIS, // [8]
43 CURSOR_STYLE_RESIZE_DIAG2, // [9]
46 CURSOR_STYLE_RESIZE_DIAG1 // [12]
66 ColorPicker color_picker;
67 ActionPicker action_picker;
71 Vec2f move_anchor; // The mouse offset from the left-top
72 // corner of the rect during moving it
73 Edit_field *id_edit_field;
77 const char *id_name_prefix;
81 // this is the initial size of the selected rectangle during a resize.
82 // we update this whenever the rectangle is resized, so we know the ratio
84 Vec2f initial_rectangle_size;
102 char id[ENTITY_MAX_ID_SIZE];
103 } UndoElementContext;
123 UndoElementContext element;
124 UndoSwapContext swap;
128 UndoContext create_undo_add_context(RectLayer *layer, size_t index)
131 trace_assert(index < dynarray_count(layer->rects));
133 UndoContext undo_context;
134 undo_context.add.type = UNDO_ADD;
135 undo_context.add.layer = layer;
136 undo_context.add.index = index;
141 UndoContext create_undo_element_context(RectLayer *layer)
144 size_t index = (size_t) layer->selection;
145 trace_assert(index < dynarray_count(layer->rects));
147 UndoContext undo_context;
148 undo_context.element.layer = layer;
149 undo_context.element.index = index;
150 dynarray_copy_to(layer->rects, &undo_context.element.rect, index);
151 dynarray_copy_to(layer->colors, &undo_context.element.color, index);
152 dynarray_copy_to(layer->ids, undo_context.element.id, index);
153 dynarray_copy_to(layer->actions, &undo_context.element.action, index);
158 UndoContext create_undo_update_context(RectLayer *rect_layer)
160 UndoContext undo_context = create_undo_element_context(rect_layer);
161 undo_context.type = UNDO_UPDATE;
166 UndoContext create_undo_delete_context(RectLayer *rect_layer)
168 UndoContext undo_context = create_undo_element_context(rect_layer);
169 undo_context.type = UNDO_DELETE;
174 UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
176 UndoContext undo_context;
177 undo_context.swap.type = UNDO_SWAP;
178 undo_context.swap.layer = rect_layer;
179 undo_context.swap.index1 = index1;
180 undo_context.swap.index2 = index2;
185 void rect_layer_undo(void *context, size_t context_size)
187 trace_assert(context);
188 trace_assert(sizeof(UndoContext) == context_size);
190 UndoContext *undo_context = context;
192 switch (undo_context->type) {
194 RectLayer *layer = undo_context->add.layer;
195 dynarray_delete_at(layer->rects, undo_context->add.index);
196 dynarray_delete_at(layer->colors, undo_context->add.index);
197 dynarray_delete_at(layer->ids, undo_context->add.index);
198 dynarray_delete_at(layer->actions, undo_context->add.index);
199 layer->selection = -1;
203 RectLayer *layer = undo_context->element.layer;
204 dynarray_insert_before(layer->rects, undo_context->element.index, &undo_context->element.rect);
205 dynarray_insert_before(layer->colors, undo_context->element.index, &undo_context->element.color);
206 dynarray_insert_before(layer->ids, undo_context->element.index, &undo_context->element.id);
207 dynarray_insert_before(layer->actions, undo_context->element.index, &undo_context->element.action);
208 layer->selection = -1;
212 RectLayer *layer = undo_context->element.layer;
213 dynarray_replace_at(layer->rects, undo_context->element.index, &undo_context->element.rect);
214 dynarray_replace_at(layer->colors, undo_context->element.index, &undo_context->element.color);
215 dynarray_replace_at(layer->ids, undo_context->element.index, &undo_context->element.id);
216 dynarray_replace_at(layer->actions, undo_context->element.index, &undo_context->element.action);
220 RectLayer *layer = undo_context->element.layer;
221 dynarray_swap(layer->rects, undo_context->swap.index1, undo_context->swap.index2);
222 dynarray_swap(layer->colors, undo_context->swap.index1, undo_context->swap.index2);
223 dynarray_swap(layer->ids, undo_context->swap.index1, undo_context->swap.index2);
224 dynarray_swap(layer->actions, undo_context->swap.index1, undo_context->swap.index2);
229 #define UNDO_PUSH(HISTORY, CONTEXT) \
231 UndoContext context = (CONTEXT); \
239 static int rect_layer_add_rect(RectLayer *layer,
242 UndoHistory *undo_history)
246 if (dynarray_push(layer->rects, &rect) < 0) {
250 if (dynarray_push(layer->colors, &color) < 0) {
254 char id[ENTITY_MAX_ID_SIZE];
255 snprintf(id, ENTITY_MAX_ID_SIZE, "%s_%d",
256 layer->id_name_prefix,
257 layer->id_name_counter++);
258 if (dynarray_push(layer->ids, id)) {
262 dynarray_push_empty(layer->actions);
266 create_undo_add_context(
268 dynarray_count(layer->rects) - 1));
273 static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
277 int n = (int) dynarray_count(layer->rects);
278 Rect *rects = dynarray_data(layer->rects);
280 for (int i = n - 1; i >= 0; --i) {
281 if (rect_contains_point(rects[i], position)) {
289 static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
290 UndoHistory *undo_history)
293 trace_assert(a < dynarray_count(layer->rects));
294 trace_assert(b < dynarray_count(layer->rects));
296 dynarray_swap(layer->rects, a, b);
297 dynarray_swap(layer->colors, a, b);
298 dynarray_swap(layer->ids, a, b);
299 dynarray_swap(layer->actions, a, b);
301 UNDO_PUSH(undo_history, create_undo_swap_context(layer, a, b));
304 static int rect_layer_delete_rect_at(RectLayer *layer,
306 UndoHistory *undo_history)
310 UNDO_PUSH(undo_history, create_undo_delete_context(layer));
312 dynarray_delete_at(layer->rects, i);
313 dynarray_delete_at(layer->colors, i);
314 dynarray_delete_at(layer->ids, i);
315 dynarray_delete_at(layer->actions, i);
320 static int calc_resize_mask(Vec2f point, Rect rect)
323 for (Rect_side side = 0; side < RECT_SIDE_N; ++side) {
324 if (rect_side_distance(rect, point, side) < RECT_LAYER_SELECTION_THICCNESS) {
325 mask = mask | (1 << side);
331 static int rect_layer_event_idle(RectLayer *layer,
332 const SDL_Event *event,
333 const Camera *camera,
334 UndoHistory *undo_history)
338 trace_assert(camera);
340 int color_changed = 0;
341 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
346 if (layer->selection >= 0) {
347 dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
348 layer->state = RECT_LAYER_RECOLOR;
353 Rect *rects = dynarray_data(layer->rects);
355 switch (event->type) {
356 case SDL_MOUSEBUTTONDOWN: {
357 switch (event->button.button) {
358 case SDL_BUTTON_LEFT: {
359 Vec2f position = camera_map_screen(
363 int rect_at_position =
364 rect_layer_rect_at(layer, position);
367 Color *colors = dynarray_data(layer->colors);
369 if (layer->selection >= 0 &&
370 layer->selection == rect_at_position &&
371 (layer->resize_mask = calc_resize_mask(
372 vec((float) event->button.x, (float)event->button.y),
373 camera_rect(camera, rects[layer->selection])))) {
374 layer->state = RECT_LAYER_RESIZE;
375 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
376 } else if (rect_at_position >= 0) {
377 layer->selection = rect_at_position;
378 layer->state = RECT_LAYER_MOVE;
379 layer->move_anchor = vec_sub(
382 rects[layer->selection].x,
383 rects[layer->selection].y));
384 layer->color_picker =
385 create_color_picker_from_rgba(colors[rect_at_position]);
386 dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
388 layer->selection = rect_at_position;
390 if (layer->selection < 0) {
391 layer->state = RECT_LAYER_CREATE;
392 layer->create_begin = position;
393 layer->create_end = position;
397 if (layer->selection >= 0) {
398 layer->initial_rectangle_size = vec(rects[layer->selection].w,
399 rects[layer->selection].h);
405 case SDL_MOUSEMOTION: {
407 Vec2f position = camera_map_screen(
411 if (layer->selection >= 0 &&
412 layer->selection == rect_layer_rect_at(layer, position) &&
413 (resize_mask = calc_resize_mask(
414 vec((float) event->button.x, (float)event->button.y),
415 camera_rect(camera, rects[layer->selection])))) {
416 layer->cursor->style = resize_styles[resize_mask];
418 layer->cursor->style = CURSOR_STYLE_POINTER;
423 switch (event->key.keysym.sym) {
425 if ((event->key.keysym.mod & KMOD_SHIFT)
426 && (layer->selection >= 0)
427 && ((size_t)(layer->selection + 1) < dynarray_count(layer->rects))) {
428 rect_layer_swap_elements(
430 (size_t) layer->selection,
431 (size_t) layer->selection + 1,
438 if ((event->key.keysym.mod & KMOD_SHIFT)
439 && (layer->selection > 0)
440 && ((size_t) layer->selection < dynarray_count(layer->rects))) {
441 rect_layer_swap_elements(
443 (size_t) layer->selection,
444 (size_t) layer->selection - 1,
451 if (layer->selection >= 0) {
452 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
453 layer->selection = -1;
458 if (layer->selection >= 0) {
459 const char *ids = dynarray_data(layer->ids);
460 Color *colors = dynarray_data(layer->colors);
463 layer->id_edit_field,
464 RECT_LAYER_ID_LABEL_SIZE,
465 color_invert(colors[layer->selection]));
467 layer->state = RECT_LAYER_ID_RENAME;
469 layer->id_edit_field,
470 ids + layer->selection * ENTITY_MAX_ID_SIZE);
471 SDL_StartTextInput();
476 if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
478 dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
479 dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
484 if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
486 SDL_GetMouseState(&x, &y);
487 Vec2f position = camera_map_screen(camera, x, y);
491 rect(position.x, position.y,
492 clipboard_rect.w, clipboard_rect.h),
504 static int rect_layer_event_create(RectLayer *layer,
505 const SDL_Event *event,
506 const Camera *camera,
507 UndoHistory *undo_history)
511 trace_assert(camera);
513 switch (event->type) {
514 case SDL_MOUSEBUTTONUP: {
515 switch (event->button.button) {
516 case SDL_BUTTON_LEFT: {
517 const Rect real_rect =
521 const float area = real_rect.w * real_rect.h;
523 if (area >= CREATE_AREA_THRESHOLD) {
527 color_picker_rgba(&layer->color_picker),
530 log_info("The area is too small %f. Such small box won't be created.\n", area);
532 layer->state = RECT_LAYER_IDLE;
537 case SDL_MOUSEMOTION: {
538 layer->create_end = camera_map_screen(
548 int segment_overlap(Vec2f a, Vec2f b)
550 trace_assert(a.x <= a.y);
551 trace_assert(b.x <= b.y);
552 return a.y >= b.x && b.y >= a.x;
556 int snap_var(float *x, // the value we are snapping
557 float y, // the target we are snapping x to
558 float xo, // x offset
559 float yo, // y offset
560 float st) // snap threshold
562 if (fabsf((*x + xo) - (y + yo)) < st) {
570 int snap_var2seg(float *x, float y,
574 // note: do not use || because we do *not* want short-circuiting, so use |.
575 return snap_var(x, y, xo, 0, st) | snap_var(x, y, xo, yo, st);
579 void snap_seg2seg(float *x, float y, float xo, float yo, float st)
581 snap_var(x, y, 0, 0, st);
582 snap_var(x, y, 0, yo, st);
583 snap_var(x, y, xo, 0, st);
584 snap_var(x, y, xo, yo, st);
588 void fix_rect_ratio(RectLayer *layer,
589 float *x, float *y, // the things we can change to fix the ratio
590 Vec2f ref_pt) // the (fixed) reference point of the rect
595 // if we're not holding down shift, don't bother.
596 if (!(SDL_GetKeyboardState(NULL)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(NULL)[SDL_SCANCODE_RSHIFT]))
599 float ratio = layer->initial_rectangle_size.x / layer->initial_rectangle_size.y;
601 // if we are holding down control also, then make squares.
602 if (SDL_GetKeyboardState(NULL)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(NULL)[SDL_SCANCODE_RCTRL])
605 // make some constants for us to use.
606 float inv_ratio = 1.0f / ratio;
607 float w = *x - ref_pt.x;
608 float h = *y - ref_pt.y;
610 float ab_w = fabsf(w);
611 float ab_h = fabsf(h);
613 // note: copysign takes (magnitude, sign). this thing basically lengthens the shorter side
614 // to fit the ratio. copysign is used to handle when the length is negative due to the
615 // different corner positions.
616 if (ab_w <= ratio * ab_h) {
617 *x = ref_pt.x + (ratio * copysignf(h, w));
618 } else if (ab_h <= inv_ratio * ab_w) {
619 *y = ref_pt.y + inv_ratio * copysignf(w, h);
624 static int rect_layer_event_resize(RectLayer *layer,
625 const SDL_Event *event,
626 const Camera *camera,
627 UndoHistory *undo_history)
631 trace_assert(camera);
632 trace_assert(layer->selection >= 0);
634 Rect *rects = dynarray_data(layer->rects);
636 float scaled_snap_threshold = SNAPPING_THRESHOLD / camera->scale;
638 switch (event->type) {
639 case SDL_MOUSEMOTION: {
640 Vec2f position = camera_map_screen(
645 switch (layer->resize_mask) {
647 float y = position.y;
648 float x = rects[layer->selection].x;
649 float w = rects[layer->selection].w;
650 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
651 if (i == (size_t) layer->selection) continue;
653 const Rect b = rects[i];
654 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
655 snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold);
659 layer->inter_rect = rect_from_points(
661 rect_position2(rects[layer->selection]));
665 float y = rects[layer->selection].y;
666 float x = position.x;
667 float h = rects[layer->selection].h;
668 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
669 if (i == (size_t) layer->selection) continue;
671 const Rect b = rects[i];
672 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
673 snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold);
677 layer->inter_rect = rect_from_points(
679 rect_position2(rects[layer->selection]));
682 case 3: { // TOP,LEFT
683 float x = position.x;
684 float y = position.y;
685 float w = rects[layer->selection].w;
686 float h = rects[layer->selection].h;
688 // use the bottom-right as reference.
689 Vec2f ref_pos = rect_position2(rects[layer->selection]);
690 fix_rect_ratio(layer, &x, &y, ref_pos);
692 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
693 if (i == (size_t) layer->selection) continue;
695 const Rect b = rects[i];
696 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
697 if (snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold)) {
698 // if we did a snap, we need to update the rect to make sure it
699 // still fits the ratio. same pattern repeats below.
700 fix_rect_ratio(layer, &x, &y, ref_pos);
704 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
705 if (snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold)) {
706 fix_rect_ratio(layer, &x, &y, ref_pos);
711 layer->inter_rect = rect_from_points(
713 rect_position2(rects[layer->selection]));
717 float y = position.y;
718 float x = rects[layer->selection].x;
719 float w = rects[layer->selection].w;
720 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
721 if (i == (size_t) layer->selection) continue;
723 const Rect b = rects[i];
724 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
725 snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold);
729 layer->inter_rect = rect_from_points(
730 rect_position(rects[layer->selection]),
731 vec(rects[layer->selection].x + rects[layer->selection].w,
735 case 6: { // BOTTOM,LEFT
736 float x = position.x;
737 float y = position.y;
738 float w = rects[layer->selection].w;
739 float h = rects[layer->selection].h;
741 // use the top-right as reference.
742 Vec2f ref_pos = vec(rects[layer->selection].x + rects[layer->selection].w, rects[layer->selection].y);
743 fix_rect_ratio(layer, &x, &y, ref_pos);
745 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
746 if (i == (size_t) layer->selection) continue;
748 const Rect b = rects[i];
749 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
750 if (snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold)) {
751 fix_rect_ratio(layer, &x, &y, ref_pos);
755 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
756 if (snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold)) {
757 fix_rect_ratio(layer, &x, &y, ref_pos);
762 layer->inter_rect = rect_from_points(
763 vec(x, rects[layer->selection].y),
764 vec(rects[layer->selection].x + rects[layer->selection].w,
769 float y = rects[layer->selection].y;
770 float x = position.x;
771 float h = rects[layer->selection].h;
773 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
774 if (i == (size_t) layer->selection) continue;
776 const Rect b = rects[i];
777 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
778 snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold);
782 layer->inter_rect = rect_from_points(
783 rect_position(rects[layer->selection]),
785 rects[layer->selection].y + rects[layer->selection].h));
788 case 9: { // TOP,RIGHT
789 float x = position.x;
790 float y = position.y;
791 float w = rects[layer->selection].w;
792 float h = rects[layer->selection].h;
794 // use bottom-left as reference.
795 Vec2f ref_pos = vec(rects[layer->selection].x, rects[layer->selection].y + rects[layer->selection].h);
796 fix_rect_ratio(layer, &x, &y, ref_pos);
798 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
799 if (i == (size_t) layer->selection) continue;
801 const Rect b = rects[i];
802 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
803 if (snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold)) {
804 fix_rect_ratio(layer, &x, &y, ref_pos);
808 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
809 if (snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold)) {
810 fix_rect_ratio(layer, &x, &y, ref_pos);
815 layer->inter_rect = rect_from_points(
816 vec(rects[layer->selection].x, y),
818 rects[layer->selection].y + rects[layer->selection].h));
821 case 12: { // BOTTOM,RIGHT
822 float x = position.x;
823 float y = position.y;
824 float w = rects[layer->selection].w;
825 float h = rects[layer->selection].h;
827 // use top-left as reference.
828 Vec2f ref_pos = rect_position(rects[layer->selection]);
829 fix_rect_ratio(layer, &x, &y, ref_pos);
831 for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
832 if (i == (size_t) layer->selection) continue;
834 const Rect b = rects[i];
835 if (segment_overlap(vec(y, y + h), vec(b.y, b.y + b.h))) {
836 if (snap_var2seg(&x, b.x, 0, b.w, scaled_snap_threshold)) {
837 fix_rect_ratio(layer, &x, &y, ref_pos);
841 if (segment_overlap(vec(x, x + w), vec(b.x, b.x + b.w))) {
842 if (snap_var2seg(&y, b.y, 0, b.h, scaled_snap_threshold)) {
843 fix_rect_ratio(layer, &x, &y, ref_pos);
848 layer->inter_rect = rect_from_points(
849 rect_position(rects[layer->selection]),
854 // note that we need to update the "initial size" even during the drag. this is because
855 // we can enter/exit the ratio mode in the middle of dragging!
856 layer->initial_rectangle_size = vec(layer->inter_rect.w, layer->inter_rect.h);
860 case SDL_MOUSEBUTTONUP: {
861 layer->state = RECT_LAYER_IDLE;
862 UNDO_PUSH(undo_history, create_undo_update_context(layer));
863 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
871 void snap_rects(size_t ignore_index, Rect *a,
872 Rect *rects, size_t rects_size,
873 float snapping_threshold)
878 for (size_t i = 0; i < rects_size; ++i) {
879 if (i == ignore_index) continue;
881 const Rect b = rects[i];
883 if (segment_overlap(vec(a->x, a->x + a->w), vec(b.x, b.x + b.w))) {
884 snap_seg2seg(&a->y, b.y, a->h, b.h, snapping_threshold);
887 if (segment_overlap(vec(a->y, a->y + a->h), vec(b.y, b.y + b.h))) {
888 snap_seg2seg(&a->x, b.x, a->w, b.w, snapping_threshold);
893 static int rect_layer_event_move(RectLayer *layer,
894 const SDL_Event *event,
895 const Camera *camera,
896 UndoHistory *undo_history)
900 trace_assert(camera);
901 trace_assert(layer->selection >= 0);
903 Rect *rects = dynarray_data(layer->rects);
905 switch (event->type) {
906 case SDL_MOUSEMOTION: {
907 const Uint8 *state = SDL_GetKeyboardState(NULL);
908 const Vec2f mouse_pos = vec_sub(
915 if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
916 layer->inter_rect.x = mouse_pos.x;
917 layer->inter_rect.y = mouse_pos.y;
919 const Vec2f rect_pos = rect_position(rects[layer->selection]);
921 const float dx = fabsf(rect_pos.x - mouse_pos.x);
922 const float dy = fabsf(rect_pos.y - mouse_pos.y);
925 layer->inter_rect.x = mouse_pos.x;
926 layer->inter_rect.y = rect_pos.y;
928 layer->inter_rect.x = rect_pos.x;
929 layer->inter_rect.y = mouse_pos.y;
933 // TODO(#1141): Rect Snapping in Level Editor should be optional
934 snap_rects((size_t) layer->selection, &layer->inter_rect,
935 rects, dynarray_count(layer->rects),
936 SNAPPING_THRESHOLD / camera->scale);
939 case SDL_MOUSEBUTTONUP: {
940 layer->state = RECT_LAYER_IDLE;
942 float distance = vec_length(
943 vec_sub(rect_position(layer->inter_rect),
944 rect_position(rects[layer->selection])));
946 if (distance > 1e-6) {
947 UNDO_PUSH(undo_history, create_undo_update_context(layer));
948 dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
955 static int rect_layer_event_id_rename(RectLayer *layer,
956 const SDL_Event *event,
957 const Camera *camera,
958 UndoHistory *undo_history)
962 trace_assert(camera);
963 trace_assert(layer->selection >= 0);
965 switch (event->type) {
967 switch (event->key.keysym.sym) {
969 UNDO_PUSH(undo_history, create_undo_update_context(layer));
971 char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
972 memset(id, 0, ENTITY_MAX_ID_SIZE);
973 memcpy(id, edit_field_as_text(layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1);
974 layer->state = RECT_LAYER_IDLE;
979 layer->state = RECT_LAYER_IDLE;
986 return edit_field_event(layer->id_edit_field, event);
989 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
998 RectLayer *create_rect_layer(const char *id_name_prefix, Cursor *cursor)
1000 trace_assert(cursor);
1002 Lt *lt = create_lt();
1004 RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
1005 if (layer == NULL) {
1006 RETURN_LT(lt, NULL);
1010 layer->ids = PUSH_LT(
1012 create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE),
1014 if (layer->ids == NULL) {
1015 RETURN_LT(lt, NULL);
1018 layer->rects = PUSH_LT(
1020 create_dynarray(sizeof(Rect)),
1022 if (layer->rects == NULL) {
1023 RETURN_LT(lt, NULL);
1026 layer->colors = PUSH_LT(
1028 create_dynarray(sizeof(Color)),
1030 if (layer->colors == NULL) {
1031 RETURN_LT(lt, NULL);
1034 layer->actions = PUSH_LT(
1036 create_dynarray(sizeof(Action)),
1038 if (layer->actions == NULL) {
1039 RETURN_LT(lt, NULL);
1042 layer->id_edit_field = PUSH_LT(
1045 RECT_LAYER_ID_LABEL_SIZE,
1047 destroy_edit_field);
1048 if (layer->id_edit_field == NULL) {
1049 RETURN_LT(lt, NULL);
1057 sizeof(Grid) + sizeof(Widget*) * RECT_LAYER_GRID_ROWS * RECT_LAYER_GRID_COLUMNS),
1059 if (layer->grid == NULL) {
1060 RETURN_LT(lt, NULL);
1062 layer->grid->rows = RECT_LAYER_GRID_ROWS;
1063 layer->grid->columns = RECT_LAYER_GRID_COLUMNS;
1064 grid_put_widget(layer->grid, &layer->action_picker.widget, 0, RECT_LAYER_GRID_COLUMNS - 1);
1066 layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
1067 layer->selection = -1;
1068 layer->id_name_prefix = id_name_prefix;
1069 layer->cursor = cursor;
1074 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream,
1075 const char *id_name_prefix,
1078 trace_assert(line_stream);
1080 RectLayer *layer = create_rect_layer(id_name_prefix, cursor);
1081 if (layer == NULL) {
1085 const char *line = line_stream_next(line_stream);
1087 RETURN_LT(layer->lt, NULL);
1091 if (sscanf(line, "%zu", &count) < 0) {
1092 RETURN_LT(layer->lt, NULL);
1095 for (size_t i = 0; i < count; ++i) {
1096 line = line_stream_next(line_stream);
1098 RETURN_LT(layer->lt, NULL);
1103 char id[ENTITY_MAX_ID_SIZE];
1107 "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n",
1112 log_fail("%s\n", strerror(errno));
1113 RETURN_LT(layer->lt, NULL);
1117 Color color = hexstr(hex);
1118 dynarray_push(layer->rects, &rect);
1119 dynarray_push(layer->ids, id);
1120 dynarray_push(layer->colors, &color);
1123 .type = ACTION_NONE,
1127 if (sscanf(line, "%d%n", (int*)&action.type, &n) > 0) {
1129 switch (action.type) {
1130 case ACTION_NONE: break;
1132 case ACTION_TOGGLE_GOAL:
1133 case ACTION_HIDE_LABEL: {
1134 if (sscanf(line, "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s", action.entity_id) <= 0) {
1135 log_fail("%s\n", strerror(errno));
1136 RETURN_LT(layer->lt, NULL);
1140 case ACTION_N: break;
1144 dynarray_push(layer->actions, &action);
1150 void destroy_rect_layer(RectLayer *layer)
1152 trace_assert(layer);
1153 RETURN_LT0(layer->lt);
1156 int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
1158 trace_assert(layer);
1159 trace_assert(camera);
1161 const size_t n = dynarray_count(layer->rects);
1162 Rect *rects = dynarray_data(layer->rects);
1163 Color *colors = dynarray_data(layer->colors);
1164 const char *ids = dynarray_data(layer->ids);
1167 for (size_t i = 0; i < n; ++i) {
1168 Rect rect = rects[i];
1169 Color color = colors[i];
1171 if (layer->selection == (int) i) {
1172 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1173 rect = layer->inter_rect;
1176 if (layer->state == RECT_LAYER_RECOLOR) {
1177 color = layer->inter_color;
1182 if (camera_fill_rect(
1187 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
1192 // Selection Overlay
1193 if (active && layer->selection >= 0) {
1194 Rect rect = rects[layer->selection];
1195 Color color = colors[layer->selection];
1197 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1198 rect = layer->inter_rect;
1201 if (layer->state == RECT_LAYER_RECOLOR) {
1202 color = layer->inter_color;
1205 const Rect overlay_rect =
1207 camera_rect(camera, rect),
1208 -RECT_LAYER_SELECTION_THICCNESS * 0.5f);
1209 const Color overlay_color = color_invert(color);
1212 if (camera_draw_thicc_rect_screen(
1216 RECT_LAYER_SELECTION_THICCNESS) < 0) {
1220 const Vec2f rect_id_pos = vec_sub(
1221 rect_position(rect),
1223 RECT_LAYER_ID_LABEL_SIZE,
1224 vec(0.0f, FONT_CHAR_HEIGHT)));
1227 if (layer->state == RECT_LAYER_ID_RENAME) {
1228 // ID renaming Edit Field
1229 if (edit_field_render_world(
1230 layer->id_edit_field,
1237 if (camera_render_text(
1239 ids + layer->selection * ENTITY_MAX_ID_SIZE,
1240 RECT_LAYER_ID_LABEL_SIZE,
1241 color_invert(color),
1249 const Color color = color_picker_rgba(&layer->color_picker);
1250 if (layer->state == RECT_LAYER_CREATE) {
1251 if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
1256 if (active && color_picker_render(&layer->color_picker, camera) < 0) {
1264 int rect_layer_event_recolor(RectLayer *layer,
1265 const SDL_Event *event,
1266 const Camera *camera,
1267 UndoHistory *undo_history)
1269 trace_assert(layer);
1270 trace_assert(event);
1271 trace_assert(camera);
1272 trace_assert(undo_history);
1273 trace_assert(layer->selection >= 0);
1275 int color_changed = 0;
1276 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
1280 if (color_changed) {
1281 layer->inter_color = color_picker_rgba(&layer->color_picker);
1283 if (!color_picker_drag(&layer->color_picker)) {
1284 UNDO_PUSH(undo_history, create_undo_update_context(layer));
1285 dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
1286 layer->state = RECT_LAYER_IDLE;
1293 int rect_layer_event(RectLayer *layer,
1294 const SDL_Event *event,
1295 const Camera *camera,
1296 UndoHistory *undo_history)
1298 trace_assert(layer);
1299 trace_assert(event);
1300 trace_assert(undo_history);
1302 switch (event->type) {
1303 case SDL_WINDOWEVENT: {
1304 switch (event->window.event) {
1305 case SDL_WINDOWEVENT_RESIZED: {
1306 grid_relayout(layer->grid, rect(0.0f, 0.0f,
1307 (float) event->window.data1,
1308 (float) event->window.data2));
1314 switch (layer->state) {
1315 case RECT_LAYER_IDLE:
1316 return rect_layer_event_idle(layer, event, camera, undo_history);
1318 case RECT_LAYER_CREATE:
1319 return rect_layer_event_create(layer, event, camera, undo_history);
1321 case RECT_LAYER_RESIZE:
1322 return rect_layer_event_resize(layer, event, camera, undo_history);
1324 case RECT_LAYER_MOVE:
1325 return rect_layer_event_move(layer, event, camera, undo_history);
1327 case RECT_LAYER_ID_RENAME:
1328 return rect_layer_event_id_rename(layer, event, camera, undo_history);
1330 case RECT_LAYER_RECOLOR:
1331 return rect_layer_event_recolor(layer, event, camera, undo_history);
1338 size_t rect_layer_count(const RectLayer *layer)
1340 return dynarray_count(layer->rects);
1343 const Rect *rect_layer_rects(const RectLayer *layer)
1345 return dynarray_data(layer->rects);
1348 const Color *rect_layer_colors(const RectLayer *layer)
1350 return dynarray_data(layer->colors);
1353 const char *rect_layer_ids(const RectLayer *layer)
1355 return dynarray_data(layer->ids);
1358 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
1360 trace_assert(layer);
1361 trace_assert(filedump);
1363 size_t n = dynarray_count(layer->ids);
1364 char *ids = dynarray_data(layer->ids);
1365 Rect *rects = dynarray_data(layer->rects);
1366 Color *colors = dynarray_data(layer->colors);
1367 Action *actions = dynarray_data(layer->actions);
1369 fprintf(filedump, "%zd\n", n);
1370 for (size_t i = 0; i < n; ++i) {
1371 fprintf(filedump, "%s %f %f %f %f ",
1372 ids + ENTITY_MAX_ID_SIZE * i,
1373 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
1374 color_hex_to_stream(colors[i], filedump);
1376 switch (actions[i].type) {
1377 case ACTION_NONE: {} break;
1379 case ACTION_TOGGLE_GOAL:
1380 case ACTION_HIDE_LABEL: {
1381 fprintf(filedump, " %d %.*s",
1382 (int)actions[i].type,
1383 ENTITY_MAX_ID_SIZE, actions[i].entity_id);
1385 case ACTION_N: break;
1388 fprintf(filedump, "\n");
1394 const Action *rect_layer_actions(const RectLayer *layer)
1396 return dynarray_data(layer->actions);