]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/label_layer.c
7692df16ca2192601781cc2ef6aed78151ec77cc
[nothing.git] / src / game / level / level_editor / label_layer.c
1 #include <stdio.h>
2
3 #include <SDL.h>
4
5 #include "system/line_stream.h"
6 #include "system/stacktrace.h"
7 #include "system/nth_alloc.h"
8 #include "system/lt.h"
9 #include "system/str.h"
10 #include "system/log.h"
11 #include "math/point.h"
12 #include "label_layer.h"
13 #include "dynarray.h"
14 #include "color.h"
15 #include "game/camera.h"
16 #include "color_picker.h"
17 #include "ui/edit_field.h"
18
19 #define LABEL_LAYER_SELECTION_THICCNESS 5.0f
20
21 // TODO(#999): LabelLayer does not support UndoHistory
22
23 typedef enum {
24     LABEL_LAYER_IDLE = 0,
25     LABEL_LAYER_MOVE,
26     LABEL_LAYER_EDIT_TEXT,
27     LABEL_LAYER_EDIT_ID,
28     LABEL_LAYER_RECOLOR
29 } LabelLayerState;
30
31 struct LabelLayer {
32     Lt *lt;
33     LabelLayerState state;
34     Dynarray *ids;
35     Dynarray *positions;
36     Dynarray *colors;
37     Dynarray *texts;
38     int selected;
39     ColorPicker color_picker;
40     Point move_anchor;
41     Edit_field *edit_field;
42     Point inter_position;
43     Color inter_color;
44 };
45
46 typedef enum {
47     UNDO_ADD,
48     UNDO_DELETE,
49     UNDO_UPDATE
50 } UndoType;
51
52 typedef struct {
53     UndoType type;
54     char id[LABEL_LAYER_ID_MAX_SIZE];
55     Point position;
56     Color color;
57     char text[LABEL_LAYER_TEXT_MAX_SIZE];
58     size_t index;
59 } UndoContext;
60
61 static
62 UndoContext create_undo_context(LabelLayer *label_layer, UndoType type)
63 {
64     UndoContext undo_context;
65
66     size_t index = type == UNDO_ADD
67         ? dynarray_count(label_layer->positions) - 1
68         : (size_t)label_layer->selected;
69
70     undo_context.type = type;
71     dynarray_copy_to(label_layer->ids, &undo_context.id, index);
72     dynarray_copy_to(label_layer->positions, &undo_context.position, index);
73     dynarray_copy_to(label_layer->colors, &undo_context.color, index);
74     dynarray_copy_to(label_layer->texts, &undo_context.text, index);
75     undo_context.index = index;
76
77     return undo_context;
78 }
79
80 static
81 void label_layer_undo(void *layer, void *context, size_t context_size)
82 {
83     trace_assert(layer);
84     trace_assert(context);
85     trace_assert(sizeof(UndoContext) == context_size);
86
87     LabelLayer *label_layer = layer;
88     UndoContext *undo_context = context;
89
90     switch (undo_context->type) {
91     case UNDO_ADD: {
92         dynarray_delete_at(label_layer->ids, undo_context->index);
93         dynarray_delete_at(label_layer->positions, undo_context->index);
94         dynarray_delete_at(label_layer->colors, undo_context->index);
95         dynarray_delete_at(label_layer->texts, undo_context->index);
96     } break;
97
98     case UNDO_DELETE: {
99         dynarray_insert_before(label_layer->ids, undo_context->index, &undo_context->id);
100         dynarray_insert_before(label_layer->positions, undo_context->index, &undo_context->position);
101         dynarray_insert_before(label_layer->colors, undo_context->index, &undo_context->color);
102         dynarray_insert_before(label_layer->texts, undo_context->index, &undo_context->text);
103     } break;
104
105     case UNDO_UPDATE: {
106         dynarray_replace_at(label_layer->ids, undo_context->index, &undo_context->id);
107         dynarray_replace_at(label_layer->positions, undo_context->index, &undo_context->position);
108         dynarray_replace_at(label_layer->colors, undo_context->index, &undo_context->color);
109         dynarray_replace_at(label_layer->texts, undo_context->index, &undo_context->text);
110     } break;
111     }
112 }
113
114 #define UNDO_PUSH(LAYER, HISTORY, UNDO_TYPE)                            \
115     do {                                                                \
116         UndoContext context = create_undo_context(LAYER, UNDO_TYPE);    \
117         undo_history_push(                                              \
118             HISTORY,                                                    \
119             LAYER,                                                      \
120             label_layer_undo,                                           \
121             &context,                                                   \
122             sizeof(context));                                           \
123     } while(0)
124
125
126 LayerPtr label_layer_as_layer(LabelLayer *label_layer)
127 {
128     LayerPtr layer = {
129         .ptr = label_layer,
130         .type = LAYER_LABEL
131     };
132     return layer;
133 }
134
135 LabelLayer *create_label_layer(void)
136 {
137     Lt *lt = create_lt();
138
139     LabelLayer *label_layer = PUSH_LT(
140         lt, nth_calloc(1, sizeof(LabelLayer)), free);
141     if (label_layer == NULL) {
142         RETURN_LT(lt, NULL);
143     }
144     label_layer->lt = lt;
145
146     label_layer->ids = PUSH_LT(
147         lt,
148         create_dynarray(sizeof(char) * LABEL_LAYER_ID_MAX_SIZE),
149         destroy_dynarray);
150     if (label_layer->ids == NULL) {
151         RETURN_LT(lt, NULL);
152     }
153
154     label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray);
155     if (label_layer->positions == NULL) {
156         RETURN_LT(lt, NULL);
157     }
158
159     label_layer->colors = PUSH_LT(lt, create_dynarray(sizeof(Color)), destroy_dynarray);
160     if (label_layer->colors == NULL) {
161         RETURN_LT(lt, NULL);
162     }
163
164     label_layer->texts = PUSH_LT(
165         lt,
166         create_dynarray(sizeof(char) * LABEL_LAYER_TEXT_MAX_SIZE),
167         destroy_dynarray);
168     if (label_layer->texts == NULL) {
169         RETURN_LT(lt, NULL);
170     }
171
172     label_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
173     label_layer->selected = -1;
174
175     label_layer->edit_field = PUSH_LT(
176         lt,
177         create_edit_field(LABELS_SIZE, COLOR_RED),
178         destroy_edit_field);
179     if (label_layer->edit_field == NULL) {
180         RETURN_LT(lt, NULL);
181     }
182
183     return label_layer;
184 }
185
186 LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
187 {
188     trace_assert(line_stream);
189     LabelLayer *label_layer = create_label_layer();
190
191     if (label_layer == NULL) {
192         RETURN_LT(label_layer->lt, NULL);
193     }
194
195     const char *line = line_stream_next(line_stream);
196     if (line == NULL) {
197         log_fail("Could not read amount of labels\n");
198         RETURN_LT(label_layer->lt, NULL);
199     }
200
201     size_t n = 0;
202     if (sscanf(line, "%zu", &n) == EOF) {
203         log_fail("Could not parse amount of labels\n");
204         RETURN_LT(label_layer->lt, NULL);
205     }
206
207     for (size_t i = 0; i < n; ++i) {
208         char hex[7];
209         char id[LABEL_LAYER_ID_MAX_SIZE];
210         Point position;
211
212         line = line_stream_next(line_stream);
213         if (line == NULL) {
214             log_fail("Could not read label meta info\n");
215             RETURN_LT(label_layer->lt, NULL);
216         }
217
218         if (sscanf(
219                 line,
220                 "%"STRINGIFY(LABEL_LAYER_ID_MAX_SIZE)"s%f%f%6s\n",
221                 id, &position.x, &position.y, hex) == EOF) {
222             log_fail("Could not parse label meta info\n");
223             RETURN_LT(label_layer->lt, NULL);
224         }
225
226         Color color = hexstr(hex);
227
228         dynarray_push(label_layer->ids, id);
229         dynarray_push(label_layer->positions, &position);
230         dynarray_push(label_layer->colors, &color);
231
232         line = line_stream_next(line_stream);
233         if (line == NULL) {
234             log_fail("Could not read label text\n");
235         }
236
237         char label_text[LABEL_LAYER_TEXT_MAX_SIZE] = {0};
238         memcpy(label_text, line, LABEL_LAYER_TEXT_MAX_SIZE - 1);
239         trim_endline(label_text);
240         dynarray_push(label_layer->texts, &label_text);
241     }
242
243     return label_layer;
244 }
245
246 void destroy_label_layer(LabelLayer *label_layer)
247 {
248     trace_assert(label_layer);
249     destroy_lt(label_layer->lt);
250 }
251
252 int label_layer_render(const LabelLayer *label_layer,
253                        Camera *camera,
254                        int active)
255 {
256     trace_assert(label_layer);
257     trace_assert(camera);
258
259     if (active && color_picker_render(&label_layer->color_picker, camera) < 0) {
260         return -1;
261     }
262
263     size_t n = dynarray_count(label_layer->ids);
264     char *ids = dynarray_data(label_layer->ids);
265     Point *positions = dynarray_data(label_layer->positions);
266     Color *colors = dynarray_data(label_layer->colors);
267     char *texts = dynarray_data(label_layer->texts);
268
269     /* TODO(#891): LabelLayer doesn't show the final position of Label after the animation */
270     for (size_t i = 0; i < n; ++i) {
271         const Color color = label_layer->state == LABEL_LAYER_RECOLOR && label_layer->selected == (int) i
272             ? label_layer->inter_color
273             : colors[i];
274
275         const Point position =
276             label_layer->state == LABEL_LAYER_MOVE && label_layer->selected == (int) i
277             ? label_layer->inter_position
278             : positions[i];
279
280         // Label Text
281         if (label_layer->state == LABEL_LAYER_EDIT_TEXT && label_layer->selected == (int) i) {
282             if (edit_field_render_world(
283                     label_layer->edit_field,
284                     camera,
285                     position) < 0) {
286                 return -1;
287             }
288         } else {
289             if (camera_render_text(
290                     camera,
291                     texts + i * LABEL_LAYER_TEXT_MAX_SIZE,
292                     LABELS_SIZE,
293                     color_scale(
294                         color,
295                         rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
296                     position) < 0) {
297                 return -1;
298             }
299         }
300
301         // Label ID
302         if (label_layer->state == LABEL_LAYER_EDIT_ID && label_layer->selected == (int)i) {
303             if (edit_field_render_world(
304                     label_layer->edit_field,
305                     camera,
306                     vec_sub(
307                         position,
308                         vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
309                 return -1;
310             }
311         } else {
312             if (camera_render_text(
313                     camera,
314                     ids + i * LABEL_LAYER_ID_MAX_SIZE,
315                     vec(1.0f, 1.0f),
316                     color_scale(
317                         color_invert(color),
318                         rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
319                     vec_sub(position, vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
320                 return -1;
321             }
322         }
323
324         // Label Selection
325         if (label_layer->selected == (int) i) {
326             Rect selection =
327                 rect_scale(
328                     camera_rect(
329                         camera,
330                         rect_boundary2(
331                             sprite_font_boundary_box(
332                                 camera_font(camera),
333                                 position,
334                                 LABELS_SIZE,
335                                 texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE),
336                             sprite_font_boundary_box(
337                                 camera_font(camera),
338                                 vec_sub(
339                                     position,
340                                     vec(0.0f, FONT_CHAR_HEIGHT)),
341                                 vec(1.0f, 1.0f),
342                                 ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE))),
343                     LABEL_LAYER_SELECTION_THICCNESS * 0.5f);
344
345
346             if (camera_draw_thicc_rect_screen(
347                     camera,
348                     selection,
349                     color,
350                     LABEL_LAYER_SELECTION_THICCNESS) < 0) {
351                 return -1;
352             }
353         }
354     }
355
356
357     return 0;
358 }
359
360 static
361 int label_layer_element_at(LabelLayer *label_layer,
362                            const Sprite_font *font,
363                            Point position)
364 {
365     trace_assert(label_layer);
366
367     const size_t n = dynarray_count(label_layer->texts);
368     char *ids = dynarray_data(label_layer->ids);
369     char *texts = dynarray_data(label_layer->texts);
370     Point *positions = dynarray_data(label_layer->positions);
371
372     for (size_t i = 0; i < n; ++i) {
373         Rect boundary = rect_boundary2(
374             sprite_font_boundary_box(
375                 font,
376                 positions[i],
377                 LABELS_SIZE,
378                 texts + i * LABEL_LAYER_TEXT_MAX_SIZE),
379             sprite_font_boundary_box(
380                 font,
381                 vec_sub(
382                     positions[i],
383                     vec(0.0f, FONT_CHAR_HEIGHT)),
384                 vec(1.0f, 1.0f),
385                 ids + i * LABEL_LAYER_ID_MAX_SIZE));
386
387         if (rect_contains_point(boundary, position)) {
388             return (int) i;
389         }
390     }
391
392     return -1;
393 }
394
395 static
396 void label_layer_delete_selected_label(LabelLayer *label_layer,
397                                        UndoHistory *undo_history)
398 {
399     trace_assert(label_layer);
400     trace_assert(label_layer->selected >= 0);
401
402     UNDO_PUSH(label_layer, undo_history, UNDO_DELETE);
403
404     dynarray_delete_at(label_layer->ids, (size_t)label_layer->selected);
405     dynarray_delete_at(label_layer->positions, (size_t)label_layer->selected);
406     dynarray_delete_at(label_layer->colors, (size_t)label_layer->selected);
407     dynarray_delete_at(label_layer->texts, (size_t)label_layer->selected);
408 }
409
410 static
411 int label_layer_add_label(LabelLayer *label_layer,
412                           Point position,
413                           Color color,
414                           UndoHistory *undo_history)
415 {
416     trace_assert(label_layer);
417
418     // TODO(#982): id generation code is duplicated in label_layer, point_layer and rect_layer
419     char id[LABEL_LAYER_ID_MAX_SIZE];
420     for (size_t i = 0; i < LABEL_LAYER_ID_MAX_SIZE - 1; ++i) {
421         id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
422     }
423     id[LABEL_LAYER_ID_MAX_SIZE - 1] = '\0';
424
425     size_t n = dynarray_count(label_layer->ids);
426
427     dynarray_push(label_layer->ids, id);
428     dynarray_push(label_layer->positions, &position);
429     dynarray_push(label_layer->colors, &color);
430     dynarray_push_empty(label_layer->texts);
431
432     UNDO_PUSH(label_layer, undo_history, UNDO_ADD);
433
434     return (int) n;
435 }
436
437 static
438 int label_layer_idle_event(LabelLayer *label_layer,
439                            const SDL_Event *event,
440                            const Camera *camera,
441                            UndoHistory *undo_history)
442 {
443     trace_assert(label_layer);
444     trace_assert(event);
445     trace_assert(camera);
446
447     int changed = 0;
448
449     if (color_picker_event(
450             &label_layer->color_picker,
451             event,
452             camera,
453             &changed) < 0) {
454         return -1;
455     }
456
457     if (changed) {
458         label_layer->state = LABEL_LAYER_RECOLOR;
459         label_layer->inter_color = color_picker_rgba(&label_layer->color_picker);
460         return 0;
461     }
462
463     Color *colors = dynarray_data(label_layer->colors);
464     Point *positions = dynarray_data(label_layer->positions);
465     char *ids = dynarray_data(label_layer->ids);
466     char *texts = dynarray_data(label_layer->texts);
467
468     switch (event->type) {
469     case SDL_MOUSEBUTTONDOWN: {
470         switch (event->button.button) {
471         case SDL_BUTTON_LEFT: {
472             const Point position = camera_map_screen(
473                 camera,
474                 event->button.x,
475                 event->button.y);
476
477             const int element = label_layer_element_at(
478                 label_layer,
479                 camera_font(camera),
480                 position);
481
482             if (element >= 0) {
483                 label_layer->move_anchor = vec_sub(position, positions[element]);
484                 label_layer->selected = element;
485                 label_layer->state = LABEL_LAYER_MOVE;
486                 label_layer->inter_position = positions[element];
487
488                 label_layer->color_picker =
489                     create_color_picker_from_rgba(colors[element]);
490             } else {
491                 label_layer->selected = label_layer_add_label(
492                     label_layer,
493                     position,
494                     color_picker_rgba(
495                         &label_layer->color_picker),
496                     undo_history);
497                 label_layer->state = LABEL_LAYER_EDIT_TEXT;
498                 edit_field_replace(
499                     label_layer->edit_field,
500                     texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
501                 edit_field_restyle(
502                     label_layer->edit_field,
503                     LABELS_SIZE,
504                     colors[label_layer->selected]);
505                 SDL_StartTextInput();
506             }
507         } break;
508         }
509     } break;
510
511     case SDL_KEYDOWN: {
512         switch (event->key.keysym.sym) {
513         case SDLK_F2: {
514             if (label_layer->selected >= 0) {
515                 label_layer->state = LABEL_LAYER_EDIT_TEXT;
516                 edit_field_replace(
517                     label_layer->edit_field,
518                     texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
519                 edit_field_restyle(
520                     label_layer->edit_field,
521                     LABELS_SIZE,
522                     colors[label_layer->selected]);
523                 SDL_StartTextInput();
524             }
525         } break;
526
527         case SDLK_F3: {
528             if (label_layer->selected >= 0) {
529                 label_layer->state = LABEL_LAYER_EDIT_ID;
530                 edit_field_replace(
531                     label_layer->edit_field,
532                     ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE);
533                 edit_field_restyle(
534                     label_layer->edit_field,
535                     vec(1.0f, 1.0f),
536                     color_invert(colors[label_layer->selected]));
537                 SDL_StartTextInput();
538             }
539         } break;
540
541         case SDLK_DELETE: {
542             if (label_layer->selected >= 0) {
543                 label_layer_delete_selected_label(
544                     label_layer,
545                     undo_history);
546                 label_layer->selected = -1;
547             }
548         } break;
549         }
550     } break;
551     }
552
553     return 0;
554 }
555
556 static
557 int label_layer_move_event(LabelLayer *label_layer,
558                            const SDL_Event *event,
559                            const Camera *camera,
560                            UndoHistory *undo_history)
561 {
562     trace_assert(label_layer);
563     trace_assert(event);
564     trace_assert(camera);
565     trace_assert(label_layer->selected >= 0);
566
567     switch (event->type) {
568     case SDL_MOUSEMOTION: {
569         label_layer->inter_position = vec_sub(
570             camera_map_screen(
571                 camera,
572                 event->motion.x,
573                 event->motion.y),
574             label_layer->move_anchor);
575     } break;
576
577     case SDL_MOUSEBUTTONUP: {
578         switch (event->button.button) {
579         case SDL_BUTTON_LEFT: {
580             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
581
582             dynarray_replace_at(
583                 label_layer->positions,
584                 (size_t)label_layer->selected,
585                 &label_layer->inter_position);
586             label_layer->state = LABEL_LAYER_IDLE;
587         } break;
588         }
589     } break;
590     }
591
592     return 0;
593 }
594
595 static
596 int label_layer_edit_text_event(LabelLayer *label_layer,
597                                 const SDL_Event *event,
598                                 const Camera *camera,
599                                 UndoHistory *undo_history)
600 {
601     trace_assert(label_layer);
602     trace_assert(event);
603     trace_assert(camera);
604     trace_assert(label_layer->selected >= 0);
605
606     switch (event->type) {
607     case SDL_KEYDOWN: {
608         switch (event->key.keysym.sym) {
609         case SDLK_RETURN: {
610             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
611
612             char *text =
613                 (char*)dynarray_data(label_layer->texts) + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE;
614             memset(text, 0, LABEL_LAYER_TEXT_MAX_SIZE);
615             memcpy(text, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_TEXT_MAX_SIZE - 1);
616             label_layer->state = LABEL_LAYER_IDLE;
617             SDL_StopTextInput();
618             return 0;
619         } break;
620
621         case SDLK_ESCAPE: {
622             label_layer->state = LABEL_LAYER_IDLE;
623             SDL_StopTextInput();
624             return 0;
625         } break;
626         }
627     } break;
628     }
629
630     return edit_field_event(label_layer->edit_field, event);
631 }
632
633 static
634 int label_layer_edit_id_event(LabelLayer *label_layer,
635                               const SDL_Event *event,
636                               const Camera *camera,
637                               UndoHistory *undo_history)
638 {
639     trace_assert(label_layer);
640     trace_assert(event);
641     trace_assert(camera);
642     trace_assert(undo_history);
643     trace_assert(label_layer->selected >= 0);
644
645     switch (event->type) {
646     case SDL_KEYDOWN: {
647         switch (event->key.keysym.sym) {
648         case SDLK_RETURN: {
649             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
650
651             char *id =
652                 (char*)dynarray_data(label_layer->ids) + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE;
653             memset(id, 0, LABEL_LAYER_ID_MAX_SIZE);
654             memcpy(id, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_ID_MAX_SIZE - 1);
655             label_layer->state = LABEL_LAYER_IDLE;
656             SDL_StopTextInput();
657             return 0;
658         } break;
659
660         case SDLK_ESCAPE: {
661             label_layer->state = LABEL_LAYER_IDLE;
662             SDL_StopTextInput();
663             return 0;
664         } break;
665         }
666     } break;
667     }
668
669     return edit_field_event(label_layer->edit_field, event);
670 }
671
672 static
673 int label_layer_recolor_event(LabelLayer *label_layer,
674                               const SDL_Event *event,
675                               const Camera *camera,
676                               UndoHistory *undo_history)
677 {
678     trace_assert(label_layer);
679     trace_assert(event);
680     trace_assert(camera);
681     trace_assert(undo_history);
682     trace_assert(label_layer->selected >= 0);
683
684     int changed = 0;
685
686     if (color_picker_event(
687             &label_layer->color_picker,
688             event,
689             camera,
690             &changed) < 0) {
691         return -1;
692     }
693
694     if (changed) {
695         label_layer->inter_color =
696             color_picker_rgba(&label_layer->color_picker);
697
698         if (!color_picker_drag(&label_layer->color_picker)) {
699             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
700
701             dynarray_replace_at(
702                 label_layer->colors,
703                 (size_t) label_layer->selected,
704                 &label_layer->inter_color);
705             label_layer->state = LABEL_LAYER_IDLE;
706         }
707     }
708
709     return 0;
710 }
711
712 int label_layer_event(LabelLayer *label_layer,
713                       const SDL_Event *event,
714                       const Camera *camera,
715                       UndoHistory *undo_history)
716 {
717     trace_assert(label_layer);
718     trace_assert(event);
719     trace_assert(camera);
720     trace_assert(undo_history);
721
722     switch (label_layer->state) {
723     case LABEL_LAYER_IDLE:
724         return label_layer_idle_event(label_layer, event, camera, undo_history);
725
726     case LABEL_LAYER_MOVE:
727         return label_layer_move_event(label_layer, event, camera, undo_history);
728
729     case LABEL_LAYER_EDIT_TEXT:
730         return label_layer_edit_text_event(label_layer, event, camera, undo_history);
731
732     case LABEL_LAYER_EDIT_ID:
733         return label_layer_edit_id_event(label_layer, event, camera, undo_history);
734
735     case LABEL_LAYER_RECOLOR:
736         return label_layer_recolor_event(label_layer, event, camera, undo_history);
737     }
738
739     return 0;
740 }
741
742 size_t label_layer_count(const LabelLayer *label_layer)
743 {
744     return dynarray_count(label_layer->ids);
745 }
746
747 char *label_layer_ids(const LabelLayer *label_layer)
748 {
749     return dynarray_data(label_layer->ids);
750 }
751
752 Point *label_layer_positions(const LabelLayer *label_layer)
753 {
754     return dynarray_data(label_layer->positions);
755 }
756
757 Color *label_layer_colors(const LabelLayer *label_layer)
758 {
759     return dynarray_data(label_layer->colors);
760 }
761
762 char *labels_layer_texts(const LabelLayer *label_layer)
763 {
764     return dynarray_data(label_layer->texts);
765 }
766
767 int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump)
768 {
769     trace_assert(label_layer);
770     trace_assert(filedump);
771
772     size_t n = dynarray_count(label_layer->ids);
773     char *ids = dynarray_data(label_layer->ids);
774     Point *positions = dynarray_data(label_layer->positions);
775     Color *colors = dynarray_data(label_layer->colors);
776     char *texts = dynarray_data(label_layer->texts);
777
778     fprintf(filedump, "%zd\n", n);
779     for (size_t i = 0; i < n; ++i) {
780         fprintf(filedump, "%s %f %f ",
781                 ids + LABEL_LAYER_ID_MAX_SIZE * i,
782                 positions[i].x, positions[i].y);
783         color_hex_to_stream(colors[i], filedump);
784         fprintf(filedump, "\n%s\n", texts + i * LABEL_LAYER_TEXT_MAX_SIZE);
785     }
786
787     return 0;
788 }