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