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