]> git.lizzy.rs Git - nothing.git/blob - src/game/level/level_editor/rect_layer.c
Refactor IDLE state dispatching
[nothing.git] / src / game / level / level_editor / rect_layer.c
1 #include "game/camera.h"
2 #include "system/lt.h"
3 #include "system/stacktrace.h"
4 #include "system/nth_alloc.h"
5 #include "system/log.h"
6 #include "math/rect.h"
7 #include "color.h"
8 #include "rect_layer.h"
9 #include "dynarray.h"
10 #include "system/line_stream.h"
11 #include "color_picker.h"
12 #include "system/str.h"
13
14 #define RECT_LAYER_ID_MAX_SIZE 36
15 #define RECT_LAYER_SELECTION_THICCNESS 5.0f
16 #define PROTO_AREA_THRESHOLD 10.0
17
18 // TODO(#942): RectLayer does not allow to move rectangle arround
19 typedef enum {
20     RECT_LAYER_IDLE = 0,
21     RECT_LAYER_PROTO,
22     RECT_LAYER_RESIZE,
23     RECT_LAYER_MOVE
24 } RectLayerState;
25
26 /* TODO(#886): RectLayer does not allow to modify ids of Rects */
27 struct RectLayer {
28     Lt *lt;
29     RectLayerState state;
30     Dynarray *ids;
31     Dynarray *rects;
32     Dynarray *colors;
33     ColorPicker color_picker;
34     Vec proto_begin;
35     Vec proto_end;
36     int selection;
37     Vec move_anchor;
38 };
39
40 static int rect_layer_rect_at(RectLayer *layer, Vec position)
41 {
42     trace_assert(layer);
43
44     const size_t n = dynarray_count(layer->rects);
45     Rect *rects = dynarray_data(layer->rects);
46
47     for (size_t i = 0; i < n; ++i) {
48         if (rect_contains_point(rects[i], position)) {
49             return (int) i;
50         }
51     }
52
53     return -1;
54 }
55
56 static int rect_layer_delete_rect_at(RectLayer *layer, size_t i)
57 {
58     trace_assert(layer);
59
60     dynarray_delete_at(layer->rects, i);
61     dynarray_delete_at(layer->colors, i);
62     dynarray_delete_at(layer->ids, i);
63
64     return 0;
65 }
66
67 static Rect rect_layer_resize_anchor(const RectLayer *layer, size_t i)
68 {
69     Rect *rects = dynarray_data(layer->rects);
70     return rect(rects[i].x + rects[i].w,
71                 rects[i].y + rects[i].h,
72                 RECT_LAYER_SELECTION_THICCNESS * 2.0f,
73                 RECT_LAYER_SELECTION_THICCNESS * 2.0f);
74 }
75
76 static int rect_layer_add_rect(RectLayer *layer, Rect rect, Color color)
77 {
78     trace_assert(layer);
79
80     if (dynarray_push(layer->rects, &rect) < 0) {
81         return -1;
82     }
83
84     if (dynarray_push(layer->colors, &color) < 0) {
85         return -1;
86     }
87
88     char id[RECT_LAYER_ID_MAX_SIZE];
89     for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
90         id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
91     }
92     id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
93
94     if (dynarray_push(layer->ids, id)) {
95         return -1;
96     }
97
98     return 0;
99 }
100
101
102 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
103 {
104     LayerPtr layer = {
105         .type = LAYER_RECT,
106         .ptr = rect_layer
107     };
108     return layer;
109 }
110
111 RectLayer *create_rect_layer(void)
112 {
113     Lt *lt = create_lt();
114
115     RectLayer *layer = PUSH_LT(lt, nth_calloc(1, sizeof(RectLayer)), free);
116     if (layer == NULL) {
117         RETURN_LT(lt, NULL);
118     }
119     layer->lt = lt;
120
121     layer->ids = PUSH_LT(
122         lt,
123         create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
124         destroy_dynarray);
125     if (layer->ids == NULL) {
126         RETURN_LT(lt, NULL);
127     }
128
129     layer->rects = PUSH_LT(
130         lt,
131         create_dynarray(sizeof(Rect)),
132         destroy_dynarray);
133     if (layer->rects == NULL) {
134         RETURN_LT(lt, NULL);
135     }
136
137     layer->colors = PUSH_LT(
138         lt,
139         create_dynarray(sizeof(Color)),
140         destroy_dynarray);
141     if (layer->colors == NULL) {
142         RETURN_LT(lt, NULL);
143     }
144
145     layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
146     layer->selection = -1;
147
148     return layer;
149 }
150
151 RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
152 {
153     trace_assert(line_stream);
154
155     RectLayer *layer = create_rect_layer();
156     if (layer == NULL) {
157         return NULL;
158     }
159
160     const char *line = line_stream_next(line_stream);
161     if (line == NULL) {
162         RETURN_LT(layer->lt, NULL);
163     }
164
165     size_t count = 0;
166     if (sscanf(line, "%zu", &count) < 0) {
167         RETURN_LT(layer->lt, NULL);
168     }
169
170     for (size_t i = 0; i < count; ++i) {
171         line = line_stream_next(line_stream);
172         if (line == NULL) {
173             RETURN_LT(layer->lt, NULL);
174         }
175
176         char hex[7];
177         Rect rect;
178         char id[RECT_LAYER_ID_MAX_SIZE];
179
180         if (sscanf(line,
181                    "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
182                    id,
183                    &rect.x, &rect.y,
184                    &rect.w, &rect.h,
185                    hex) < 0) {
186             RETURN_LT(layer->lt, NULL);
187         }
188
189         Color color = hexstr(hex);
190
191         dynarray_push(layer->rects, &rect);
192         dynarray_push(layer->ids, id);
193         dynarray_push(layer->colors, &color);
194     }
195
196     return layer;
197 }
198
199 void destroy_rect_layer(RectLayer *layer)
200 {
201     trace_assert(layer);
202     RETURN_LT0(layer->lt);
203 }
204
205 int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
206 {
207     trace_assert(layer);
208     trace_assert(camera);
209
210     const size_t n = dynarray_count(layer->rects);
211     Rect *rects = dynarray_data(layer->rects);
212     Color *colors = dynarray_data(layer->colors);
213
214     for (size_t i = 0; i < n; ++i) {
215         if (layer->selection == (int) i) {
216             if (active) {
217                 const Color color = color_invert(colors[i]);
218
219                 if (camera_fill_rect(
220                         camera,
221                         // TODO(#943): thiccness of RectLayer selection should be probably based on zoom
222                         rect_scale(rects[i], RECT_LAYER_SELECTION_THICCNESS),
223                         color) < 0) {
224                     return -1;
225                 }
226
227                 if (camera_fill_rect(
228                         camera,
229                         rect_layer_resize_anchor(layer, i),
230                         color) < 0) {
231                     return -1;
232                 }
233             }
234         }
235
236         if (camera_fill_rect(
237                 camera,
238                 rects[i],
239                 color_scale(
240                     colors[i],
241                     rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
242             return -1;
243         }
244
245     }
246
247     const Color color = color_picker_rgba(&layer->color_picker);
248     if (layer->state == RECT_LAYER_PROTO) {
249         if (camera_fill_rect(camera, rect_from_points(layer->proto_begin, layer->proto_end), color) < 0) {
250             return -1;
251         }
252     }
253
254     if (active && color_picker_render(&layer->color_picker, camera) < 0) {
255         return -1;
256     }
257
258     return 0;
259 }
260
261 int rect_layer_event(RectLayer *layer, const SDL_Event *event, const Camera *camera)
262 {
263     trace_assert(layer);
264     trace_assert(event);
265
266     int selected = 0;
267     if (color_picker_event(&layer->color_picker, event, &selected) < 0) {
268         return -1;
269     }
270
271     if (selected) {
272         return 0;
273     }
274
275     const Color color = color_picker_rgba(&layer->color_picker);
276
277     switch (layer->state) {
278     case RECT_LAYER_PROTO: {
279         switch (event->type) {
280         case SDL_MOUSEBUTTONUP: {
281             switch (event->button.button) {
282             case SDL_BUTTON_LEFT: {
283                 const Rect real_rect =
284                     rect_from_points(
285                         layer->proto_begin,
286                         layer->proto_end);
287                 const float area = real_rect.w * real_rect.h;
288
289                 if (area >= PROTO_AREA_THRESHOLD) {
290                     rect_layer_add_rect(layer, real_rect, color);
291                 } else {
292                     log_info("The area is too small %f. Such small box won't be created.\n", area);
293                 }
294                 layer->state = RECT_LAYER_IDLE;
295             } break;
296             }
297         } break;
298
299         case SDL_MOUSEMOTION: {
300             layer->proto_end = camera_map_screen(
301                 camera,
302                 event->motion.x,
303                 event->motion.y);
304         } break;
305         }
306     } break;
307
308     case RECT_LAYER_RESIZE: {
309         switch (event->type) {
310         case SDL_MOUSEMOTION: {
311             Rect *rects = dynarray_data(layer->rects);
312             trace_assert(layer->selection >= 0);
313             rects[layer->selection] = rect_from_points(
314                 vec(rects[layer->selection].x, rects[layer->selection].y),
315                 vec_sum(
316                     camera_map_screen(
317                         camera,
318                         event->button.x,
319                         event->button.y),
320                     vec(RECT_LAYER_SELECTION_THICCNESS * -0.5f,
321                         RECT_LAYER_SELECTION_THICCNESS * -0.5f)));
322         } break;
323
324         case SDL_MOUSEBUTTONUP: {
325             layer->state = RECT_LAYER_IDLE;
326         } break;
327         }
328     } break;
329
330     case RECT_LAYER_IDLE: {
331         switch (event->type) {
332         case SDL_MOUSEBUTTONDOWN: {
333             switch (event->button.button) {
334             case SDL_BUTTON_LEFT: {
335                 Point position = camera_map_screen(
336                     camera,
337                     event->button.x,
338                     event->button.y);
339                 int rect_at_position =
340                     rect_layer_rect_at(layer, position);
341
342                 if (rect_at_position >= 0) {
343                     Rect *rects = dynarray_data(layer->rects);
344                     layer->selection = rect_at_position;
345                     layer->state = RECT_LAYER_MOVE;
346                     layer->move_anchor =
347                         vec_sub(
348                             position,
349                             vec(
350                                 rects[layer->selection].x,
351                                 rects[layer->selection].y));
352                 } else if (layer->selection >= 0 && rect_contains_point(
353                                rect_layer_resize_anchor(
354                                    layer,
355                                    (size_t)layer->selection),
356                                position)) {
357                     layer->state = RECT_LAYER_RESIZE;
358                 } else {
359                     layer->selection = rect_at_position;
360
361                     if (layer->selection < 0) {
362                         layer->state = RECT_LAYER_PROTO;
363                         layer->proto_begin = position;
364                         layer->proto_end = position;
365                     }
366                 }
367             } break;
368             }
369         } break;
370
371         case SDL_KEYDOWN: {
372             switch (event->key.keysym.sym) {
373             case SDLK_DELETE: {
374                 if (layer->selection >= 0) {
375                     rect_layer_delete_rect_at(layer, (size_t) layer->selection);
376                     layer->selection = -1;
377                 }
378             } break;
379             }
380         } break;
381         }
382     } break;
383
384     case RECT_LAYER_MOVE: {
385         switch (event->type) {
386         case SDL_MOUSEMOTION: {
387             Point position = vec_sub(
388                 camera_map_screen(
389                     camera,
390                     event->button.x,
391                     event->button.y),
392                 layer->move_anchor);
393
394             Rect *rects = dynarray_data(layer->rects);
395
396             trace_assert(layer->selection >= 0);
397
398             rects[layer->selection].x = position.x;
399             rects[layer->selection].y = position.y;
400         } break;
401
402         case SDL_MOUSEBUTTONUP: {
403             layer->state = RECT_LAYER_IDLE;
404         } break;
405         }
406     } break;
407     }
408
409     return 0;
410 }
411
412 size_t rect_layer_count(const RectLayer *layer)
413 {
414     return dynarray_count(layer->rects);
415 }
416
417 const Rect *rect_layer_rects(const RectLayer *layer)
418 {
419     return dynarray_data(layer->rects);
420 }
421
422 const Color *rect_layer_colors(const RectLayer *layer)
423 {
424     return dynarray_data(layer->colors);
425 }
426
427 const char *rect_layer_ids(const RectLayer *layer)
428 {
429     return dynarray_data(layer->ids);
430 }
431
432 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
433 {
434     trace_assert(layer);
435     trace_assert(filedump);
436
437     size_t n = dynarray_count(layer->ids);
438     char *ids = dynarray_data(layer->ids);
439     Rect *rects = dynarray_data(layer->rects);
440     Color *colors = dynarray_data(layer->colors);
441
442     fprintf(filedump, "%zd\n", n);
443     for (size_t i = 0; i < n; ++i) {
444         fprintf(filedump, "%s %f %f %f %f ",
445                 ids + RECT_LAYER_ID_MAX_SIZE * i,
446                 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
447         color_hex_to_stream(colors[i], filedump);
448         fprintf(filedump, "\n");
449     }
450
451     return 0;
452 }