]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/label_layer.c
Merge pull request #1046 from tsoding/704
[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     LabelLayer *layer;
55     char id[LABEL_LAYER_ID_MAX_SIZE];
56     Point position;
57     Color color;
58     char text[LABEL_LAYER_TEXT_MAX_SIZE];
59     size_t index;
60 } UndoContext;
61
62 static
63 UndoContext create_undo_context(LabelLayer *label_layer, UndoType type)
64 {
65     UndoContext undo_context;
66
67     size_t index = type == UNDO_ADD
68         ? dynarray_count(label_layer->positions) - 1
69         : (size_t)label_layer->selected;
70
71     undo_context.type = type;
72     undo_context.layer = label_layer;
73     dynarray_copy_to(label_layer->ids, &undo_context.id, index);
74     dynarray_copy_to(label_layer->positions, &undo_context.position, index);
75     dynarray_copy_to(label_layer->colors, &undo_context.color, index);
76     dynarray_copy_to(label_layer->texts, &undo_context.text, index);
77     undo_context.index = index;
78
79     return undo_context;
80 }
81
82 static
83 void label_layer_undo(void *context, size_t context_size)
84 {
85     trace_assert(context);
86     trace_assert(sizeof(UndoContext) == context_size);
87
88     UndoContext *undo_context = context;
89     LabelLayer *label_layer = undo_context->layer;
90
91     switch (undo_context->type) {
92     case UNDO_ADD: {
93         dynarray_delete_at(label_layer->ids, undo_context->index);
94         dynarray_delete_at(label_layer->positions, undo_context->index);
95         dynarray_delete_at(label_layer->colors, undo_context->index);
96         dynarray_delete_at(label_layer->texts, undo_context->index);
97     } break;
98
99     case UNDO_DELETE: {
100         dynarray_insert_before(label_layer->ids, undo_context->index, &undo_context->id);
101         dynarray_insert_before(label_layer->positions, undo_context->index, &undo_context->position);
102         dynarray_insert_before(label_layer->colors, undo_context->index, &undo_context->color);
103         dynarray_insert_before(label_layer->texts, undo_context->index, &undo_context->text);
104     } break;
105
106     case UNDO_UPDATE: {
107         dynarray_replace_at(label_layer->ids, undo_context->index, &undo_context->id);
108         dynarray_replace_at(label_layer->positions, undo_context->index, &undo_context->position);
109         dynarray_replace_at(label_layer->colors, undo_context->index, &undo_context->color);
110         dynarray_replace_at(label_layer->texts, undo_context->index, &undo_context->text);
111     } break;
112     }
113 }
114
115 #define UNDO_PUSH(LAYER, HISTORY, UNDO_TYPE)                            \
116     do {                                                                \
117         UndoContext context = create_undo_context(LAYER, UNDO_TYPE);    \
118         undo_history_push(                                              \
119             HISTORY,                                                    \
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 (active && 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         if (label_layer->selected >= 0) {
459             label_layer->state = LABEL_LAYER_RECOLOR;
460             label_layer->inter_color = color_picker_rgba(&label_layer->color_picker);
461         }
462         return 0;
463     }
464
465     Color *colors = dynarray_data(label_layer->colors);
466     Point *positions = dynarray_data(label_layer->positions);
467     char *ids = dynarray_data(label_layer->ids);
468     char *texts = dynarray_data(label_layer->texts);
469
470     switch (event->type) {
471     case SDL_MOUSEBUTTONDOWN: {
472         switch (event->button.button) {
473         case SDL_BUTTON_LEFT: {
474             const Point position = camera_map_screen(
475                 camera,
476                 event->button.x,
477                 event->button.y);
478
479             const int element = label_layer_element_at(
480                 label_layer,
481                 camera_font(camera),
482                 position);
483
484             if (element >= 0) {
485                 label_layer->move_anchor = vec_sub(position, positions[element]);
486                 label_layer->selected = element;
487                 label_layer->state = LABEL_LAYER_MOVE;
488                 label_layer->inter_position = positions[element];
489
490                 label_layer->color_picker =
491                     create_color_picker_from_rgba(colors[element]);
492             } else {
493                 label_layer->selected = label_layer_add_label(
494                     label_layer,
495                     position,
496                     color_picker_rgba(
497                         &label_layer->color_picker),
498                     undo_history);
499                 label_layer->state = LABEL_LAYER_EDIT_TEXT;
500                 edit_field_replace(
501                     label_layer->edit_field,
502                     texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
503                 edit_field_restyle(
504                     label_layer->edit_field,
505                     LABELS_SIZE,
506                     colors[label_layer->selected]);
507                 SDL_StartTextInput();
508             }
509         } break;
510         }
511     } break;
512
513     case SDL_KEYDOWN: {
514         switch (event->key.keysym.sym) {
515         case SDLK_F2: {
516             if (label_layer->selected >= 0) {
517                 label_layer->state = LABEL_LAYER_EDIT_TEXT;
518                 edit_field_replace(
519                     label_layer->edit_field,
520                     texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
521                 edit_field_restyle(
522                     label_layer->edit_field,
523                     LABELS_SIZE,
524                     colors[label_layer->selected]);
525                 SDL_StartTextInput();
526             }
527         } break;
528
529         case SDLK_F3: {
530             if (label_layer->selected >= 0) {
531                 label_layer->state = LABEL_LAYER_EDIT_ID;
532                 edit_field_replace(
533                     label_layer->edit_field,
534                     ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE);
535                 edit_field_restyle(
536                     label_layer->edit_field,
537                     vec(1.0f, 1.0f),
538                     color_invert(colors[label_layer->selected]));
539                 SDL_StartTextInput();
540             }
541         } break;
542
543         case SDLK_DELETE: {
544             if (label_layer->selected >= 0) {
545                 label_layer_delete_selected_label(
546                     label_layer,
547                     undo_history);
548                 label_layer->selected = -1;
549             }
550         } break;
551         }
552     } break;
553     }
554
555     return 0;
556 }
557
558 static
559 int label_layer_move_event(LabelLayer *label_layer,
560                            const SDL_Event *event,
561                            const Camera *camera,
562                            UndoHistory *undo_history)
563 {
564     trace_assert(label_layer);
565     trace_assert(event);
566     trace_assert(camera);
567     trace_assert(label_layer->selected >= 0);
568
569     switch (event->type) {
570     case SDL_MOUSEMOTION: {
571         label_layer->inter_position = vec_sub(
572             camera_map_screen(
573                 camera,
574                 event->motion.x,
575                 event->motion.y),
576             label_layer->move_anchor);
577     } break;
578
579     case SDL_MOUSEBUTTONUP: {
580         switch (event->button.button) {
581         case SDL_BUTTON_LEFT: {
582             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
583
584             dynarray_replace_at(
585                 label_layer->positions,
586                 (size_t)label_layer->selected,
587                 &label_layer->inter_position);
588             label_layer->state = LABEL_LAYER_IDLE;
589         } break;
590         }
591     } break;
592     }
593
594     return 0;
595 }
596
597 static
598 int label_layer_edit_text_event(LabelLayer *label_layer,
599                                 const SDL_Event *event,
600                                 const Camera *camera,
601                                 UndoHistory *undo_history)
602 {
603     trace_assert(label_layer);
604     trace_assert(event);
605     trace_assert(camera);
606     trace_assert(label_layer->selected >= 0);
607
608     switch (event->type) {
609     case SDL_KEYDOWN: {
610         switch (event->key.keysym.sym) {
611         case SDLK_RETURN: {
612             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
613
614             char *text =
615                 (char*)dynarray_data(label_layer->texts) + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE;
616             memset(text, 0, LABEL_LAYER_TEXT_MAX_SIZE);
617             memcpy(text, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_TEXT_MAX_SIZE - 1);
618             label_layer->state = LABEL_LAYER_IDLE;
619             SDL_StopTextInput();
620             return 0;
621         } break;
622
623         case SDLK_ESCAPE: {
624             label_layer->state = LABEL_LAYER_IDLE;
625             SDL_StopTextInput();
626             return 0;
627         } break;
628         }
629     } break;
630     }
631
632     return edit_field_event(label_layer->edit_field, event);
633 }
634
635 static
636 int label_layer_edit_id_event(LabelLayer *label_layer,
637                               const SDL_Event *event,
638                               const Camera *camera,
639                               UndoHistory *undo_history)
640 {
641     trace_assert(label_layer);
642     trace_assert(event);
643     trace_assert(camera);
644     trace_assert(undo_history);
645     trace_assert(label_layer->selected >= 0);
646
647     switch (event->type) {
648     case SDL_KEYDOWN: {
649         switch (event->key.keysym.sym) {
650         case SDLK_RETURN: {
651             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
652
653             char *id =
654                 (char*)dynarray_data(label_layer->ids) + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE;
655             memset(id, 0, LABEL_LAYER_ID_MAX_SIZE);
656             memcpy(id, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_ID_MAX_SIZE - 1);
657             label_layer->state = LABEL_LAYER_IDLE;
658             SDL_StopTextInput();
659             return 0;
660         } break;
661
662         case SDLK_ESCAPE: {
663             label_layer->state = LABEL_LAYER_IDLE;
664             SDL_StopTextInput();
665             return 0;
666         } break;
667         }
668     } break;
669     }
670
671     return edit_field_event(label_layer->edit_field, event);
672 }
673
674 static
675 int label_layer_recolor_event(LabelLayer *label_layer,
676                               const SDL_Event *event,
677                               const Camera *camera,
678                               UndoHistory *undo_history)
679 {
680     trace_assert(label_layer);
681     trace_assert(event);
682     trace_assert(camera);
683     trace_assert(undo_history);
684     trace_assert(label_layer->selected >= 0);
685
686     int changed = 0;
687
688     if (color_picker_event(
689             &label_layer->color_picker,
690             event,
691             camera,
692             &changed) < 0) {
693         return -1;
694     }
695
696     if (changed) {
697         label_layer->inter_color =
698             color_picker_rgba(&label_layer->color_picker);
699
700         if (!color_picker_drag(&label_layer->color_picker)) {
701             UNDO_PUSH(label_layer, undo_history, UNDO_UPDATE);
702
703             dynarray_replace_at(
704                 label_layer->colors,
705                 (size_t) label_layer->selected,
706                 &label_layer->inter_color);
707             label_layer->state = LABEL_LAYER_IDLE;
708         }
709     }
710
711     return 0;
712 }
713
714 int label_layer_event(LabelLayer *label_layer,
715                       const SDL_Event *event,
716                       const Camera *camera,
717                       UndoHistory *undo_history)
718 {
719     trace_assert(label_layer);
720     trace_assert(event);
721     trace_assert(camera);
722     trace_assert(undo_history);
723
724     switch (label_layer->state) {
725     case LABEL_LAYER_IDLE:
726         return label_layer_idle_event(label_layer, event, camera, undo_history);
727
728     case LABEL_LAYER_MOVE:
729         return label_layer_move_event(label_layer, event, camera, undo_history);
730
731     case LABEL_LAYER_EDIT_TEXT:
732         return label_layer_edit_text_event(label_layer, event, camera, undo_history);
733
734     case LABEL_LAYER_EDIT_ID:
735         return label_layer_edit_id_event(label_layer, event, camera, undo_history);
736
737     case LABEL_LAYER_RECOLOR:
738         return label_layer_recolor_event(label_layer, event, camera, undo_history);
739     }
740
741     return 0;
742 }
743
744 size_t label_layer_count(const LabelLayer *label_layer)
745 {
746     return dynarray_count(label_layer->ids);
747 }
748
749 char *label_layer_ids(const LabelLayer *label_layer)
750 {
751     return dynarray_data(label_layer->ids);
752 }
753
754 Point *label_layer_positions(const LabelLayer *label_layer)
755 {
756     return dynarray_data(label_layer->positions);
757 }
758
759 Color *label_layer_colors(const LabelLayer *label_layer)
760 {
761     return dynarray_data(label_layer->colors);
762 }
763
764 char *labels_layer_texts(const LabelLayer *label_layer)
765 {
766     return dynarray_data(label_layer->texts);
767 }
768
769 int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump)
770 {
771     trace_assert(label_layer);
772     trace_assert(filedump);
773
774     size_t n = dynarray_count(label_layer->ids);
775     char *ids = dynarray_data(label_layer->ids);
776     Point *positions = dynarray_data(label_layer->positions);
777     Color *colors = dynarray_data(label_layer->colors);
778     char *texts = dynarray_data(label_layer->texts);
779
780     fprintf(filedump, "%zd\n", n);
781     for (size_t i = 0; i < n; ++i) {
782         fprintf(filedump, "%s %f %f ",
783                 ids + LABEL_LAYER_ID_MAX_SIZE * i,
784                 positions[i].x, positions[i].y);
785         color_hex_to_stream(colors[i], filedump);
786         fprintf(filedump, "\n%s\n", texts + i * LABEL_LAYER_TEXT_MAX_SIZE);
787     }
788
789     return 0;
790 }