]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/point_layer.c
Try to remove malloc from LevelEditor
[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 PointLayer *create_point_layer_from_memory(Memory *memory,
156                                            const char *id_name_prefix)
157 {
158     trace_assert(memory);
159     trace_assert(id_name_prefix);
160
161     PointLayer *result = memory_alloc(memory, sizeof(PointLayer));
162     memset(result, 0, sizeof(PointLayer));
163     result->state = POINT_LAYER_IDLE;
164     result->positions = create_dynarray_from_memory(memory, sizeof(Vec2f));
165     result->colors = create_dynarray_from_memory(memory, sizeof(Color));
166     result->ids = create_dynarray_from_memory(memory, sizeof(char) * ID_MAX_SIZE);
167     result->edit_field.font_size = POINT_LAYER_ID_TEXT_SIZE;
168     result->edit_field.font_color = POINT_LAYER_ID_TEXT_COLOR;
169     result->id_name_prefix = id_name_prefix;
170     return result;
171 }
172
173 void point_layer_load(PointLayer *point_layer,
174                       Memory *memory,
175                       String *input)
176 {
177     trace_assert(point_layer);
178     trace_assert(memory);
179     trace_assert(input);
180
181     int n = atoi(string_to_cstr(memory, trim(chop_by_delim(input, '\n'))));
182     char id[ENTITY_MAX_ID_SIZE];
183     for (int i = 0; i < n; ++i) {
184         String line = trim(chop_by_delim(input, '\n'));
185         String string_id = trim(chop_word(&line));
186         Vec2f point;
187         point.x = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
188         point.y = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
189         Color color = hexs(trim(chop_word(&line)));
190
191         memset(id, 0, ENTITY_MAX_ID_SIZE);
192         memcpy(id, string_id.data, min_size_t(ENTITY_MAX_ID_SIZE - 1, string_id.count));
193
194         dynarray_push(&point_layer->positions, &point);
195         dynarray_push(&point_layer->colors, &color);
196         dynarray_push(&point_layer->ids, id);
197     }
198 }
199
200 static inline
201 Triangle element_shape(Vec2f position, float scale)
202 {
203     return triangle_mat3x3_product(
204         equilateral_triangle(),
205         mat3x3_product(
206             trans_mat_vec(position),
207             scale_mat(scale)));
208 }
209
210 int point_layer_render(const PointLayer *point_layer,
211                        const Camera *camera,
212                        int active)
213 {
214     trace_assert(point_layer);
215     trace_assert(camera);
216
217     const int n = (int)point_layer->positions.count;
218     Vec2f *positions = (Vec2f *)point_layer->positions.data;
219     Color *colors = (Color *)point_layer->colors.data;
220     char *ids = (char *)point_layer->ids.data;
221
222     for (int i = 0; i < n; ++i) {
223         const Color color = color_scale(
224             point_layer->state == POINT_LAYER_RECOLOR && i == point_layer->selection
225             ? point_layer->inter_color
226             : colors[i],
227             rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f));
228
229         const Vec2f position =
230             point_layer->state == POINT_LAYER_MOVE && i == point_layer->selection
231             ? point_layer->inter_position
232             : positions[i];
233
234         // Selection Layer
235         if (active && i == point_layer->selection) {
236             if (camera_fill_triangle(
237                     camera,
238                     element_shape(
239                         position,
240                         POINT_LAYER_ELEMENT_RADIUS + 5.0f),
241                     color_invert(color)) < 0) {
242                 return -1;
243             }
244
245             if (point_layer->state != POINT_LAYER_EDIT_ID &&
246                 camera_render_text(
247                     camera,
248                     ids + ID_MAX_SIZE * i,
249                     POINT_LAYER_ID_TEXT_SIZE,
250                     POINT_LAYER_ID_TEXT_COLOR,
251                     position) < 0) {
252                 return -1;
253             }
254         }
255
256         if (camera_fill_triangle(
257                 camera,
258                 element_shape(
259                     position,
260                     POINT_LAYER_ELEMENT_RADIUS),
261                 color) < 0) {
262             return -1;
263         }
264     }
265
266     if (point_layer->state == POINT_LAYER_EDIT_ID) {
267         if (edit_field_render_world(
268                 &point_layer->edit_field,
269                 camera,
270                 positions[point_layer->selection]) < 0) {
271             return -1;
272         }
273     }
274
275     if (active && color_picker_render(&point_layer->color_picker, camera) < 0) {
276         return -1;
277     }
278
279
280     return 0;
281 }
282
283 static
284 int point_layer_element_at(const PointLayer *point_layer,
285                            Vec2f position)
286 {
287     trace_assert(point_layer);
288
289     int n = (int) point_layer->positions.count;
290     Vec2f *positions = (Vec2f *)point_layer->positions.data;
291
292     for (int i = n - 1; i >= 0; --i) {
293         if (vec_length(vec_sub(positions[i], position)) < POINT_LAYER_ELEMENT_RADIUS) {
294             return i;
295         }
296     }
297
298     return -1;
299 }
300
301 static
302 int point_layer_add_element(PointLayer *point_layer,
303                             Vec2f position,
304                             Color color,
305                             UndoHistory *undo_history)
306 {
307     trace_assert(point_layer);
308     trace_assert(undo_history);
309
310     char id[ID_MAX_SIZE];
311     snprintf(id, ID_MAX_SIZE, "%s_%d",
312              point_layer->id_name_prefix,
313              point_layer->id_name_counter++);
314
315     dynarray_push(&point_layer->positions, &position);
316     dynarray_push(&point_layer->colors, &color);
317     dynarray_push(&point_layer->ids, id);
318
319     POINT_UNDO_PUSH(
320         undo_history,
321         create_point_undo_context(point_layer, POINT_UNDO_ADD));
322
323     return 0;
324 }
325
326 static
327 void point_layer_swap_elements(PointLayer *point_layer,
328                                size_t a, size_t b,
329                                UndoHistory *undo_history)
330 {
331     trace_assert(point_layer);
332     trace_assert(undo_history);
333     trace_assert(a < point_layer->positions.count);
334     trace_assert(b < point_layer->positions.count);
335
336     dynarray_swap(&point_layer->positions, a, b);
337     dynarray_swap(&point_layer->colors, a, b);
338     dynarray_swap(&point_layer->ids, a, b);
339
340     POINT_UNDO_PUSH(
341         undo_history,
342         create_point_undo_swap_context(point_layer, a, b));
343 }
344
345 static
346 void point_layer_delete_nth_element(PointLayer *point_layer,
347                                     size_t i,
348                                     UndoHistory *undo_history)
349 {
350     trace_assert(point_layer);
351
352     POINT_UNDO_PUSH(
353         undo_history,
354         create_point_undo_context(
355             point_layer,
356             POINT_UNDO_DELETE));
357
358     dynarray_delete_at(&point_layer->positions, i);
359     dynarray_delete_at(&point_layer->colors, i);
360     dynarray_delete_at(&point_layer->ids, i);
361 }
362
363 static
364 int point_layer_idle_event(PointLayer *point_layer,
365                            const SDL_Event *event,
366                            const Camera *camera,
367                            UndoHistory *undo_history)
368 {
369     trace_assert(point_layer);
370     trace_assert(event);
371     trace_assert(camera);
372
373     int selected = 0;
374     if (color_picker_event(
375             &point_layer->color_picker,
376             event,
377             camera,
378             &selected) < 0) {
379         return -1;
380     }
381
382     if (selected) {
383         if (point_layer->selection >= 0) {
384             point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
385             point_layer->state = POINT_LAYER_RECOLOR;
386         }
387         return 0;
388     }
389
390     switch (event->type) {
391     case SDL_MOUSEBUTTONDOWN: {
392         switch (event->button.button) {
393         case SDL_BUTTON_LEFT: {
394             const Vec2f position = camera_map_screen(camera, event->button.x, event->button.y);
395
396             point_layer->selection = point_layer_element_at(
397                 point_layer, position);
398
399             if (point_layer->selection < 0) {
400                 point_layer_add_element(
401                     point_layer,
402                     position,
403                     color_picker_rgba(&point_layer->color_picker),
404                     undo_history);
405             } else {
406                 Color *colors = (Color*)point_layer->colors.data;
407                 Vec2f *positions = (Vec2f*)point_layer->positions.data;
408
409                 point_layer->state = POINT_LAYER_MOVE;
410                 point_layer->color_picker =
411                     create_color_picker_from_rgba(colors[point_layer->selection]);
412                 point_layer->inter_position = positions[point_layer->selection];
413             }
414         } break;
415         }
416     } break;
417
418     case SDL_KEYDOWN: {
419         switch (event->key.keysym.sym) {
420         case SDLK_UP: {
421             if ((event->key.keysym.mod & KMOD_SHIFT)
422                 && (point_layer->selection >= 0)
423                 && ((size_t)(point_layer->selection + 1) < point_layer->positions.count)) {
424                 point_layer_swap_elements(
425                     point_layer,
426                     (size_t) point_layer->selection,
427                     (size_t) point_layer->selection + 1,
428                     undo_history);
429                 point_layer->selection++;
430             }
431         } break;
432
433         case SDLK_DOWN: {
434             if ((event->key.keysym.mod & KMOD_SHIFT)
435                 && (point_layer->selection > 0)
436                 && ((size_t) point_layer->selection < point_layer->positions.count)) {
437                 point_layer_swap_elements(
438                     point_layer,
439                     (size_t) point_layer->selection,
440                     (size_t) point_layer->selection - 1,
441                     undo_history);
442                 point_layer->selection--;
443             }
444         } break;
445
446         case SDLK_DELETE: {
447             if (0 <= point_layer->selection && point_layer->selection < (int) point_layer->positions.count) {
448                 point_layer_delete_nth_element(
449                     point_layer,
450                     (size_t)point_layer->selection,
451                     undo_history);
452                 point_layer->selection = -1;
453             }
454         } break;
455
456         case SDLK_F2: {
457             if (point_layer->selection >= 0) {
458                 char *ids = (char*)point_layer->ids.data;
459                 point_layer->state = POINT_LAYER_EDIT_ID;
460                 edit_field_replace(
461                     &point_layer->edit_field,
462                     ids + ID_MAX_SIZE * point_layer->selection);
463                 SDL_StartTextInput();
464             }
465         } break;
466
467         case SDLK_c: {
468             if ((event->key.keysym.mod & KMOD_LCTRL) && point_layer->selection >= 0) {
469                 point_clipboard = 1;
470                 dynarray_copy_to(&point_layer->colors, &point_clipboard_color, (size_t)point_layer->selection);
471             }
472         } break;
473
474         case SDLK_v: {
475             if ((event->key.keysym.mod & KMOD_LCTRL) && point_clipboard) {
476                 int x, y;
477                 SDL_GetMouseState(&x, &y);
478                 Vec2f position = camera_map_screen(camera, x, y);
479
480                 point_layer_add_element(
481                     point_layer,
482                     position,
483                     point_clipboard_color,
484                     undo_history);
485             }
486         } break;
487         }
488     } break;
489     }
490
491     return 0;
492 }
493
494 static
495 int point_layer_edit_id_event(PointLayer *point_layer,
496                               const SDL_Event *event,
497                               const Camera *camera,
498                               UndoHistory *undo_history)
499 {
500     trace_assert(point_layer);
501     trace_assert(event);
502     trace_assert(camera);
503
504     switch (event->type) {
505     case SDL_KEYDOWN: {
506         switch(event->key.keysym.sym) {
507         case SDLK_RETURN: {
508             POINT_UNDO_PUSH(
509                 undo_history,
510                 create_point_undo_context(
511                     point_layer,
512                     POINT_UNDO_UPDATE));
513
514             char *id = dynarray_pointer_at(&point_layer->ids, (size_t) point_layer->selection);
515             const char *text = edit_field_as_text(&point_layer->edit_field);
516             size_t n = min_size_t(strlen(text), ID_MAX_SIZE - 1);
517             memcpy(id, text, n);
518             memset(id + n, 0, ID_MAX_SIZE - n);
519
520             point_layer->state = POINT_LAYER_IDLE;
521             SDL_StopTextInput();
522             return 0;
523         } break;
524
525         case SDLK_ESCAPE: {
526             point_layer->state = POINT_LAYER_IDLE;
527             SDL_StopTextInput();
528             return 0;
529         } break;
530         }
531     } break;
532     }
533
534     return edit_field_event(&point_layer->edit_field, event);
535 }
536
537 static
538 int point_layer_move_event(PointLayer *point_layer,
539                            const SDL_Event *event,
540                            const Camera *camera,
541                            UndoHistory *undo_history)
542 {
543     trace_assert(point_layer);
544     trace_assert(event);
545     trace_assert(camera);
546     trace_assert(point_layer->selection >= 0);
547
548     Vec2f *positions = (Vec2f*)point_layer->positions.data;
549
550     switch (event->type) {
551     case SDL_MOUSEBUTTONUP: {
552         switch (event->button.button) {
553         case SDL_BUTTON_LEFT: {
554             point_layer->state = POINT_LAYER_IDLE;
555
556             const float distance = vec_length(
557                 vec_sub(point_layer->inter_position,
558                         positions[point_layer->selection]));
559
560             if (distance > 1e-6) {
561                 POINT_UNDO_PUSH(
562                     undo_history,
563                     create_point_undo_context(
564                         point_layer,
565                         POINT_UNDO_UPDATE));
566
567                 dynarray_replace_at(
568                     &point_layer->positions,
569                     (size_t) point_layer->selection,
570                     &point_layer->inter_position);
571             }
572         } break;
573         }
574     } break;
575
576     case SDL_MOUSEMOTION: {
577         const Uint8 *state = SDL_GetKeyboardState(NULL);
578         const Vec2f mouse_pos = camera_map_screen(camera, event->motion.x, event->motion.y);
579         const Vec2f point_pos = positions[point_layer->selection];
580
581         if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
582             point_layer->inter_position = mouse_pos;
583         } else {
584             const float dx = fabsf(point_pos.x - mouse_pos.x);
585             const float dy = fabsf(point_pos.y - mouse_pos.y);
586
587             if (dx > dy) {
588                 point_layer->inter_position = vec(mouse_pos.x, point_pos.y);
589             } else {
590                 point_layer->inter_position = vec(point_pos.x, mouse_pos.y);
591             }
592         }
593     } break;
594     }
595
596     return 0;
597 }
598
599 static
600 int point_layer_recolor_event(PointLayer *point_layer,
601                               const SDL_Event *event,
602                               const Camera *camera,
603                               UndoHistory *undo_history)
604 {
605     trace_assert(point_layer);
606     trace_assert(event);
607     trace_assert(camera);
608     trace_assert(undo_history);
609     trace_assert(point_layer->selection >= 0);
610
611     int selected = 0;
612     if (color_picker_event(
613             &point_layer->color_picker,
614             event,
615             camera,
616             &selected) < 0) {
617         return -1;
618     }
619
620     if (selected) {
621         point_layer->inter_color = color_picker_rgba(&point_layer->color_picker);
622
623         if (!color_picker_drag(&point_layer->color_picker)) {
624             POINT_UNDO_PUSH(
625                 undo_history,
626                 create_point_undo_context(
627                     point_layer,
628                     POINT_UNDO_UPDATE));
629
630             dynarray_replace_at(
631                 &point_layer->colors,
632                 (size_t) point_layer->selection,
633                 &point_layer->inter_color);
634
635             point_layer->state = POINT_LAYER_IDLE;
636         }
637     }
638
639
640     return 0;
641 }
642
643 int point_layer_event(PointLayer *point_layer,
644                       const SDL_Event *event,
645                       const Camera *camera,
646                       UndoHistory *undo_history)
647 {
648     trace_assert(point_layer);
649     trace_assert(event);
650     trace_assert(camera);
651     trace_assert(undo_history);
652
653     switch (point_layer->state) {
654     case POINT_LAYER_IDLE:
655         return point_layer_idle_event(point_layer, event, camera, undo_history);
656
657     case POINT_LAYER_EDIT_ID:
658         return point_layer_edit_id_event(point_layer, event, camera, undo_history);
659
660     case POINT_LAYER_MOVE:
661         return point_layer_move_event(point_layer, event, camera, undo_history);
662
663     case POINT_LAYER_RECOLOR:
664         return point_layer_recolor_event(point_layer, event, camera, undo_history);
665     }
666
667     return 0;
668 }
669
670 size_t point_layer_count(const PointLayer *point_layer)
671 {
672     trace_assert(point_layer);
673     return point_layer->positions.count;
674 }
675
676 const Vec2f *point_layer_positions(const PointLayer *point_layer)
677 {
678     trace_assert(point_layer);
679     return (const Vec2f *)point_layer->positions.data;
680 }
681
682 const Color *point_layer_colors(const PointLayer *point_layer)
683 {
684     trace_assert(point_layer);
685     return (const Color *)point_layer->colors.data;
686 }
687
688 const char *point_layer_ids(const PointLayer *point_layer)
689 {
690     trace_assert(point_layer);
691     return (const char *)point_layer->ids.data;
692 }
693
694 int point_layer_dump_stream(const PointLayer *point_layer,
695                             FILE *filedump)
696 {
697     trace_assert(point_layer);
698     trace_assert(filedump);
699
700     size_t n = point_layer->ids.count;
701     char *ids = (char *) point_layer->ids.data;
702     Vec2f *positions = (Vec2f *) point_layer->positions.data;
703     Color *colors = (Color *) point_layer->colors.data;
704
705     fprintf(filedump, "%zd\n", n);
706     for (size_t i = 0; i < n; ++i) {
707         fprintf(filedump, "%s %f %f ",
708                 ids + ID_MAX_SIZE * i,
709                 positions[i].x, positions[i].y);
710         color_hex_to_stream(colors[i], filedump);
711         fprintf(filedump, "\n");
712     }
713
714     return 0;
715 }