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