]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/rect_layer.c
(#1153) Fix square ratio snapping
[nothing.git] / src / game / level / level_editor / rect_layer.c
1 #include <float.h>
2 #include <errno.h>
3
4 #include "game/camera.h"
5 #include "system/lt.h"
6 #include "system/stacktrace.h"
7 #include "system/nth_alloc.h"
8 #include "system/log.h"
9 #include "math/rect.h"
10 #include "color.h"
11 #include "rect_layer.h"
12 #include "dynarray.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"
20 #include "game.h"
21
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
28
29 static int clipboard = 0;
30 static Rect clipboard_rect;
31 static Color clipboard_color;
32
33 static Cursor_Style resize_styles[1 << RECT_SIDE_N] = {
34     0,                         // [0]
35     CURSOR_STYLE_RESIZE_VERT,  // [1]
36     CURSOR_STYLE_RESIZE_HORIS, // [2]
37     CURSOR_STYLE_RESIZE_DIAG1, // [3]
38     CURSOR_STYLE_RESIZE_VERT,  // [4]
39     0,                         // [5]
40     CURSOR_STYLE_RESIZE_DIAG2, // [6]
41     0,                         // [7]
42     CURSOR_STYLE_RESIZE_HORIS, // [8]
43     CURSOR_STYLE_RESIZE_DIAG2, // [9]
44     0,                         // [10]
45     0,                         // [11]
46     CURSOR_STYLE_RESIZE_DIAG1  // [12]
47 };
48
49 typedef enum {
50     RECT_LAYER_IDLE = 0,
51     RECT_LAYER_CREATE,
52     RECT_LAYER_RESIZE,
53     RECT_LAYER_MOVE,
54     RECT_LAYER_ID_RENAME,
55     RECT_LAYER_RECOLOR
56 } RectLayerState;
57
58 struct RectLayer {
59     Lt *lt;
60     RectLayerState state;
61     int resize_mask;
62     Dynarray *ids;
63     Dynarray *rects;
64     Dynarray *colors;
65     Dynarray *actions;
66     ColorPicker color_picker;
67     ActionPicker action_picker;
68     Vec2f create_begin;
69     Vec2f create_end;
70     int selection;
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;
74     Color inter_color;
75     Rect inter_rect;
76     int id_name_counter;
77     const char *id_name_prefix;
78     Grid *grid;
79     Cursor *cursor;
80
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
83     // to fix it to.
84     Vec2f initial_rectangle_size;
85 };
86
87 typedef enum {
88     UNDO_ADD,
89     UNDO_DELETE,
90     UNDO_UPDATE,
91     UNDO_SWAP
92 } UndoType;
93
94 // Delete, Update
95 typedef struct {
96     UndoType type;
97     RectLayer *layer;
98     size_t index;
99     Rect rect;
100     Color color;
101     Action action;
102     char id[ENTITY_MAX_ID_SIZE];
103 } UndoElementContext;
104
105 // Add
106 typedef struct {
107     UndoType type;
108     RectLayer *layer;
109     size_t index;
110 } UndoAddContext;
111
112 // Swap
113 typedef struct {
114     UndoType type;
115     RectLayer *layer;
116     size_t index1;
117     size_t index2;
118 } UndoSwapContext;
119
120 typedef union {
121     UndoType type;
122     UndoAddContext add;
123     UndoElementContext element;
124     UndoSwapContext swap;
125 } UndoContext;
126
127 static
128 UndoContext create_undo_add_context(RectLayer *layer, size_t index)
129 {
130     trace_assert(layer);
131     trace_assert(index < dynarray_count(layer->rects));
132
133     UndoContext undo_context;
134     undo_context.add.type = UNDO_ADD;
135     undo_context.add.layer = layer;
136     undo_context.add.index = index;
137     return undo_context;
138 }
139
140 static
141 UndoContext create_undo_element_context(RectLayer *layer)
142 {
143     trace_assert(layer);
144     size_t index = (size_t) layer->selection;
145     trace_assert(index < dynarray_count(layer->rects));
146
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);
154     return undo_context;
155 }
156
157 static
158 UndoContext create_undo_update_context(RectLayer *rect_layer)
159 {
160     UndoContext undo_context = create_undo_element_context(rect_layer);
161     undo_context.type = UNDO_UPDATE;
162     return undo_context;
163 }
164
165 static
166 UndoContext create_undo_delete_context(RectLayer *rect_layer)
167 {
168     UndoContext undo_context = create_undo_element_context(rect_layer);
169     undo_context.type = UNDO_DELETE;
170     return undo_context;
171 }
172
173 static
174 UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
175 {
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;
181     return undo_context;
182 }
183
184 static
185 void rect_layer_undo(void *context, size_t context_size)
186 {
187     trace_assert(context);
188     trace_assert(sizeof(UndoContext) == context_size);
189
190     UndoContext *undo_context = context;
191
192     switch (undo_context->type) {
193     case UNDO_ADD: {
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;
200     } break;
201
202     case UNDO_DELETE: {
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;
209     } break;
210
211     case UNDO_UPDATE: {
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);
217     } break;
218
219     case UNDO_SWAP: {
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);
225     } break;
226     }
227 }
228
229 #define UNDO_PUSH(HISTORY, CONTEXT)                                     \
230     do {                                                                \
231         UndoContext context = (CONTEXT);                                \
232         undo_history_push(                                              \
233             HISTORY,                                                    \
234             rect_layer_undo,                                            \
235             &context,                                                   \
236             sizeof(context));                                           \
237     } while(0)
238
239 static int rect_layer_add_rect(RectLayer *layer,
240                                Rect rect,
241                                Color color,
242                                UndoHistory *undo_history)
243 {
244     trace_assert(layer);
245
246     if (dynarray_push(layer->rects, &rect) < 0) {
247         return -1;
248     }
249
250     if (dynarray_push(layer->colors, &color) < 0) {
251         return -1;
252     }
253
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)) {
259         return -1;
260     }
261
262     dynarray_push_empty(layer->actions);
263
264     UNDO_PUSH(
265         undo_history,
266         create_undo_add_context(
267             layer,
268             dynarray_count(layer->rects) - 1));
269
270     return 0;
271 }
272
273 static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
274 {
275     trace_assert(layer);
276
277     int n = (int) dynarray_count(layer->rects);
278     Rect *rects = dynarray_data(layer->rects);
279
280     for (int i = n - 1; i >= 0; --i) {
281         if (rect_contains_point(rects[i], position)) {
282             return (int) i;
283         }
284     }
285
286     return -1;
287 }
288
289 static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
290                                      UndoHistory *undo_history)
291 {
292     trace_assert(layer);
293     trace_assert(a < dynarray_count(layer->rects));
294     trace_assert(b < dynarray_count(layer->rects));
295
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);
300
301     UNDO_PUSH(undo_history, create_undo_swap_context(layer, a, b));
302 }
303
304 static int rect_layer_delete_rect_at(RectLayer *layer,
305                                      size_t i,
306                                      UndoHistory *undo_history)
307 {
308     trace_assert(layer);
309
310     UNDO_PUSH(undo_history, create_undo_delete_context(layer));
311
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);
316
317     return 0;
318 }
319
320 static int calc_resize_mask(Vec2f point, Rect rect)
321 {
322     int mask = 0;
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);
326         }
327     }
328     return mask;
329 }
330
331 static int rect_layer_event_idle(RectLayer *layer,
332                                  const SDL_Event *event,
333                                  const Camera *camera,
334                                  UndoHistory *undo_history)
335 {
336     trace_assert(layer);
337     trace_assert(event);
338     trace_assert(camera);
339
340     int color_changed = 0;
341     if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
342         return -1;
343     }
344
345     if (color_changed) {
346         if (layer->selection >= 0) {
347             dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
348             layer->state = RECT_LAYER_RECOLOR;
349         }
350         return 0;
351     }
352
353     Rect *rects = dynarray_data(layer->rects);
354
355     switch (event->type) {
356     case SDL_MOUSEBUTTONDOWN: {
357         switch (event->button.button) {
358         case SDL_BUTTON_LEFT: {
359             Vec2f position = camera_map_screen(
360                 camera,
361                 event->button.x,
362                 event->button.y);
363             int rect_at_position =
364                 rect_layer_rect_at(layer, position);
365
366
367             Color *colors = dynarray_data(layer->colors);
368
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(
380                     position,
381                     vec(
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);
387             } else {
388                 layer->selection = rect_at_position;
389
390                 if (layer->selection < 0) {
391                     layer->state = RECT_LAYER_CREATE;
392                     layer->create_begin = position;
393                     layer->create_end = position;
394                 }
395             }
396
397             if (layer->selection >= 0) {
398                 layer->initial_rectangle_size = vec(rects[layer->selection].w,
399                     rects[layer->selection].h);
400             }
401         } break;
402         }
403     } break;
404
405     case SDL_MOUSEMOTION: {
406         int resize_mask = 0;
407         Vec2f position = camera_map_screen(
408             camera,
409             event->button.x,
410             event->button.y);
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];
417         } else {
418             layer->cursor->style = CURSOR_STYLE_POINTER;
419         }
420     } break;
421
422     case SDL_KEYDOWN: {
423         switch (event->key.keysym.sym) {
424         case SDLK_UP: {
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(
429                     layer,
430                     (size_t) layer->selection,
431                     (size_t) layer->selection + 1,
432                     undo_history);
433                 layer->selection++;
434             }
435         } break;
436
437         case SDLK_DOWN: {
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(
442                     layer,
443                     (size_t) layer->selection,
444                     (size_t) layer->selection - 1,
445                     undo_history);
446                 layer->selection--;
447             }
448         } break;
449
450         case SDLK_DELETE: {
451             if (layer->selection >= 0) {
452                 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
453                 layer->selection = -1;
454             }
455         } break;
456
457         case SDLK_F2: {
458             if (layer->selection >= 0) {
459                 const char *ids = dynarray_data(layer->ids);
460                 Color *colors = dynarray_data(layer->colors);
461
462                 edit_field_restyle(
463                     layer->id_edit_field,
464                     RECT_LAYER_ID_LABEL_SIZE,
465                     color_invert(colors[layer->selection]));
466
467                 layer->state = RECT_LAYER_ID_RENAME;
468                 edit_field_replace(
469                     layer->id_edit_field,
470                     ids + layer->selection * ENTITY_MAX_ID_SIZE);
471                 SDL_StartTextInput();
472             }
473         } break;
474
475         case SDLK_c: {
476             if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
477                 clipboard = 1;
478                 dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
479                 dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
480             }
481         } break;
482
483         case SDLK_v: {
484             if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
485                 int x, y;
486                 SDL_GetMouseState(&x, &y);
487                 Vec2f position = camera_map_screen(camera, x, y);
488
489                 rect_layer_add_rect(
490                     layer,
491                     rect(position.x, position.y,
492                          clipboard_rect.w, clipboard_rect.h),
493                     clipboard_color,
494                     undo_history);
495             }
496         } break;
497         }
498     } break;
499     }
500
501     return 0;
502 }
503
504 static int rect_layer_event_create(RectLayer *layer,
505                                    const SDL_Event *event,
506                                    const Camera *camera,
507                                    UndoHistory *undo_history)
508 {
509     trace_assert(layer);
510     trace_assert(event);
511     trace_assert(camera);
512
513     switch (event->type) {
514     case SDL_MOUSEBUTTONUP: {
515         switch (event->button.button) {
516         case SDL_BUTTON_LEFT: {
517             const Rect real_rect =
518                 rect_from_points(
519                     layer->create_begin,
520                     layer->create_end);
521             const float area = real_rect.w * real_rect.h;
522
523             if (area >= CREATE_AREA_THRESHOLD) {
524                 rect_layer_add_rect(
525                     layer,
526                     real_rect,
527                     color_picker_rgba(&layer->color_picker),
528                     undo_history);
529             } else {
530                 log_info("The area is too small %f. Such small box won't be created.\n", area);
531             }
532             layer->state = RECT_LAYER_IDLE;
533         } break;
534         }
535     } break;
536
537     case SDL_MOUSEMOTION: {
538         layer->create_end = camera_map_screen(
539             camera,
540             event->motion.x,
541             event->motion.y);
542     } break;
543     }
544     return 0;
545 }
546
547 static inline
548 int segment_overlap(Vec2f a, Vec2f b)
549 {
550     trace_assert(a.x <= a.y);
551     trace_assert(b.x <= b.y);
552     return a.y >= b.x && b.y >= a.x;
553 }
554
555 static
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
561 {
562     if (fabsf((*x + xo) - (y + yo)) < st) {
563         *x = y + yo - xo;
564         return true;
565     }
566     return false;
567 }
568
569 static
570 int snap_var2seg(float *x, float y,
571                float xo, float yo,
572                float st)
573 {
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);
576 }
577
578 static
579 void snap_seg2seg(float *x, float y, float xo, float yo, float st)
580 {
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);
585 }
586
587 static
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
591 {
592     trace_assert(x);
593     trace_assert(y);
594
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]))
597         return;
598
599     float ratio = layer->initial_rectangle_size.x / layer->initial_rectangle_size.y;
600
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])
603         ratio = 1.0f;
604
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;
609
610     float ab_w = fabsf(w);
611     float ab_h = fabsf(h);
612
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);
620     }
621 }
622
623
624 static int rect_layer_event_resize(RectLayer *layer,
625                                    const SDL_Event *event,
626                                    const Camera *camera,
627                                    UndoHistory *undo_history)
628 {
629     trace_assert(layer);
630     trace_assert(event);
631     trace_assert(camera);
632     trace_assert(layer->selection >= 0);
633
634     Rect *rects = dynarray_data(layer->rects);
635
636     float scaled_snap_threshold = SNAPPING_THRESHOLD / camera->scale;
637
638     switch (event->type) {
639     case SDL_MOUSEMOTION: {
640         Vec2f position = camera_map_screen(
641             camera,
642             event->button.x,
643             event->button.y);
644
645         switch (layer->resize_mask) {
646         case 1: {               // TOP
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;
652
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);
656                 }
657             }
658
659             layer->inter_rect = rect_from_points(
660                 vec(x, y),
661                 rect_position2(rects[layer->selection]));
662         } break;
663
664         case 2: {               // LEFT
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;
670
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);
674                 }
675             }
676
677             layer->inter_rect = rect_from_points(
678                 vec(x, y),
679                 rect_position2(rects[layer->selection]));
680         } break;
681
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;
687
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);
691
692             for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
693                 if (i == (size_t) layer->selection) continue;
694
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);
701                     }
702                 }
703
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);
707                     }
708                 }
709             }
710
711             layer->inter_rect = rect_from_points(
712                 vec(x, y),
713                 rect_position2(rects[layer->selection]));
714         } break;
715
716         case 4: {               // BOTTOM
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;
722
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);
726                 }
727             }
728
729             layer->inter_rect = rect_from_points(
730                 rect_position(rects[layer->selection]),
731                 vec(rects[layer->selection].x + rects[layer->selection].w,
732                     y));
733         } break;
734
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;
740
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);
744
745             for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
746                 if (i == (size_t) layer->selection) continue;
747
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);
752                     }
753                 }
754
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);
758                     }
759                 }
760             }
761
762             layer->inter_rect = rect_from_points(
763                 vec(x, rects[layer->selection].y),
764                 vec(rects[layer->selection].x + rects[layer->selection].w,
765                     y));
766         } break;
767
768         case 8: {               // RIGHT
769             float y = rects[layer->selection].y;
770             float x = position.x;
771             float h = rects[layer->selection].h;
772
773             for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
774                 if (i == (size_t) layer->selection) continue;
775
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);
779                 }
780             }
781
782             layer->inter_rect = rect_from_points(
783                 rect_position(rects[layer->selection]),
784                 vec(x,
785                     rects[layer->selection].y + rects[layer->selection].h));
786         } break;
787
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;
793
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);
797
798             for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
799                 if (i == (size_t) layer->selection) continue;
800
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);
805                     }
806                 }
807
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);
811                     }
812                 }
813             }
814
815             layer->inter_rect = rect_from_points(
816                 vec(rects[layer->selection].x, y),
817                 vec(x,
818                     rects[layer->selection].y + rects[layer->selection].h));
819         } break;
820
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;
826
827             // use top-left as reference.
828             Vec2f ref_pos = rect_position(rects[layer->selection]);
829             fix_rect_ratio(layer, &x, &y, ref_pos);
830
831             for (size_t i = 0; i < dynarray_count(layer->rects); ++i) {
832                 if (i == (size_t) layer->selection) continue;
833
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);
838                     }
839                 }
840
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);
844                     }
845                 }
846             }
847
848             layer->inter_rect = rect_from_points(
849                 rect_position(rects[layer->selection]),
850                 vec(x, y));
851         } break;
852         }
853
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);
857
858     } break;
859
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);
864     } break;
865     }
866
867     return 0;
868 }
869
870 static
871 void snap_rects(size_t ignore_index, Rect *a,
872                 Rect *rects, size_t rects_size,
873                 float snapping_threshold)
874 {
875     trace_assert(rects);
876     trace_assert(a);
877
878     for (size_t i = 0; i < rects_size; ++i) {
879         if (i == ignore_index) continue;
880
881         const Rect b = rects[i];
882
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);
885         }
886
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);
889         }
890     }
891 }
892
893 static int rect_layer_event_move(RectLayer *layer,
894                                  const SDL_Event *event,
895                                  const Camera *camera,
896                                  UndoHistory *undo_history)
897 {
898     trace_assert(layer);
899     trace_assert(event);
900     trace_assert(camera);
901     trace_assert(layer->selection >= 0);
902
903     Rect *rects = dynarray_data(layer->rects);
904
905     switch (event->type) {
906     case SDL_MOUSEMOTION: {
907         const Uint8 *state = SDL_GetKeyboardState(NULL);
908         const Vec2f mouse_pos = vec_sub(
909             camera_map_screen(
910                 camera,
911                 event->button.x,
912                 event->button.y),
913             layer->move_anchor);
914
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;
918         } else {
919             const Vec2f rect_pos = rect_position(rects[layer->selection]);
920
921             const float dx = fabsf(rect_pos.x - mouse_pos.x);
922             const float dy = fabsf(rect_pos.y - mouse_pos.y);
923
924             if (dx > dy) {
925                 layer->inter_rect.x = mouse_pos.x;
926                 layer->inter_rect.y = rect_pos.y;
927             } else {
928                 layer->inter_rect.x = rect_pos.x;
929                 layer->inter_rect.y = mouse_pos.y;
930             }
931         }
932
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);
937     } break;
938
939     case SDL_MOUSEBUTTONUP: {
940         layer->state = RECT_LAYER_IDLE;
941
942         float distance = vec_length(
943             vec_sub(rect_position(layer->inter_rect),
944                     rect_position(rects[layer->selection])));
945
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);
949         }
950     } break;
951     }
952     return 0;
953 }
954
955 static int rect_layer_event_id_rename(RectLayer *layer,
956                                       const SDL_Event *event,
957                                       const Camera *camera,
958                                       UndoHistory *undo_history)
959 {
960     trace_assert(layer);
961     trace_assert(event);
962     trace_assert(camera);
963     trace_assert(layer->selection >= 0);
964
965     switch (event->type) {
966     case SDL_KEYDOWN: {
967         switch (event->key.keysym.sym) {
968         case SDLK_RETURN: {
969             UNDO_PUSH(undo_history, create_undo_update_context(layer));
970
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;
975             SDL_StopTextInput();
976         } break;
977
978         case SDLK_ESCAPE: {
979             layer->state = RECT_LAYER_IDLE;
980             SDL_StopTextInput();
981         } break;
982         }
983     } break;
984     }
985
986     return edit_field_event(layer->id_edit_field, event);
987 }
988
989 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
990 {
991     LayerPtr layer = {
992         .type = LAYER_RECT,
993         .ptr = rect_layer
994     };
995     return layer;
996 }
997
998 RectLayer *create_rect_layer(const char *id_name_prefix, Cursor *cursor)
999 {
1000     trace_assert(cursor);
1001
1002     Lt *lt = create_lt();
1003
1004     RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
1005     if (layer == NULL) {
1006         RETURN_LT(lt, NULL);
1007     }
1008     layer->lt = lt;
1009
1010     layer->ids = PUSH_LT(
1011         lt,
1012         create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE),
1013         destroy_dynarray);
1014     if (layer->ids == NULL) {
1015         RETURN_LT(lt, NULL);
1016     }
1017
1018     layer->rects = PUSH_LT(
1019         lt,
1020         create_dynarray(sizeof(Rect)),
1021         destroy_dynarray);
1022     if (layer->rects == NULL) {
1023         RETURN_LT(lt, NULL);
1024     }
1025
1026     layer->colors = PUSH_LT(
1027         lt,
1028         create_dynarray(sizeof(Color)),
1029         destroy_dynarray);
1030     if (layer->colors == NULL) {
1031         RETURN_LT(lt, NULL);
1032     }
1033
1034     layer->actions = PUSH_LT(
1035         lt,
1036         create_dynarray(sizeof(Action)),
1037         destroy_dynarray);
1038     if (layer->actions == NULL) {
1039         RETURN_LT(lt, NULL);
1040     }
1041
1042     layer->id_edit_field = PUSH_LT(
1043         lt,
1044         create_edit_field(
1045             RECT_LAYER_ID_LABEL_SIZE,
1046             COLOR_BLACK),
1047         destroy_edit_field);
1048     if (layer->id_edit_field == NULL) {
1049         RETURN_LT(lt, NULL);
1050     }
1051
1052     layer->grid =
1053         PUSH_LT(
1054             lt,
1055             nth_calloc(
1056                 1,
1057                 sizeof(Grid) + sizeof(Widget*) * RECT_LAYER_GRID_ROWS * RECT_LAYER_GRID_COLUMNS),
1058             free);
1059     if (layer->grid == NULL) {
1060         RETURN_LT(lt, NULL);
1061     }
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);
1065
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;
1070
1071     return layer;
1072 }
1073
1074 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream,
1075                                               const char *id_name_prefix,
1076                                               Cursor *cursor)
1077 {
1078     trace_assert(line_stream);
1079
1080     RectLayer *layer = create_rect_layer(id_name_prefix, cursor);
1081     if (layer == NULL) {
1082         return NULL;
1083     }
1084
1085     const char *line = line_stream_next(line_stream);
1086     if (line == NULL) {
1087         RETURN_LT(layer->lt, NULL);
1088     }
1089
1090     size_t count = 0;
1091     if (sscanf(line, "%zu", &count) < 0) {
1092         RETURN_LT(layer->lt, NULL);
1093     }
1094
1095     for (size_t i = 0; i < count; ++i) {
1096         line = line_stream_next(line_stream);
1097         if (line == NULL) {
1098             RETURN_LT(layer->lt, NULL);
1099         }
1100
1101         char hex[7];
1102         Rect rect;
1103         char id[ENTITY_MAX_ID_SIZE];
1104
1105         int n = 0;
1106         if (sscanf(line,
1107                    "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n",
1108                    id,
1109                    &rect.x, &rect.y,
1110                    &rect.w, &rect.h,
1111                    hex, &n) <= 0) {
1112             log_fail("%s\n", strerror(errno));
1113             RETURN_LT(layer->lt, NULL);
1114         }
1115         line += n;
1116
1117         Color color = hexstr(hex);
1118         dynarray_push(layer->rects, &rect);
1119         dynarray_push(layer->ids, id);
1120         dynarray_push(layer->colors, &color);
1121
1122         Action action = {
1123             .type = ACTION_NONE,
1124             .entity_id = {0}
1125         };
1126
1127         if (sscanf(line, "%d%n", (int*)&action.type, &n) > 0) {
1128             line += n;
1129             switch (action.type) {
1130             case ACTION_NONE: break;
1131
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);
1137                 }
1138             } break;
1139
1140             case ACTION_N: break;
1141             }
1142         }
1143
1144         dynarray_push(layer->actions, &action);
1145     }
1146
1147     return layer;
1148 }
1149
1150 void destroy_rect_layer(RectLayer *layer)
1151 {
1152     trace_assert(layer);
1153     RETURN_LT0(layer->lt);
1154 }
1155
1156 int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
1157 {
1158     trace_assert(layer);
1159     trace_assert(camera);
1160
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);
1165
1166     // The Rectangles
1167     for (size_t i = 0; i < n; ++i) {
1168         Rect rect = rects[i];
1169         Color color = colors[i];
1170
1171         if (layer->selection == (int) i) {
1172             if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1173                 rect = layer->inter_rect;
1174             }
1175
1176             if (layer->state == RECT_LAYER_RECOLOR) {
1177                 color = layer->inter_color;
1178             }
1179         }
1180
1181         // Main Rectangle
1182         if (camera_fill_rect(
1183                 camera,
1184                 rect,
1185                 color_scale(
1186                     color,
1187                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
1188             return -1;
1189         }
1190     }
1191
1192     // Selection Overlay
1193     if (active && layer->selection >= 0) {
1194         Rect rect = rects[layer->selection];
1195         Color color = colors[layer->selection];
1196
1197         if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1198             rect = layer->inter_rect;
1199         }
1200
1201         if (layer->state == RECT_LAYER_RECOLOR) {
1202             color = layer->inter_color;
1203         }
1204
1205         const Rect overlay_rect =
1206             rect_pad(
1207                 camera_rect(camera, rect),
1208                 -RECT_LAYER_SELECTION_THICCNESS * 0.5f);
1209         const Color overlay_color = color_invert(color);
1210
1211         // Selection
1212         if (camera_draw_thicc_rect_screen(
1213                 camera,
1214                 overlay_rect,
1215                 overlay_color,
1216                 RECT_LAYER_SELECTION_THICCNESS) < 0) {
1217             return -1;
1218         }
1219
1220         const Vec2f rect_id_pos = vec_sub(
1221             rect_position(rect),
1222             vec_mult(
1223                 RECT_LAYER_ID_LABEL_SIZE,
1224                 vec(0.0f, FONT_CHAR_HEIGHT)));
1225
1226         // Rectangle Id
1227         if (layer->state == RECT_LAYER_ID_RENAME) {
1228             // ID renaming Edit Field
1229             if (edit_field_render_world(
1230                     layer->id_edit_field,
1231                     camera,
1232                     rect_id_pos) < 0) {
1233                 return -1;
1234             }
1235         } else {
1236             // Id text
1237             if (camera_render_text(
1238                     camera,
1239                     ids + layer->selection * ENTITY_MAX_ID_SIZE,
1240                     RECT_LAYER_ID_LABEL_SIZE,
1241                     color_invert(color),
1242                     rect_id_pos) < 0) {
1243                 return -1;
1244             }
1245         }
1246     }
1247
1248     // Proto Rectangle
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) {
1252             return -1;
1253         }
1254     }
1255
1256     if (active && color_picker_render(&layer->color_picker, camera) < 0) {
1257         return -1;
1258     }
1259
1260     return 0;
1261 }
1262
1263 static
1264 int rect_layer_event_recolor(RectLayer *layer,
1265                              const SDL_Event *event,
1266                              const Camera *camera,
1267                              UndoHistory *undo_history)
1268 {
1269     trace_assert(layer);
1270     trace_assert(event);
1271     trace_assert(camera);
1272     trace_assert(undo_history);
1273     trace_assert(layer->selection >= 0);
1274
1275     int color_changed = 0;
1276     if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
1277         return -1;
1278     }
1279
1280     if (color_changed) {
1281         layer->inter_color = color_picker_rgba(&layer->color_picker);
1282
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;
1287         }
1288     }
1289
1290     return 0;
1291 }
1292
1293 int rect_layer_event(RectLayer *layer,
1294                      const SDL_Event *event,
1295                      const Camera *camera,
1296                      UndoHistory *undo_history)
1297 {
1298     trace_assert(layer);
1299     trace_assert(event);
1300     trace_assert(undo_history);
1301
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));
1309         } break;
1310         }
1311     } break;
1312     }
1313
1314     switch (layer->state) {
1315     case RECT_LAYER_IDLE:
1316         return rect_layer_event_idle(layer, event, camera, undo_history);
1317
1318     case RECT_LAYER_CREATE:
1319         return rect_layer_event_create(layer, event, camera, undo_history);
1320
1321     case RECT_LAYER_RESIZE:
1322         return rect_layer_event_resize(layer, event, camera, undo_history);
1323
1324     case RECT_LAYER_MOVE:
1325         return rect_layer_event_move(layer, event, camera, undo_history);
1326
1327     case RECT_LAYER_ID_RENAME:
1328         return rect_layer_event_id_rename(layer, event, camera, undo_history);
1329
1330     case RECT_LAYER_RECOLOR:
1331         return rect_layer_event_recolor(layer, event, camera, undo_history);
1332     }
1333
1334
1335     return 0;
1336 }
1337
1338 size_t rect_layer_count(const RectLayer *layer)
1339 {
1340     return dynarray_count(layer->rects);
1341 }
1342
1343 const Rect *rect_layer_rects(const RectLayer *layer)
1344 {
1345     return dynarray_data(layer->rects);
1346 }
1347
1348 const Color *rect_layer_colors(const RectLayer *layer)
1349 {
1350     return dynarray_data(layer->colors);
1351 }
1352
1353 const char *rect_layer_ids(const RectLayer *layer)
1354 {
1355     return dynarray_data(layer->ids);
1356 }
1357
1358 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
1359 {
1360     trace_assert(layer);
1361     trace_assert(filedump);
1362
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);
1368
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);
1375
1376         switch (actions[i].type) {
1377         case ACTION_NONE: {} break;
1378
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);
1384         } break;
1385         case ACTION_N: break;
1386         }
1387
1388         fprintf(filedump, "\n");
1389     }
1390
1391     return 0;
1392 }
1393
1394 const Action *rect_layer_actions(const RectLayer *layer)
1395 {
1396     return dynarray_data(layer->actions);
1397 }