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