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