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