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