]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/point_layer.c
Remove TODO(#956)
[nothing.git] / src / game / level / level_editor / point_layer.c
1 #include <stdio.h>
2
3 #include <SDL.h>
4
5 #include "dynarray.h"
6 #include "game/camera.h"
7 #include "system/line_stream.h"
8 #include "system/log.h"
9 #include "system/lt.h"
10 #include "system/nth_alloc.h"
11 #include "system/stacktrace.h"
12 #include "system/str.h"
13 #include "ui/edit_field.h"
14 #include "./point_layer.h"
15 #include "math/extrema.h"
16 #include "math/mat3x3.h"
17 #include "./color_picker.h"
18 #include "undo_history.h"
19
20 #define POINT_LAYER_ELEMENT_RADIUS 10.0f
21 #define POINT_LAYER_ID_TEXT_SIZE vec(2.0f, 2.0f)
22 #define POINT_LAYER_ID_TEXT_COLOR COLOR_BLACK
23
24 // TODO: PointLayer does not support z reordering
25
26 static int clipboard = 0;
27 static Color clipboard_color;
28
29 typedef enum {
30     POINT_LAYER_IDLE = 0,
31     POINT_LAYER_EDIT_ID,
32     POINT_LAYER_MOVE,
33     POINT_LAYER_RECOLOR
34 } PointLayerState;
35
36 struct PointLayer
37 {
38     Lt *lt;
39     PointLayerState state;
40     Dynarray/*<Point>*/ *positions;
41     Dynarray/*<Color>*/ *colors;
42     Dynarray/*<char[ID_MAX_SIZE]>*/ *ids;
43     int selected;
44     ColorPicker color_picker;
45
46     Point inter_position;
47     Color inter_color;
48     Edit_field *edit_field;
49
50     int id_name_counter;
51     const char *id_name_prefix;
52 };
53
54 typedef enum {
55     UNDO_ADD,
56     UNDO_DELETE,
57     UNDO_UPDATE,
58     UNDO_SWAP
59 } UndoType;
60
61 typedef struct {
62     UndoType type;
63     PointLayer *layer;
64     Point position;
65     Color color;
66     char id[ID_MAX_SIZE];
67     size_t index;
68     size_t index2;
69 } UndoContext;
70
71 static
72 UndoContext create_undo_swap_context(PointLayer *point_layer,
73                                      size_t index, size_t index2)
74 {
75     UndoContext undo_context;
76     undo_context.type = UNDO_SWAP;
77     undo_context.layer = point_layer;
78     undo_context.index = index;
79     undo_context.index2 = index2;
80     return undo_context;
81 }
82
83 static
84 UndoContext create_undo_context(PointLayer *point_layer,
85                                             UndoType type)
86 {
87     trace_assert(type != UNDO_SWAP);
88
89     (void) create_undo_swap_context;
90
91     UndoContext undo_context;
92
93     size_t index =
94         type == UNDO_ADD
95         ? dynarray_count(point_layer->positions) - 1
96         : (size_t) point_layer->selected;
97
98     undo_context.type = type;
99     undo_context.layer = point_layer;
100     dynarray_copy_to(point_layer->positions, &undo_context.position, index);
101     dynarray_copy_to(point_layer->colors, &undo_context.color, index);
102     dynarray_copy_to(point_layer->ids, &undo_context.id, index);
103     undo_context.index = index;
104
105     return undo_context;
106 }
107
108 static
109 void point_layer_undo(void *context, size_t context_size)
110 {
111     trace_assert(context);
112     trace_assert(sizeof(UndoContext) == context_size);
113
114     UndoContext *undo_context = context;
115     PointLayer *point_layer = undo_context->layer;
116
117     switch (undo_context->type) {
118     case UNDO_ADD: {
119         dynarray_pop(point_layer->positions, NULL);
120         dynarray_pop(point_layer->colors, NULL);
121         dynarray_pop(point_layer->ids, NULL);
122         point_layer->selected = -1;
123     } break;
124
125     case UNDO_DELETE: {
126         dynarray_insert_before(point_layer->positions, undo_context->index, &undo_context->position);
127         dynarray_insert_before(point_layer->colors, undo_context->index, &undo_context->color);
128         dynarray_insert_before(point_layer->ids, undo_context->index, &undo_context->id);
129         point_layer->selected = -1;
130     } break;
131
132     case UNDO_UPDATE: {
133         dynarray_replace_at(point_layer->positions, undo_context->index, &undo_context->position);
134         dynarray_replace_at(point_layer->colors, undo_context->index, &undo_context->color);
135         dynarray_replace_at(point_layer->ids, undo_context->index, &undo_context->id);
136     } break;
137
138     case UNDO_SWAP: {
139         dynarray_swap(point_layer->positions, undo_context->index, undo_context->index2);
140         dynarray_swap(point_layer->colors, undo_context->index, undo_context->index2);
141         dynarray_swap(point_layer->ids, undo_context->index, undo_context->index2);
142     } break;
143     }
144 }
145
146 #define UNDO_PUSH(HISTORY, CONTEXT)                                     \
147     do {                                                                \
148         UndoContext context = (CONTEXT);                                \
149         undo_history_push(                                              \
150             HISTORY,                                                    \
151             point_layer_undo,                                           \
152             &context,                                                   \
153             sizeof(context));                                           \
154     } while(0)
155
156 LayerPtr point_layer_as_layer(PointLayer *point_layer)
157 {
158     LayerPtr layer = {
159         .type = LAYER_POINT,
160         .ptr = point_layer
161     };
162     return layer;
163 }
164
165 PointLayer *create_point_layer(const char *id_name_prefix)
166 {
167     Lt *lt = create_lt();
168
169     PointLayer *point_layer = PUSH_LT(lt, nth_calloc(1, sizeof(PointLayer)), free);
170     if (point_layer == NULL) {
171         RETURN_LT(lt, NULL);
172     }
173     point_layer->lt = lt;
174
175     point_layer->state = POINT_LAYER_IDLE;
176
177     point_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray);
178     if (point_layer->positions == NULL) {
179         RETURN_LT(lt, NULL);
180     }
181
182     point_layer->colors = PUSH_LT(lt, create_dynarray(sizeof(Color)), destroy_dynarray);
183     if (point_layer->colors == NULL) {
184         RETURN_LT(lt, NULL);
185     }
186
187     point_layer->ids = PUSH_LT(lt, create_dynarray(sizeof(char) * ID_MAX_SIZE), destroy_dynarray);
188     if (point_layer->ids == NULL) {
189         RETURN_LT(lt, NULL);
190     }
191
192     point_layer->edit_field = PUSH_LT(
193         lt,
194         create_edit_field(
195             POINT_LAYER_ID_TEXT_SIZE,
196             POINT_LAYER_ID_TEXT_COLOR),
197         destroy_edit_field);
198     if (point_layer->edit_field == NULL) {
199         RETURN_LT(lt, NULL);
200     }
201
202     point_layer->id_name_prefix = id_name_prefix;
203
204     return point_layer;
205 }
206
207 PointLayer *create_point_layer_from_line_stream(LineStream *line_stream,
208                                                 const char *id_name_prefix)
209 {
210     trace_assert(line_stream);
211
212     PointLayer *point_layer = create_point_layer(id_name_prefix);
213
214     size_t count = 0;
215     if (sscanf(
216             line_stream_next(line_stream),
217             "%zu",
218             &count) == EOF) {
219         log_fail("Could not read amount of points");
220         RETURN_LT(point_layer->lt, NULL);
221     }
222
223     char color_name[7];
224     char id[ID_MAX_SIZE];
225     float x, y;
226     for (size_t i = 0; i < count; ++i) {
227         if (sscanf(
228                 line_stream_next(line_stream),
229                 "%"STRINGIFY(ID_MAX_SIZE)"s%f%f%6s",
230                 id, &x, &y, color_name) < 0) {
231             log_fail("Could not read %dth goal\n", i);
232             RETURN_LT(point_layer->lt, NULL);
233         }
234         const Color color = hexstr(color_name);
235         const Point point = vec(x, y);
236
237         dynarray_push(point_layer->colors, &color);
238         dynarray_push(point_layer->positions, &point);
239         dynarray_push(point_layer->ids, id);
240     }
241
242     point_layer->selected = -1;
243
244     point_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
245
246     return point_layer;
247 }
248
249 void destroy_point_layer(PointLayer *point_layer)
250 {
251     trace_assert(point_layer);
252     RETURN_LT0(point_layer->lt);
253 }
254
255 static inline
256 Triangle element_shape(Point position, float scale)
257 {
258     return triangle_mat3x3_product(
259         equilateral_triangle(),
260         mat3x3_product(
261             trans_mat_vec(position),
262             scale_mat(scale)));
263 }
264
265 int point_layer_render(const PointLayer *point_layer,
266                        const Camera *camera,
267                        int active)
268 {
269     trace_assert(point_layer);
270     trace_assert(camera);
271
272     const int n = (int) dynarray_count(point_layer->positions);
273     Point *positions = dynarray_data(point_layer->positions);
274     Color *colors = dynarray_data(point_layer->colors);
275     char *ids = dynarray_data(point_layer->ids);
276
277     for (int i = 0; i < n; ++i) {
278         const Color color = color_scale(
279             point_layer->state == POINT_LAYER_RECOLOR && i == point_layer->selected
280             ? point_layer->inter_color
281             : colors[i],
282             rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f));
283
284         const Point position =
285             point_layer->state == POINT_LAYER_MOVE && i == point_layer->selected
286             ? point_layer->inter_position
287             : positions[i];
288
289         // Selection Layer
290         if (active && i == point_layer->selected) {
291             if (camera_fill_triangle(
292                     camera,
293                     element_shape(
294                         position,
295                         POINT_LAYER_ELEMENT_RADIUS + 5.0f),
296                     color_invert(color)) < 0) {
297                 return -1;
298             }
299
300             if (point_layer->state != POINT_LAYER_EDIT_ID &&
301                 camera_render_text(
302                     camera,
303                     ids + ID_MAX_SIZE * i,
304                     POINT_LAYER_ID_TEXT_SIZE,
305                     POINT_LAYER_ID_TEXT_COLOR,
306                     position) < 0) {
307                 return -1;
308             }
309         }
310
311         if (camera_fill_triangle(
312                 camera,
313                 element_shape(
314                     position,
315                     POINT_LAYER_ELEMENT_RADIUS),
316                 color) < 0) {
317             return -1;
318         }
319     }
320
321     if (point_layer->state == POINT_LAYER_EDIT_ID) {
322         if (edit_field_render_world(
323                 point_layer->edit_field,
324                 camera,
325                 positions[point_layer->selected]) < 0) {
326             return -1;
327         }
328     }
329
330     if (active && color_picker_render(&point_layer->color_picker, camera) < 0) {
331         return -1;
332     }
333
334
335     return 0;
336 }
337
338 static
339 int point_layer_element_at(const PointLayer *point_layer,
340                            Point position)
341 {
342     trace_assert(point_layer);
343
344     int n = (int) dynarray_count(point_layer->positions);
345     Point *positions = dynarray_data(point_layer->positions);
346
347     for (int i = n - 1; i >= 0; --i) {
348         if (vec_length(vec_sub(positions[i], position)) < POINT_LAYER_ELEMENT_RADIUS) {
349             return i;
350         }
351     }
352
353     return -1;
354 }
355
356 static
357 int point_layer_add_element(PointLayer *point_layer,
358                             Point position,
359                             Color color,
360                             UndoHistory *undo_history)
361 {
362     trace_assert(point_layer);
363     trace_assert(undo_history);
364
365     char id[ID_MAX_SIZE];
366     snprintf(id, ID_MAX_SIZE, "%s_%d",
367              point_layer->id_name_prefix,
368              point_layer->id_name_counter++);
369
370     dynarray_push(point_layer->positions, &position);
371     dynarray_push(point_layer->colors, &color);
372     dynarray_push(point_layer->ids, id);
373
374     UNDO_PUSH(
375         undo_history,
376         create_undo_context(point_layer, UNDO_ADD));
377
378     return 0;
379 }
380
381 static
382 void point_layer_delete_nth_element(PointLayer *point_layer,
383                                     size_t i,
384                                     UndoHistory *undo_history)
385 {
386     trace_assert(point_layer);
387
388     UNDO_PUSH(
389         undo_history,
390         create_undo_context(
391             point_layer,
392             UNDO_DELETE));
393
394     dynarray_delete_at(point_layer->positions, i);
395     dynarray_delete_at(point_layer->colors, i);
396     dynarray_delete_at(point_layer->ids, i);
397 }
398
399 static
400 int point_layer_idle_event(PointLayer *point_layer,
401                            const SDL_Event *event,
402                            const Camera *camera,
403                            UndoHistory *undo_history)
404 {
405     trace_assert(point_layer);
406     trace_assert(event);
407     trace_assert(camera);
408
409     int selected = 0;
410     if (color_picker_event(
411             &point_layer->color_picker,
412             event,
413             camera,
414             &selected) < 0) {
415         return -1;
416     }
417
418     if (selected) {
419         if (point_layer->selected >= 0) {
420             point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
421             point_layer->state = POINT_LAYER_RECOLOR;
422         }
423         return 0;
424     }
425
426     switch (event->type) {
427     case SDL_MOUSEBUTTONDOWN: {
428         switch (event->button.button) {
429         case SDL_BUTTON_LEFT: {
430             const Point position = camera_map_screen(camera, event->button.x, event->button.y);
431
432             point_layer->selected = point_layer_element_at(
433                 point_layer, position);
434
435             if (point_layer->selected < 0) {
436                 point_layer_add_element(
437                     point_layer,
438                     position,
439                     color_picker_rgba(&point_layer->color_picker),
440                     undo_history);
441             } else {
442                 Color *colors = dynarray_data(point_layer->colors);
443                 Point *positions = dynarray_data(point_layer->positions);
444
445                 point_layer->state = POINT_LAYER_MOVE;
446                 point_layer->color_picker =
447                     create_color_picker_from_rgba(colors[point_layer->selected]);
448                 point_layer->inter_position = positions[point_layer->selected];
449             }
450         } break;
451         }
452     } break;
453
454     case SDL_KEYDOWN: {
455         switch (event->key.keysym.sym) {
456         case SDLK_DELETE: {
457             if (0 <= point_layer->selected && point_layer->selected < (int) dynarray_count(point_layer->positions)) {
458                 point_layer_delete_nth_element(
459                     point_layer,
460                     (size_t)point_layer->selected,
461                     undo_history);
462                 point_layer->selected = -1;
463             }
464         } break;
465
466         case SDLK_F2: {
467             if (point_layer->selected >= 0) {
468                 char *ids = dynarray_data(point_layer->ids);
469                 point_layer->state = POINT_LAYER_EDIT_ID;
470                 edit_field_replace(
471                     point_layer->edit_field,
472                     ids + ID_MAX_SIZE * point_layer->selected);
473                 SDL_StartTextInput();
474             }
475         } break;
476
477         case SDLK_c: {
478             if ((event->key.keysym.mod & KMOD_LCTRL) && point_layer->selected >= 0) {
479                 clipboard = 1;
480                 dynarray_copy_to(point_layer->colors, &clipboard_color, (size_t)point_layer->selected);
481             }
482         } break;
483
484         case SDLK_v: {
485             if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
486                 int x, y;
487                 SDL_GetMouseState(&x, &y);
488                 Point position = camera_map_screen(camera, x, y);
489
490                 point_layer_add_element(
491                     point_layer,
492                     position,
493                     clipboard_color,
494                     undo_history);
495             }
496         } break;
497         }
498     } break;
499     }
500
501     return 0;
502 }
503
504 static
505 int point_layer_edit_id_event(PointLayer *point_layer,
506                               const SDL_Event *event,
507                               const Camera *camera,
508                               UndoHistory *undo_history)
509 {
510     trace_assert(point_layer);
511     trace_assert(event);
512     trace_assert(camera);
513
514     switch (event->type) {
515     case SDL_KEYDOWN: {
516         switch(event->key.keysym.sym) {
517         case SDLK_RETURN: {
518             UNDO_PUSH(
519                 undo_history,
520                 create_undo_context(
521                     point_layer,
522                     UNDO_UPDATE));
523
524             char *id = dynarray_pointer_at(point_layer->ids, (size_t) point_layer->selected);
525             const char *text = edit_field_as_text(point_layer->edit_field);
526             size_t n = min_size_t(strlen(text), ID_MAX_SIZE - 1);
527             memcpy(id, text, n);
528             memset(id + n, 0, ID_MAX_SIZE - n);
529
530             point_layer->state = POINT_LAYER_IDLE;
531             SDL_StopTextInput();
532             return 0;
533         } break;
534
535         case SDLK_ESCAPE: {
536             point_layer->state = POINT_LAYER_IDLE;
537             SDL_StopTextInput();
538             return 0;
539         } break;
540         }
541     } break;
542     }
543
544     return edit_field_event(point_layer->edit_field, event);
545 }
546
547 static
548 int point_layer_move_event(PointLayer *point_layer,
549                            const SDL_Event *event,
550                            const Camera *camera,
551                            UndoHistory *undo_history)
552 {
553     trace_assert(point_layer);
554     trace_assert(event);
555     trace_assert(camera);
556     trace_assert(point_layer->selected >= 0);
557
558     switch (event->type) {
559     case SDL_MOUSEBUTTONUP: {
560         switch (event->button.button) {
561         case SDL_BUTTON_LEFT: {
562             point_layer->state = POINT_LAYER_IDLE;
563
564             // TODO(#1014): just click (without moving) on the point creates an undo history entry
565             UNDO_PUSH(
566                 undo_history,
567                 create_undo_context(
568                     point_layer,
569                     UNDO_UPDATE));
570
571             dynarray_replace_at(
572                 point_layer->positions,
573                 (size_t) point_layer->selected,
574                 &point_layer->inter_position);
575         } break;
576         }
577     } break;
578
579     case SDL_MOUSEMOTION: {
580         point_layer->inter_position =
581             camera_map_screen(camera, event->motion.x, event->motion.y);
582     } break;
583     }
584
585     return 0;
586 }
587
588 static
589 int point_layer_recolor_event(PointLayer *point_layer,
590                               const SDL_Event *event,
591                               const Camera *camera,
592                               UndoHistory *undo_history)
593 {
594     trace_assert(point_layer);
595     trace_assert(event);
596     trace_assert(camera);
597     trace_assert(undo_history);
598     trace_assert(point_layer->selected >= 0);
599
600     int selected = 0;
601     if (color_picker_event(
602             &point_layer->color_picker,
603             event,
604             camera,
605             &selected) < 0) {
606         return -1;
607     }
608
609     if (selected) {
610         point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
611
612         if (!color_picker_drag(&point_layer->color_picker)) {
613             UNDO_PUSH(
614                 undo_history,
615                 create_undo_context(
616                     point_layer,
617                     UNDO_UPDATE));
618
619             dynarray_replace_at(
620                 point_layer->colors,
621                 (size_t) point_layer->selected,
622                 &point_layer->inter_color);
623
624             point_layer->state = POINT_LAYER_IDLE;
625         }
626     }
627
628
629     return 0;
630 }
631
632 int point_layer_event(PointLayer *point_layer,
633                       const SDL_Event *event,
634                       const Camera *camera,
635                       UndoHistory *undo_history)
636 {
637     trace_assert(point_layer);
638     trace_assert(event);
639     trace_assert(camera);
640     trace_assert(undo_history);
641
642     switch (point_layer->state) {
643     case POINT_LAYER_IDLE:
644         return point_layer_idle_event(point_layer, event, camera, undo_history);
645
646     case POINT_LAYER_EDIT_ID:
647         return point_layer_edit_id_event(point_layer, event, camera, undo_history);
648
649     case POINT_LAYER_MOVE:
650         return point_layer_move_event(point_layer, event, camera, undo_history);
651
652     case POINT_LAYER_RECOLOR:
653         return point_layer_recolor_event(point_layer, event, camera, undo_history);
654     }
655
656     return 0;
657 }
658
659 size_t point_layer_count(const PointLayer *point_layer)
660 {
661     trace_assert(point_layer);
662     return dynarray_count(point_layer->positions);
663 }
664
665 const Point *point_layer_positions(const PointLayer *point_layer)
666 {
667     trace_assert(point_layer);
668     return dynarray_data(point_layer->positions);
669 }
670
671 const Color *point_layer_colors(const PointLayer *point_layer)
672 {
673     trace_assert(point_layer);
674     return dynarray_data(point_layer->colors);
675 }
676
677 const char *point_layer_ids(const PointLayer *point_layer)
678 {
679     trace_assert(point_layer);
680     return dynarray_data(point_layer->ids);
681 }
682
683 int point_layer_dump_stream(const PointLayer *point_layer,
684                             FILE *filedump)
685 {
686     trace_assert(point_layer);
687     trace_assert(filedump);
688
689     size_t n = dynarray_count(point_layer->ids);
690     char *ids = dynarray_data(point_layer->ids);
691     Point *positions = dynarray_data(point_layer->positions);
692     Color *colors = dynarray_data(point_layer->colors);
693
694     fprintf(filedump, "%zd\n", n);
695     for (size_t i = 0; i < n; ++i) {
696         fprintf(filedump, "%s %f %f ",
697                 ids + ID_MAX_SIZE * i,
698                 positions[i].x, positions[i].y);
699         color_hex_to_stream(colors[i], filedump);
700         fprintf(filedump, "\n");
701     }
702
703     return 0;
704 }