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