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