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