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