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