]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/rect_layer.c
(#1034) Use a single undo context for the entire RectLayer
[nothing.git] / src / game / level / level_editor / rect_layer.c
1 #include "game/camera.h"
2 #include "system/lt.h"
3 #include "system/stacktrace.h"
4 #include "system/nth_alloc.h"
5 #include "system/log.h"
6 #include "math/rect.h"
7 #include "color.h"
8 #include "rect_layer.h"
9 #include "dynarray.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"
15
16 #define RECT_LAYER_ID_MAX_SIZE 36
17 #define RECT_LAYER_SELECTION_THICCNESS 10.0f
18 #define CREATE_AREA_THRESHOLD 10.0
19
20 // TODO(#1034): Can we use a single Context for everything in RectLayer
21
22 typedef enum {
23     RECT_LAYER_IDLE = 0,
24     RECT_LAYER_CREATE,
25     // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
26     RECT_LAYER_RESIZE,
27     RECT_LAYER_MOVE,
28     // TODO(#1035): id renaming in RectLayer is ugly
29     RECT_LAYER_ID_RENAME,
30 } RectLayerState;
31
32 struct RectLayer {
33     Lt *lt;
34     RectLayerState state;
35     Dynarray *ids;
36     Dynarray *rects;
37     Dynarray *colors;
38     ColorPicker color_picker;
39     Vec create_begin;
40     Vec create_end;
41     int selection;
42     Vec move_anchor;
43     Edit_field *id_edit_field;
44     Color prev_color;
45     Rect prev_rect;
46 };
47
48 typedef enum {
49     UNDO_ADD,
50     UNDO_DELETE,
51     UNDO_UPDATE
52 } UndoType;
53
54 typedef struct {
55     UndoType type;
56     Rect rect;
57     Color color;
58     char id[RECT_LAYER_ID_MAX_SIZE];
59     size_t index;
60 } UndoContext;
61
62 static
63 UndoContext rect_layer_create_undo_context(RectLayer *rect_layer, size_t index, UndoType type)
64 {
65     trace_assert(rect_layer);
66     trace_assert(index < dynarray_count(rect_layer->rects));
67
68     UndoContext undo_context = {
69         .type = type,
70         .rect = rect_layer->prev_rect,
71         .color = rect_layer->prev_color,
72         .index = index
73     };
74
75     dynarray_copy_to(rect_layer->ids, undo_context.id, index);
76
77     return undo_context;
78 }
79
80 static
81 void rect_layer_undo(void *layer, Context context)
82 {
83     trace_assert(layer);
84     RectLayer *rect_layer = layer;
85
86     trace_assert(sizeof(UndoContext) < CONTEXT_SIZE);
87     UndoContext *undo_context = (UndoContext *)context.data;
88
89     switch (undo_context->type) {
90     case UNDO_ADD: {
91         dynarray_delete_at(rect_layer->rects, undo_context->index);
92         dynarray_delete_at(rect_layer->colors, undo_context->index);
93         dynarray_delete_at(rect_layer->ids, undo_context->index);
94         rect_layer->selection = -1;
95     } break;
96
97     case UNDO_DELETE: {
98         dynarray_insert_before(rect_layer->rects, undo_context->index, &undo_context->rect);
99         dynarray_insert_before(rect_layer->colors, undo_context->index, &undo_context->color);
100         dynarray_insert_before(rect_layer->ids, undo_context->index, &undo_context->id);
101         rect_layer->selection = -1;
102     } break;
103
104     case UNDO_UPDATE: {
105         dynarray_replace_at(rect_layer->rects, undo_context->index, &undo_context->rect);
106         dynarray_replace_at(rect_layer->colors, undo_context->index, &undo_context->color);
107         dynarray_replace_at(rect_layer->ids, undo_context->index, &undo_context->id);
108     } break;
109     }
110 }
111
112 static int rect_layer_add_rect(RectLayer *layer,
113                                Rect rect,
114                                Color color,
115                                UndoHistory *undo_history)
116 {
117     trace_assert(layer);
118
119     size_t index = dynarray_count(layer->rects);
120
121     if (dynarray_push(layer->rects, &rect) < 0) {
122         return -1;
123     }
124
125     if (dynarray_push(layer->colors, &color) < 0) {
126         return -1;
127     }
128
129     char id[RECT_LAYER_ID_MAX_SIZE];
130     for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
131         id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
132     }
133     id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
134
135     if (dynarray_push(layer->ids, id)) {
136         return -1;
137     }
138
139     UndoContext context =
140         rect_layer_create_undo_context(layer, index, UNDO_ADD);
141
142     undo_history_push(
143         undo_history,
144         create_action(
145             layer,
146             rect_layer_undo,
147             &context, sizeof(context)));
148
149     return 0;
150 }
151
152 // TODO(#956): rect_layer_rect_at doesn't return rectangles according to some z-order
153 static int rect_layer_rect_at(RectLayer *layer, Vec position)
154 {
155     trace_assert(layer);
156
157     const size_t n = dynarray_count(layer->rects);
158     Rect *rects = dynarray_data(layer->rects);
159
160     for (size_t i = 0; i < n; ++i) {
161         if (rect_contains_point(rects[i], position)) {
162             return (int) i;
163         }
164     }
165
166     return -1;
167 }
168
169 static Rect rect_layer_resize_anchor(const RectLayer *layer, const Camera *camera, size_t i)
170 {
171     Rect *rects = dynarray_data(layer->rects);
172     const Rect overlay_rect =
173         rect_scale(
174             camera_rect(camera, rects[i]),
175             RECT_LAYER_SELECTION_THICCNESS * 0.5f);
176
177     return rect(
178         overlay_rect.x + overlay_rect.w,
179         overlay_rect.y + overlay_rect.h,
180         RECT_LAYER_SELECTION_THICCNESS * 2.0f,
181         RECT_LAYER_SELECTION_THICCNESS * 2.0f);
182 }
183
184 static int rect_layer_delete_rect_at(RectLayer *layer,
185                                      size_t i,
186                                      UndoHistory *undo_history)
187 {
188     trace_assert(layer);
189
190     UndoContext context = rect_layer_create_undo_context(layer, i, UNDO_DELETE);
191
192     undo_history_push(
193         undo_history,
194         create_action(
195             layer,
196             rect_layer_undo,
197             &context, sizeof(context)));
198
199     dynarray_delete_at(layer->rects, i);
200     dynarray_delete_at(layer->colors, i);
201     dynarray_delete_at(layer->ids, i);
202
203     return 0;
204 }
205
206 static int rect_layer_event_idle(RectLayer *layer,
207                                  const SDL_Event *event,
208                                  const Camera *camera,
209                                  UndoHistory *undo_history)
210 {
211     trace_assert(layer);
212     trace_assert(event);
213     trace_assert(camera);
214
215     switch (event->type) {
216     case SDL_MOUSEBUTTONDOWN: {
217         switch (event->button.button) {
218         case SDL_BUTTON_LEFT: {
219             Point position = camera_map_screen(
220                 camera,
221                 event->button.x,
222                 event->button.y);
223             int rect_at_position =
224                 rect_layer_rect_at(layer, position);
225
226             if (rect_at_position >= 0) {
227                 Rect *rects = dynarray_data(layer->rects);
228                 Color *colors = dynarray_data(layer->colors);
229                 layer->selection = rect_at_position;
230                 layer->state = RECT_LAYER_MOVE;
231                 layer->move_anchor =
232                     vec_sub(
233                         position,
234                         vec(
235                             rects[layer->selection].x,
236                             rects[layer->selection].y));
237                 layer->color_picker =
238                     create_color_picker_from_rgba(colors[rect_at_position]);
239                 layer->prev_color = colors[rect_at_position];
240                 layer->prev_rect = rects[rect_at_position];
241             } else if (layer->selection >= 0 && rect_contains_point(
242                            rect_layer_resize_anchor(
243                                layer,
244                                camera,
245                                (size_t)layer->selection),
246                            vec(
247                                (float) event->button.x,
248                                (float) event->button.y))) {
249                 layer->state = RECT_LAYER_RESIZE;
250             } else {
251                 layer->selection = rect_at_position;
252
253                 if (layer->selection < 0) {
254                     layer->state = RECT_LAYER_CREATE;
255                     layer->create_begin = position;
256                     layer->create_end = position;
257                 }
258             }
259         } break;
260         }
261     } break;
262
263     case SDL_KEYDOWN: {
264         switch (event->key.keysym.sym) {
265         case SDLK_DELETE: {
266             if (layer->selection >= 0) {
267                 rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
268                 layer->selection = -1;
269             }
270         } break;
271
272         case SDLK_F2: {
273             if (layer->selection >= 0) {
274                 const char *ids = dynarray_data(layer->ids);
275                 layer->state = RECT_LAYER_ID_RENAME;
276                 edit_field_replace(
277                     layer->id_edit_field,
278                     ids + layer->selection * RECT_LAYER_ID_MAX_SIZE);
279                 SDL_StartTextInput();
280             }
281         } break;
282         }
283     } break;
284     }
285
286     return 0;
287 }
288
289 static int rect_layer_event_create(RectLayer *layer,
290                                    const SDL_Event *event,
291                                    const Camera *camera,
292                                    UndoHistory *undo_history)
293 {
294     trace_assert(layer);
295     trace_assert(event);
296     trace_assert(camera);
297
298     switch (event->type) {
299     case SDL_MOUSEBUTTONUP: {
300         switch (event->button.button) {
301         case SDL_BUTTON_LEFT: {
302             const Rect real_rect =
303                 rect_from_points(
304                     layer->create_begin,
305                     layer->create_end);
306             const float area = real_rect.w * real_rect.h;
307
308             if (area >= CREATE_AREA_THRESHOLD) {
309                 rect_layer_add_rect(
310                     layer,
311                     real_rect,
312                     color_picker_rgba(&layer->color_picker),
313                     undo_history);
314             } else {
315                 log_info("The area is too small %f. Such small box won't be created.\n", area);
316             }
317             layer->state = RECT_LAYER_IDLE;
318         } break;
319         }
320     } break;
321
322     case SDL_MOUSEMOTION: {
323         layer->create_end = camera_map_screen(
324             camera,
325             event->motion.x,
326             event->motion.y);
327     } break;
328     }
329     return 0;
330 }
331
332 static int rect_layer_event_resize(RectLayer *layer,
333                                    const SDL_Event *event,
334                                    const Camera *camera,
335                                    UndoHistory *undo_history)
336 {
337     trace_assert(layer);
338     trace_assert(event);
339     trace_assert(camera);
340
341     Rect *rects = dynarray_data(layer->rects);
342
343     switch (event->type) {
344     case SDL_MOUSEMOTION: {
345         trace_assert(layer->selection >= 0);
346         rects[layer->selection] = rect_from_points(
347             vec(rects[layer->selection].x, rects[layer->selection].y),
348             vec_sum(
349                 camera_map_screen(
350                     camera,
351                     event->button.x,
352                     event->button.y),
353                 vec(RECT_LAYER_SELECTION_THICCNESS * -0.5f,
354                     RECT_LAYER_SELECTION_THICCNESS * -0.5f)));
355     } break;
356
357     case SDL_MOUSEBUTTONUP: {
358         layer->state = RECT_LAYER_IDLE;
359
360         UndoContext context =
361             rect_layer_create_undo_context(layer, (size_t)layer->selection, UNDO_UPDATE);
362
363         undo_history_push(
364             undo_history,
365             create_action(
366                 layer,
367                 rect_layer_undo,
368                 &context, sizeof(context)));
369
370         layer->prev_rect = rects[layer->selection];
371     } break;
372     }
373
374     return 0;
375 }
376
377 static int rect_layer_event_move(RectLayer *layer,
378                                  const SDL_Event *event,
379                                  const Camera *camera,
380                                  UndoHistory *undo_history)
381 {
382     trace_assert(layer);
383     trace_assert(event);
384     trace_assert(camera);
385
386     Rect *rects = dynarray_data(layer->rects);
387
388     switch (event->type) {
389     case SDL_MOUSEMOTION: {
390         Point position = vec_sub(
391             camera_map_screen(
392                 camera,
393                 event->button.x,
394                 event->button.y),
395             layer->move_anchor);
396
397         trace_assert(layer->selection >= 0);
398
399         rects[layer->selection].x = position.x;
400         rects[layer->selection].y = position.y;
401     } break;
402
403     case SDL_MOUSEBUTTONUP: {
404         layer->state = RECT_LAYER_IDLE;
405
406         UndoContext context =
407             rect_layer_create_undo_context(layer, (size_t)layer->selection, UNDO_UPDATE);
408
409         undo_history_push(
410             undo_history,
411             create_action(
412                 layer,
413                 rect_layer_undo,
414                 &context, sizeof(context)));
415
416         layer->prev_rect = rects[layer->selection];
417     } break;
418     }
419     return 0;
420 }
421
422 static int rect_layer_event_id_rename(RectLayer *layer,
423                                       const SDL_Event *event,
424                                       const Camera *camera,
425                                       UndoHistory *undo_history)
426 {
427     trace_assert(layer);
428     trace_assert(event);
429     trace_assert(camera);
430
431     switch (event->type) {
432     case SDL_KEYDOWN: {
433         switch (event->key.keysym.sym) {
434         case SDLK_RETURN: {
435             char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
436
437             UndoContext undo_context =
438                 rect_layer_create_undo_context(layer, (size_t)layer->selection, UNDO_UPDATE);
439
440             undo_history_push(
441                 undo_history,
442                 create_action(
443                     layer,
444                     rect_layer_undo,
445                     &undo_context, sizeof(undo_context)));
446
447             memset(id, 0, RECT_LAYER_ID_MAX_SIZE);
448             memcpy(id, edit_field_as_text(layer->id_edit_field), RECT_LAYER_ID_MAX_SIZE - 1);
449             layer->state = RECT_LAYER_IDLE;
450             SDL_StopTextInput();
451         } break;
452
453         case SDLK_ESCAPE: {
454             layer->state = RECT_LAYER_IDLE;
455             SDL_StopTextInput();
456         } break;
457         }
458     } break;
459     }
460
461     return edit_field_event(layer->id_edit_field, event);
462 }
463
464 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
465 {
466     LayerPtr layer = {
467         .type = LAYER_RECT,
468         .ptr = rect_layer
469     };
470     return layer;
471 }
472
473 RectLayer *create_rect_layer(void)
474 {
475     Lt *lt = create_lt();
476
477     RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
478     if (layer == NULL) {
479         RETURN_LT(lt, NULL);
480     }
481     layer->lt = lt;
482
483     layer->ids = PUSH_LT(
484         lt,
485         create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
486         destroy_dynarray);
487     if (layer->ids == NULL) {
488         RETURN_LT(lt, NULL);
489     }
490
491     layer->rects = PUSH_LT(
492         lt,
493         create_dynarray(sizeof(Rect)),
494         destroy_dynarray);
495     if (layer->rects == NULL) {
496         RETURN_LT(lt, NULL);
497     }
498
499     layer->colors = PUSH_LT(
500         lt,
501         create_dynarray(sizeof(Color)),
502         destroy_dynarray);
503     if (layer->colors == NULL) {
504         RETURN_LT(lt, NULL);
505     }
506
507     layer->id_edit_field = PUSH_LT(
508         lt,
509         create_edit_field(
510             vec(3.0f, 3.0f),
511             COLOR_BLACK),
512         destroy_edit_field);
513     if (layer->id_edit_field == NULL) {
514         RETURN_LT(lt, NULL);
515     }
516
517     Color init_color = rgba(1.0f, 0.0f, 0.0f, 1.0f);
518     layer->color_picker = create_color_picker_from_rgba(init_color);
519     layer->prev_color = init_color;
520     layer->selection = -1;
521
522     return layer;
523 }
524
525 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
526 {
527     trace_assert(line_stream);
528
529     RectLayer *layer = create_rect_layer();
530     if (layer == NULL) {
531         return NULL;
532     }
533
534     const char *line = line_stream_next(line_stream);
535     if (line == NULL) {
536         RETURN_LT(layer->lt, NULL);
537     }
538
539     size_t count = 0;
540     if (sscanf(line, "%zu", &count) < 0) {
541         RETURN_LT(layer->lt, NULL);
542     }
543
544     for (size_t i = 0; i < count; ++i) {
545         line = line_stream_next(line_stream);
546         if (line == NULL) {
547             RETURN_LT(layer->lt, NULL);
548         }
549
550         char hex[7];
551         Rect rect;
552         char id[RECT_LAYER_ID_MAX_SIZE];
553
554         if (sscanf(line,
555                    "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
556                    id,
557                    &rect.x, &rect.y,
558                    &rect.w, &rect.h,
559                    hex) < 0) {
560             RETURN_LT(layer->lt, NULL);
561         }
562
563         Color color = hexstr(hex);
564
565         dynarray_push(layer->rects, &rect);
566         dynarray_push(layer->ids, id);
567         dynarray_push(layer->colors, &color);
568     }
569
570     return layer;
571 }
572
573 void destroy_rect_layer(RectLayer *layer)
574 {
575     trace_assert(layer);
576     RETURN_LT0(layer->lt);
577 }
578
579 int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
580 {
581     trace_assert(layer);
582     trace_assert(camera);
583
584     const size_t n = dynarray_count(layer->rects);
585     Rect *rects = dynarray_data(layer->rects);
586     Color *colors = dynarray_data(layer->colors);
587     const char *ids = dynarray_data(layer->ids);
588
589     // The Rectangles
590     for (size_t i = 0; i < n; ++i) {
591         if (camera_fill_rect(
592                 camera,
593                 rects[i],
594                 color_scale(
595                     colors[i],
596                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
597             return -1;
598         }
599     }
600
601     // Proto Rectangle
602     const Color color = color_picker_rgba(&layer->color_picker);
603     if (layer->state == RECT_LAYER_CREATE) {
604         if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
605             return -1;
606         }
607     }
608
609     // ID renaming Edit Field
610     if (layer->state == RECT_LAYER_ID_RENAME) {
611         if (edit_field_render_screen(layer->id_edit_field, camera, vec(400.0f, 400.0f)) < 0) {
612             return -1;
613         }
614     }
615
616     // Selection Overlay
617     if (active && layer->selection >= 0) {
618         const Rect overlay_rect =
619             rect_scale(
620                 camera_rect(camera, rects[layer->selection]),
621                 RECT_LAYER_SELECTION_THICCNESS * 0.5f);
622         const Color overlay_color = color_invert(colors[layer->selection]);
623
624         // Main Rectangle
625         if (camera_fill_rect(
626                 camera,
627                 rects[layer->selection],
628                 color_scale(
629                     colors[layer->selection],
630                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
631             return -1;
632         }
633
634         if (camera_draw_thicc_rect_screen(
635                 camera,
636                 overlay_rect,
637                 overlay_color,
638                 RECT_LAYER_SELECTION_THICCNESS) < 0) {
639             return -1;
640         }
641
642         // Rectangle Id
643         if (camera_render_text(
644                 camera,
645                 ids + layer->selection * RECT_LAYER_ID_MAX_SIZE,
646                 vec(3.0f, 3.0f),
647                 color_invert(colors[layer->selection]),
648                 rect_position(rects[layer->selection])) < 0) {
649             return -1;
650         }
651
652         // Resize Anchor
653         if (camera_fill_rect_screen(
654                 camera,
655                 rect_layer_resize_anchor(layer, camera, (size_t) layer->selection),
656                 overlay_color) < 0) {
657             return -1;
658         }
659     }
660
661     if (active && color_picker_render(&layer->color_picker, camera) < 0) {
662         return -1;
663     }
664
665     return 0;
666 }
667
668 int rect_layer_event(RectLayer *layer,
669                      const SDL_Event *event,
670                      const Camera *camera,
671                      UndoHistory *undo_history)
672 {
673     trace_assert(layer);
674     trace_assert(event);
675     trace_assert(undo_history);
676
677     int color_changed = 0;
678     if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
679         return -1;
680     }
681
682     if (color_changed) {
683         if (layer->selection >= 0) {
684             Color *colors = dynarray_data(layer->colors);
685             colors[layer->selection] = color_picker_rgba(&layer->color_picker);
686
687             if (!color_picker_drag(&layer->color_picker)) {
688                 UndoContext context =
689                     rect_layer_create_undo_context(layer, (size_t)layer->selection, UNDO_UPDATE);
690
691                 undo_history_push(
692                     undo_history,
693                     create_action(
694                         layer,
695                         rect_layer_undo,
696                         &context,
697                         sizeof(context)));
698                 layer->prev_color = colors[layer->selection];
699             }
700         }
701
702         return 0;
703     }
704
705     switch (layer->state) {
706     case RECT_LAYER_IDLE:
707         return rect_layer_event_idle(layer, event, camera, undo_history);
708
709     case RECT_LAYER_CREATE:
710         return rect_layer_event_create(layer, event, camera, undo_history);
711
712     case RECT_LAYER_RESIZE:
713         return rect_layer_event_resize(layer, event, camera, undo_history);
714
715     case RECT_LAYER_MOVE:
716         return rect_layer_event_move(layer, event, camera, undo_history);
717
718     case RECT_LAYER_ID_RENAME:
719         return rect_layer_event_id_rename(layer, event, camera, undo_history);
720     }
721
722     return 0;
723 }
724
725 size_t rect_layer_count(const RectLayer *layer)
726 {
727     return dynarray_count(layer->rects);
728 }
729
730 const Rect *rect_layer_rects(const RectLayer *layer)
731 {
732     return dynarray_data(layer->rects);
733 }
734
735 const Color *rect_layer_colors(const RectLayer *layer)
736 {
737     return dynarray_data(layer->colors);
738 }
739
740 const char *rect_layer_ids(const RectLayer *layer)
741 {
742     return dynarray_data(layer->ids);
743 }
744
745 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
746 {
747     trace_assert(layer);
748     trace_assert(filedump);
749
750     size_t n = dynarray_count(layer->ids);
751     char *ids = dynarray_data(layer->ids);
752     Rect *rects = dynarray_data(layer->rects);
753     Color *colors = dynarray_data(layer->colors);
754
755     fprintf(filedump, "%zd\n", n);
756     for (size_t i = 0; i < n; ++i) {
757         fprintf(filedump, "%s %f %f %f %f ",
758                 ids + RECT_LAYER_ID_MAX_SIZE * i,
759                 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
760         color_hex_to_stream(colors[i], filedump);
761         fprintf(filedump, "\n");
762     }
763
764     return 0;
765 }