4 #include "game/camera.h"
5 #include "system/stacktrace.h"
6 #include "system/nth_alloc.h"
7 #include "system/log.h"
10 #include "rect_layer.h"
12 #include "system/str.h"
13 #include "undo_history.h"
14 #include "game/level/action.h"
16 #include "math/extrema.h"
18 #define RECT_LAYER_SELECTION_THICCNESS 15.0f
19 #define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
20 #define CREATE_AREA_THRESHOLD 10.0
22 static int rect_clipboard = 0;
23 static Rect rect_clipboard_rect;
24 static Color rect_clipboard_color;
26 static Cursor_Style resize_styles[1 << RECT_SIDE_N] = {
28 CURSOR_STYLE_RESIZE_VERT, // [1]
29 CURSOR_STYLE_RESIZE_HORIS, // [2]
30 CURSOR_STYLE_RESIZE_DIAG1, // [3]
31 CURSOR_STYLE_RESIZE_VERT, // [4]
33 CURSOR_STYLE_RESIZE_DIAG2, // [6]
35 CURSOR_STYLE_RESIZE_HORIS, // [8]
36 CURSOR_STYLE_RESIZE_DIAG2, // [9]
39 CURSOR_STYLE_RESIZE_DIAG1 // [12]
57 char id[ENTITY_MAX_ID_SIZE];
78 UndoElementContext element;
83 RectUndoContext create_rect_undo_add_context(RectLayer *layer, size_t index)
86 trace_assert(index < layer->rects.count);
88 RectUndoContext undo_context;
89 undo_context.add.type = RECT_UNDO_ADD;
90 undo_context.add.layer = layer;
91 undo_context.add.index = index;
96 RectUndoContext create_rect_undo_element_context(RectLayer *layer, size_t index)
99 trace_assert(index < layer->rects.count);
101 RectUndoContext undo_context;
102 undo_context.element.layer = layer;
103 undo_context.element.index = index;
104 dynarray_copy_to(&layer->rects, &undo_context.element.rect, index);
105 dynarray_copy_to(&layer->colors, &undo_context.element.color, index);
106 dynarray_copy_to(&layer->ids, undo_context.element.id, index);
107 dynarray_copy_to(&layer->actions, &undo_context.element.action, index);
112 RectUndoContext create_rect_undo_update_context(RectLayer *rect_layer, size_t index)
114 RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, index);
115 undo_context.type = RECT_UNDO_UPDATE;
120 RectUndoContext create_rect_undo_delete_context(RectLayer *rect_layer, size_t i)
122 RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, i);
123 undo_context.type = RECT_UNDO_DELETE;
128 RectUndoContext create_rect_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
130 RectUndoContext undo_context;
131 undo_context.swap.type = RECT_UNDO_SWAP;
132 undo_context.swap.layer = rect_layer;
133 undo_context.swap.index1 = index1;
134 undo_context.swap.index2 = index2;
139 void rect_layer_undo(void *context, size_t context_size)
141 trace_assert(context);
142 trace_assert(sizeof(RectUndoContext) == context_size);
144 RectUndoContext *undo_context = context;
146 switch (undo_context->type) {
147 case RECT_UNDO_ADD: {
148 RectLayer *layer = undo_context->add.layer;
149 dynarray_delete_at(&layer->rects, undo_context->add.index);
150 dynarray_delete_at(&layer->colors, undo_context->add.index);
151 dynarray_delete_at(&layer->ids, undo_context->add.index);
152 dynarray_delete_at(&layer->actions, undo_context->add.index);
153 layer->selection = -1;
156 case RECT_UNDO_DELETE: {
157 RectLayer *layer = undo_context->element.layer;
158 dynarray_insert_before(&layer->rects, undo_context->element.index, &undo_context->element.rect);
159 dynarray_insert_before(&layer->colors, undo_context->element.index, &undo_context->element.color);
160 dynarray_insert_before(&layer->ids, undo_context->element.index, &undo_context->element.id);
161 dynarray_insert_before(&layer->actions, undo_context->element.index, &undo_context->element.action);
162 layer->selection = -1;
165 case RECT_UNDO_UPDATE: {
166 RectLayer *layer = undo_context->element.layer;
167 dynarray_replace_at(&layer->rects, undo_context->element.index, &undo_context->element.rect);
168 dynarray_replace_at(&layer->colors, undo_context->element.index, &undo_context->element.color);
169 dynarray_replace_at(&layer->ids, undo_context->element.index, &undo_context->element.id);
170 dynarray_replace_at(&layer->actions, undo_context->element.index, &undo_context->element.action);
173 case RECT_UNDO_SWAP: {
174 RectLayer *layer = undo_context->element.layer;
175 dynarray_swap(&layer->rects, undo_context->swap.index1, undo_context->swap.index2);
176 dynarray_swap(&layer->colors, undo_context->swap.index1, undo_context->swap.index2);
177 dynarray_swap(&layer->ids, undo_context->swap.index1, undo_context->swap.index2);
178 dynarray_swap(&layer->actions, undo_context->swap.index1, undo_context->swap.index2);
183 #define RECT_UNDO_PUSH(HISTORY, CONTEXT) \
185 RectUndoContext context = (CONTEXT); \
193 static int rect_layer_add_rect(RectLayer *layer,
196 UndoHistory *undo_history)
200 dynarray_push(&layer->rects, &rect);
201 dynarray_push(&layer->colors, &color);
203 char id[ENTITY_MAX_ID_SIZE];
204 snprintf(id, ENTITY_MAX_ID_SIZE, "%s_%d",
205 layer->id_name_prefix,
206 layer->id_name_counter++);
207 dynarray_push(&layer->ids, id);
209 dynarray_push_empty(&layer->actions);
213 create_rect_undo_add_context(
215 layer->rects.count - 1));
220 static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
224 int n = (int) layer->rects.count;
225 Rect *rects = (Rect*)layer->rects.data;
227 for (int i = n - 1; i >= 0; --i) {
228 if (rect_contains_point(rects[i], position)) {
236 static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
237 UndoHistory *undo_history)
240 trace_assert(a < layer->rects.count);
241 trace_assert(b < layer->rects.count);
243 dynarray_swap(&layer->rects, a, b);
244 dynarray_swap(&layer->colors, a, b);
245 dynarray_swap(&layer->ids, a, b);
246 dynarray_swap(&layer->actions, a, b);
248 RECT_UNDO_PUSH(undo_history, create_rect_undo_swap_context(layer, a, b));
251 static int rect_layer_delete_rect_at_index(RectLayer *layer,
253 UndoHistory *undo_history)
257 RECT_UNDO_PUSH(undo_history, create_rect_undo_delete_context(layer, i));
259 dynarray_delete_at(&layer->rects, i);
260 dynarray_delete_at(&layer->colors, i);
261 dynarray_delete_at(&layer->ids, i);
262 dynarray_delete_at(&layer->actions, i);
267 static int calc_resize_mask(Vec2f point, Rect rect)
270 for (Rect_side side = 0; side < RECT_SIDE_N; ++side) {
271 if (rect_side_distance(rect, point, side) < RECT_LAYER_SELECTION_THICCNESS) {
272 mask = mask | (1 << side);
278 #define TOOL_BUTTON_WIDTH 50.0f
279 #define TOOL_BUTTON_HEIGHT 50.0f
280 #define TOOL_BAR_PADDING 20.0f
281 #define TOOL_BAR_BACKGROUND rgba(0.8f, 0.8f, 0.8f, 1.0f)
282 #define TOOL_BAR_FOREGROUND rgba(0.2f, 0.2f, 0.2f, 1.0f)
285 Rect subtract_tool_button_rect(const Camera *camera)
287 const Rect view_port = camera_view_port_screen(camera);
290 view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING,
296 Rect snapping_tool_button_rect(const Camera *camera)
298 const Rect view_port = camera_view_port_screen(camera);
300 TOOL_BAR_PADDING + TOOL_BUTTON_WIDTH + TOOL_BAR_PADDING,
301 view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING,
306 static int rect_layer_event_idle(RectLayer *layer,
307 const SDL_Event *event,
308 const Camera *camera,
309 UndoHistory *undo_history)
313 trace_assert(camera);
315 int color_changed = 0;
316 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
321 if (layer->selection >= 0) {
322 dynarray_copy_to(&layer->colors, &layer->inter_color, (size_t)layer->selection);
323 layer->state = RECT_LAYER_RECOLOR;
328 Rect *rects = (Rect*)layer->rects.data;
330 switch (event->type) {
331 case SDL_MOUSEBUTTONDOWN: {
332 switch (event->button.button) {
333 case SDL_BUTTON_LEFT: {
334 Vec2f screen_position =
335 vec((float) event->button.x,
336 (float) event->button.y);
337 if (rect_contains_point(subtract_tool_button_rect(camera), screen_position)) {
338 layer->subtract_enabled = !layer->subtract_enabled;
342 if (rect_contains_point(snapping_tool_button_rect(camera), screen_position)) {
343 layer->snapping_enabled = !layer->snapping_enabled;
347 Vec2f position = camera_map_screen(
351 int rect_at_position =
352 rect_layer_rect_at(layer, position);
355 Color *colors = (Color*)layer->colors.data;
357 if (layer->selection >= 0 &&
358 layer->selection == rect_at_position &&
359 (layer->resize_mask = calc_resize_mask(
360 vec((float) event->button.x, (float)event->button.y),
361 camera_rect(camera, rects[layer->selection])))) {
362 layer->state = RECT_LAYER_RESIZE;
363 dynarray_copy_to(&layer->rects, &layer->inter_rect, (size_t) layer->selection);
364 } else if (rect_at_position >= 0) {
365 layer->selection = rect_at_position;
366 layer->state = RECT_LAYER_MOVE;
367 layer->move_anchor = vec_sub(
370 rects[layer->selection].x,
371 rects[layer->selection].y));
372 layer->color_picker =
373 create_color_picker_from_rgba(colors[rect_at_position]);
374 dynarray_copy_to(&layer->rects, &layer->inter_rect, (size_t) rect_at_position);
376 layer->selection = rect_at_position;
378 if (layer->selection < 0) {
379 layer->state = layer->subtract_enabled
380 ? RECT_LAYER_SUBTRACT
382 layer->create_begin = position;
383 layer->create_end = position;
390 case SDL_MOUSEMOTION: {
392 Vec2f position = camera_map_screen(
396 if (layer->selection >= 0 &&
397 layer->selection == rect_layer_rect_at(layer, position) &&
398 (resize_mask = calc_resize_mask(
399 vec((float) event->button.x, (float)event->button.y),
400 camera_rect(camera, rects[layer->selection])))) {
401 layer->cursor->style = resize_styles[resize_mask];
403 layer->cursor->style = CURSOR_STYLE_POINTER;
408 switch (event->key.keysym.sym) {
410 if ((event->key.keysym.mod & KMOD_SHIFT)
411 && (layer->selection >= 0)
412 && ((size_t)(layer->selection + 1) < layer->rects.count)) {
413 rect_layer_swap_elements(
415 (size_t) layer->selection,
416 (size_t) layer->selection + 1,
423 if ((event->key.keysym.mod & KMOD_SHIFT)
424 && (layer->selection > 0)
425 && ((size_t) layer->selection < layer->rects.count)) {
426 rect_layer_swap_elements(
428 (size_t) layer->selection,
429 (size_t) layer->selection - 1,
436 if (layer->selection >= 0) {
437 rect_layer_delete_rect_at_index(
439 (size_t) layer->selection,
441 layer->selection = -1;
446 // TODO(#1171): there is no UI indication that we are in the snapping mode
447 layer->snapping_enabled = !layer->snapping_enabled;
451 if (layer->selection >= 0) {
452 const char *ids = (char*)layer->ids.data;
453 Color *colors = (Color*)layer->colors.data;
456 &layer->id_edit_field,
457 RECT_LAYER_ID_LABEL_SIZE,
458 color_invert(colors[layer->selection]));
460 layer->state = RECT_LAYER_ID_RENAME;
462 &layer->id_edit_field,
463 ids + layer->selection * ENTITY_MAX_ID_SIZE);
464 SDL_StartTextInput();
469 if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
471 dynarray_copy_to(&layer->rects, &rect_clipboard_rect, (size_t)layer->selection);
472 dynarray_copy_to(&layer->colors, &rect_clipboard_color, (size_t)layer->selection);
477 if ((event->key.keysym.mod & KMOD_LCTRL) && rect_clipboard) {
479 SDL_GetMouseState(&x, &y);
480 Vec2f position = camera_map_screen(camera, x, y);
484 rect(position.x, position.y,
485 rect_clipboard_rect.w, rect_clipboard_rect.h),
486 rect_clipboard_color,
497 #define GEOMETRY_CAPACITY 256
502 Rect rects[GEOMETRY_CAPACITY];
503 Color colors[GEOMETRY_CAPACITY];
507 void push_geometry(Geometry *geometry, Rect rect, Color color)
510 // TODO(#1252): push_geometry may fail if there is too many rects produced
511 assert(geometry->count < GEOMETRY_CAPACITY);
513 if ((rect.w * rect.h) > 1e-6f) {
514 size_t i = (geometry->first + geometry->count) % GEOMETRY_CAPACITY;
515 geometry->rects[i] = rect;
516 geometry->colors[i] = color;
522 void subtract_rect_from_rect(Rect a, Color color_a, Rect c, Geometry *result)
526 Rect b = rects_overlap_area(a, c);
528 if (b.w * b.h < 1e-6) {
529 push_geometry(result, a, color_a);
533 push_geometry(result, (Rect) {a.x, a.y, a.w, b.y - a.y}, color_a);
534 push_geometry(result, (Rect) {a.x, b.y, b.x - a.x, b.h}, color_a);
535 push_geometry(result, (Rect) {
538 a.w - (b.x - a.x) - b.w,
541 push_geometry(result, (Rect) {
545 a.h - (b.y - a.y) - b.h
550 void subtract_rect_from_geometry(Geometry *result, Rect b)
554 size_t count = result->count;
555 size_t first = result->first;
557 for (size_t i = 0; i < count; ++i) {
558 result->first = (result->first + 1) % GEOMETRY_CAPACITY;
560 subtract_rect_from_rect(
561 result->rects[(i + first) % GEOMETRY_CAPACITY],
562 result->colors[(i + first) % GEOMETRY_CAPACITY],
568 static int rect_layer_event_subtract(RectLayer *layer,
569 const SDL_Event *event,
570 const Camera *camera,
571 UndoHistory *undo_history)
575 trace_assert(camera);
576 trace_assert(undo_history);
578 Rect *rects = layer->rects.data;
579 Color *colors = layer->colors.data;
581 switch (event->type) {
582 case SDL_MOUSEBUTTONUP: {
583 switch (event->button.button) {
584 case SDL_BUTTON_LEFT: {
585 const Rect real_rect =
589 const float area = real_rect.w * real_rect.h;
591 Geometry geometry = {0};
593 if (area >= CREATE_AREA_THRESHOLD) {
594 for (size_t i = 0; i < layer->rects.count;) {
595 Rect overlap_area = rects_overlap_area(
598 if (overlap_area.w * overlap_area.h > 1e-6) {
599 push_geometry(&geometry, rects[i], colors[i]);
600 rect_layer_delete_rect_at_index(layer, i, undo_history);
606 subtract_rect_from_geometry(&geometry, real_rect);
608 for (size_t i = 0; i < geometry.count; ++i) {
609 size_t j = (i + geometry.first) % GEOMETRY_CAPACITY;
617 log_info("The area is too small %f. Such small box won't be cut.\n", area);
619 layer->state = RECT_LAYER_IDLE;
624 case SDL_MOUSEMOTION: {
625 layer->create_end = camera_map_screen(
635 static int rect_layer_event_create(RectLayer *layer,
636 const SDL_Event *event,
637 const Camera *camera,
638 UndoHistory *undo_history)
642 trace_assert(camera);
644 switch (event->type) {
645 case SDL_MOUSEBUTTONUP: {
646 switch (event->button.button) {
647 case SDL_BUTTON_LEFT: {
648 const Rect real_rect =
652 const float area = real_rect.w * real_rect.h;
654 if (area >= CREATE_AREA_THRESHOLD) {
658 color_picker_rgba(&layer->color_picker),
661 log_info("The area is too small %f. Such small box won't be created.\n", area);
663 layer->state = RECT_LAYER_IDLE;
668 case SDL_MOUSEMOTION: {
669 layer->create_end = camera_map_screen(
679 void snap_rect_resize_if_enabled(RectLayer *layer, Rect *a, float snapping_threshold)
682 trace_assert(layer->selection >= 0);
685 if (!layer->snapping_enabled) return;
687 Rect *rects = (Rect*)layer->rects.data;
688 size_t rects_size = layer->rects.count;
690 for (size_t i = 0; i < rects_size; ++i) {
691 if (i == (size_t) layer->selection) continue;
693 const Rect b = rects[i];
695 if (segment_overlap(vec(a->x, a->x + a->w), vec(b.x, b.x + b.w))) {
696 snap_var2seg(&a->y, b.y, 0, b.h, snapping_threshold);
699 if (segment_overlap(vec(a->y, a->y + a->h), vec(b.y, b.y + b.h))) {
700 snap_var2seg(&a->x, b.x, 0, b.w, snapping_threshold);
705 static int rect_layer_event_resize(RectLayer *layer,
706 const SDL_Event *event,
707 const Camera *camera,
708 UndoHistory *undo_history)
712 trace_assert(camera);
713 trace_assert(layer->selection >= 0);
715 Rect *rects = (Rect*)layer->rects.data;
717 float scaled_snap_threshold = SNAPPING_THRESHOLD / camera->scale;
719 switch (event->type) {
720 case SDL_MOUSEMOTION: {
721 Vec2f position = camera_map_screen(
726 switch (layer->resize_mask) {
728 Rect a = rect(rects[layer->selection].x,
730 rects[layer->selection].w,
731 rects[layer->selection].h);
733 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
735 layer->inter_rect = rect_from_points(
737 rect_position2(rects[layer->selection]));
741 Rect a = rect(position.x,
742 rects[layer->selection].y,
743 rects[layer->selection].w,
744 rects[layer->selection].h);
746 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
748 layer->inter_rect = rect_from_points(
750 rect_position2(rects[layer->selection]));
753 case 3: { // TOP,LEFT
757 rects[layer->selection].w,
758 rects[layer->selection].h);
760 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
762 layer->inter_rect = rect_from_points(
764 rect_position2(rects[layer->selection]));
768 Rect a = rect(rects[layer->selection].x,
770 rects[layer->selection].w,
771 rects[layer->selection].h);
773 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
775 layer->inter_rect = rect_from_points(
776 rect_position(rects[layer->selection]),
777 vec(rects[layer->selection].x + rects[layer->selection].w,
781 case 6: { // BOTTOM,LEFT
785 rects[layer->selection].w,
786 -rects[layer->selection].h);
788 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
790 layer->inter_rect = rect_from_points(
791 vec(a.x, rects[layer->selection].y),
792 vec(rects[layer->selection].x + rects[layer->selection].w,
797 Rect a = rect(position.x,
798 rects[layer->selection].y,
799 rects[layer->selection].w,
800 rects[layer->selection].h);
802 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
804 layer->inter_rect = rect_from_points(
805 rect_position(rects[layer->selection]),
806 vec(a.x, rects[layer->selection].y + rects[layer->selection].h));
809 case 9: { // TOP,RIGHT
813 -rects[layer->selection].w,
814 rects[layer->selection].h);
816 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
818 layer->inter_rect = rect_from_points(
819 vec(rects[layer->selection].x, a.y),
821 rects[layer->selection].y + rects[layer->selection].h));
824 case 12: { // BOTTOM,RIGHT
828 -rects[layer->selection].w,
829 -rects[layer->selection].h);
831 snap_rect_resize_if_enabled(layer, &a, scaled_snap_threshold);
833 layer->inter_rect = rect_from_points(
834 rect_position(rects[layer->selection]),
841 case SDL_MOUSEBUTTONUP: {
842 layer->state = RECT_LAYER_IDLE;
845 create_rect_undo_update_context(
847 (size_t) layer->selection));
848 dynarray_replace_at(&layer->rects, (size_t) layer->selection, &layer->inter_rect);
856 void snap_rect_move_if_enabled(RectLayer *layer, Rect *a,
857 float snapping_threshold)
861 trace_assert(layer->selection >= 0);
863 if (!layer->snapping_enabled) return;
865 Rect *rects = (Rect*)layer->rects.data;
866 size_t rects_size = layer->rects.count;
868 for (size_t i = 0; i < rects_size; ++i) {
869 if (i == (size_t) layer->selection) continue;
871 const Rect b = rects[i];
873 if (segment_overlap(vec(a->x, a->x + a->w), vec(b.x, b.x + b.w))) {
874 snap_seg2seg(&a->y, b.y, a->h, b.h, snapping_threshold);
877 if (segment_overlap(vec(a->y, a->y + a->h), vec(b.y, b.y + b.h))) {
878 snap_seg2seg(&a->x, b.x, a->w, b.w, snapping_threshold);
883 static int rect_layer_event_move(RectLayer *layer,
884 const SDL_Event *event,
885 const Camera *camera,
886 UndoHistory *undo_history)
890 trace_assert(camera);
891 trace_assert(layer->selection >= 0);
893 Rect *rects = (Rect*)layer->rects.data;
895 switch (event->type) {
896 case SDL_MOUSEMOTION: {
897 const Uint8 *state = SDL_GetKeyboardState(NULL);
898 const Vec2f mouse_pos = vec_sub(
905 if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
906 layer->inter_rect.x = mouse_pos.x;
907 layer->inter_rect.y = mouse_pos.y;
909 const Vec2f rect_pos = rect_position(rects[layer->selection]);
911 const float dx = fabsf(rect_pos.x - mouse_pos.x);
912 const float dy = fabsf(rect_pos.y - mouse_pos.y);
915 layer->inter_rect.x = mouse_pos.x;
916 layer->inter_rect.y = rect_pos.y;
918 layer->inter_rect.x = rect_pos.x;
919 layer->inter_rect.y = mouse_pos.y;
923 snap_rect_move_if_enabled(layer, &layer->inter_rect,
924 SNAPPING_THRESHOLD / camera->scale);
927 case SDL_MOUSEBUTTONUP: {
928 layer->state = RECT_LAYER_IDLE;
930 float distance = vec_length(
931 vec_sub(rect_position(layer->inter_rect),
932 rect_position(rects[layer->selection])));
934 if (distance > 1e-6) {
937 create_rect_undo_update_context(
938 layer, (size_t) layer->selection));
941 (size_t) layer->selection,
949 static int rect_layer_event_id_rename(RectLayer *layer,
950 const SDL_Event *event,
951 const Camera *camera,
952 UndoHistory *undo_history)
956 trace_assert(camera);
957 trace_assert(layer->selection >= 0);
959 switch (event->type) {
961 switch (event->key.keysym.sym) {
965 create_rect_undo_update_context(
967 (size_t) layer->selection));
969 char *id = dynarray_pointer_at(&layer->ids, (size_t)layer->selection);
970 memset(id, 0, ENTITY_MAX_ID_SIZE);
971 memcpy(id, edit_field_as_text(&layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1);
972 layer->state = RECT_LAYER_IDLE;
977 layer->state = RECT_LAYER_IDLE;
984 return edit_field_event(&layer->id_edit_field, event);
987 LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
996 RectLayer *create_rect_layer(Memory *memory,
997 const char *id_name_prefix,
1000 trace_assert(memory);
1001 trace_assert(id_name_prefix);
1002 trace_assert(cursor);
1004 RectLayer *rect_layer = memory_alloc(memory, sizeof(RectLayer));
1006 rect_layer->ids = create_dynarray(memory, sizeof(char) * ENTITY_MAX_ID_SIZE);
1007 rect_layer->rects = create_dynarray(memory, sizeof(Rect));
1008 rect_layer->colors = create_dynarray(memory, sizeof(Color));
1009 rect_layer->actions = create_dynarray(memory, sizeof(Action));
1010 rect_layer->id_edit_field.font_size = RECT_LAYER_ID_LABEL_SIZE;
1011 rect_layer->id_edit_field.font_color = COLOR_BLACK;
1012 rect_layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
1013 rect_layer->selection = -1;
1014 rect_layer->id_name_prefix = id_name_prefix;
1015 rect_layer->cursor = cursor;
1020 void rect_layer_load(RectLayer *layer, Memory *memory, String *input)
1022 trace_assert(layer);
1023 trace_assert(memory);
1024 trace_assert(input);
1026 int n = atoi(string_to_cstr(memory, trim(chop_by_delim(input, '\n'))));
1027 char id[ENTITY_MAX_ID_SIZE];
1028 for (int i = 0; i < n; ++i) {
1030 String line = trim(chop_by_delim(input, '\n'));
1031 String string_id = trim(chop_word(&line));
1032 rect.x = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
1033 rect.y = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
1034 rect.w = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
1035 rect.h = strtof(string_to_cstr(memory, trim(chop_word(&line))), NULL);
1036 Color color = hexs(trim(chop_word(&line)));
1038 memset(id, 0, ENTITY_MAX_ID_SIZE);
1042 min_size_t(ENTITY_MAX_ID_SIZE - 1, string_id.count));
1044 dynarray_push(&layer->rects, &rect);
1045 dynarray_push(&layer->colors, &color);
1046 dynarray_push(&layer->ids, id);
1049 .type = ACTION_NONE,
1053 String action_string = trim(chop_word(&line));
1054 if (action_string.count > 0) {
1055 action.type = (ActionType)atol(string_to_cstr(memory, action_string));
1056 switch (action.type) {
1057 case ACTION_NONE: break;
1058 case ACTION_TOGGLE_GOAL:
1059 case ACTION_HIDE_LABEL: {
1060 String label_id = trim(chop_word(&line));
1061 trace_assert(label_id.count > 0);
1062 memset(action.entity_id, 0, ENTITY_MAX_ID_SIZE);
1063 memcpy(action.entity_id,
1066 ENTITY_MAX_ID_SIZE - 1,
1070 case ACTION_N: break;
1074 dynarray_push(&layer->actions, &action);
1079 void render_tool_bar_button(const Camera *camera,
1080 Rect button_rect, const char *text,
1081 Color background_color,
1082 Color foreground_color)
1084 const Vec2f text_size = vec(5.0f, 5.0f);
1085 const Rect text_rect = sprite_font_boundary_box(
1086 vec(0.0f, 0.0f), text_size, text);
1087 camera_fill_rect_screen(
1091 camera_render_text_screen(
1097 button_rect.x + (button_rect.w - text_rect.w) * 0.5f,
1098 button_rect.y + (button_rect.h - text_rect.h) * 0.5f));
1102 int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
1104 trace_assert(layer);
1105 trace_assert(camera);
1107 const size_t n = layer->rects.count;
1108 Rect *rects = (Rect *)layer->rects.data;
1109 Color *colors = (Color *)layer->colors.data;
1110 const char *ids = (const char *)layer->ids.data;
1113 for (size_t i = 0; i < n; ++i) {
1114 Rect rect = rects[i];
1115 Color color = colors[i];
1117 if (layer->selection == (int) i) {
1118 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1119 rect = layer->inter_rect;
1122 if (layer->state == RECT_LAYER_RECOLOR) {
1123 color = layer->inter_color;
1128 if (camera_fill_rect(
1133 rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
1138 // Selection Overlay
1139 if (active && layer->selection >= 0) {
1140 Rect rect = rects[layer->selection];
1141 Color color = colors[layer->selection];
1143 if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
1144 rect = layer->inter_rect;
1147 if (layer->state == RECT_LAYER_RECOLOR) {
1148 color = layer->inter_color;
1151 const Rect overlay_rect =
1153 camera_rect(camera, rect),
1154 -RECT_LAYER_SELECTION_THICCNESS * 0.5f);
1155 const Color overlay_color = color_invert(color);
1158 if (camera_draw_thicc_rect_screen(
1162 RECT_LAYER_SELECTION_THICCNESS) < 0) {
1166 const Vec2f rect_id_pos = vec_sub(
1167 rect_position(rect),
1169 RECT_LAYER_ID_LABEL_SIZE,
1170 vec(0.0f, FONT_CHAR_HEIGHT)));
1173 if (layer->state == RECT_LAYER_ID_RENAME) {
1174 // ID renaming Edit Field
1175 if (edit_field_render_world(
1176 &layer->id_edit_field,
1183 if (camera_render_text(
1185 ids + layer->selection * ENTITY_MAX_ID_SIZE,
1186 RECT_LAYER_ID_LABEL_SIZE,
1187 color_invert(color),
1195 const Color color = color_picker_rgba(&layer->color_picker);
1196 if (layer->state == RECT_LAYER_CREATE) {
1197 if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
1202 if (layer->state == RECT_LAYER_SUBTRACT) {
1203 if (camera_draw_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
1208 if (active && color_picker_render(&layer->color_picker, camera) < 0) {
1214 render_tool_bar_button(
1215 camera, subtract_tool_button_rect(camera), "/",
1216 layer->subtract_enabled ? TOOL_BAR_BACKGROUND : TOOL_BAR_FOREGROUND,
1217 layer->subtract_enabled ? TOOL_BAR_FOREGROUND : TOOL_BAR_BACKGROUND);
1219 render_tool_bar_button(
1220 camera, snapping_tool_button_rect(camera), "S",
1221 layer->snapping_enabled ? TOOL_BAR_BACKGROUND : TOOL_BAR_FOREGROUND,
1222 layer->snapping_enabled ? TOOL_BAR_FOREGROUND : TOOL_BAR_BACKGROUND);
1229 int rect_layer_event_recolor(RectLayer *layer,
1230 const SDL_Event *event,
1231 const Camera *camera,
1232 UndoHistory *undo_history)
1234 trace_assert(layer);
1235 trace_assert(event);
1236 trace_assert(camera);
1237 trace_assert(undo_history);
1238 trace_assert(layer->selection >= 0);
1240 int color_changed = 0;
1241 if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
1245 if (color_changed) {
1246 layer->inter_color = color_picker_rgba(&layer->color_picker);
1248 if (!color_picker_drag(&layer->color_picker)) {
1251 create_rect_undo_update_context(
1253 (size_t)layer->selection));
1254 dynarray_replace_at(&layer->colors, (size_t) layer->selection, &layer->inter_color);
1255 layer->state = RECT_LAYER_IDLE;
1262 int rect_layer_event(RectLayer *layer,
1263 const SDL_Event *event,
1264 const Camera *camera,
1265 UndoHistory *undo_history)
1267 trace_assert(layer);
1268 trace_assert(event);
1269 trace_assert(undo_history);
1271 switch (layer->state) {
1272 case RECT_LAYER_IDLE:
1273 return rect_layer_event_idle(layer, event, camera, undo_history);
1275 case RECT_LAYER_CREATE:
1276 return rect_layer_event_create(layer, event, camera, undo_history);
1278 case RECT_LAYER_RESIZE:
1279 return rect_layer_event_resize(layer, event, camera, undo_history);
1281 case RECT_LAYER_MOVE:
1282 return rect_layer_event_move(layer, event, camera, undo_history);
1284 case RECT_LAYER_ID_RENAME:
1285 return rect_layer_event_id_rename(layer, event, camera, undo_history);
1287 case RECT_LAYER_RECOLOR:
1288 return rect_layer_event_recolor(layer, event, camera, undo_history);
1290 case RECT_LAYER_SUBTRACT:
1291 return rect_layer_event_subtract(layer, event, camera, undo_history);
1298 size_t rect_layer_count(const RectLayer *layer)
1300 return layer->rects.count;
1303 const Rect *rect_layer_rects(const RectLayer *layer)
1305 return (const Rect *)layer->rects.data;
1308 const Color *rect_layer_colors(const RectLayer *layer)
1310 return (const Color *)layer->colors.data;
1313 const char *rect_layer_ids(const RectLayer *layer)
1315 return (const char *)layer->ids.data;
1318 int rect_layer_dump_stream(const RectLayer *layer, FILE *filedump)
1320 trace_assert(layer);
1321 trace_assert(filedump);
1323 size_t n = layer->ids.count;
1324 char *ids = (char *)layer->ids.data;
1325 Rect *rects = (Rect *)layer->rects.data;
1326 Color *colors = (Color *)layer->colors.data;
1327 Action *actions = (Action *)layer->actions.data;
1329 fprintf(filedump, "%zd\n", n);
1330 for (size_t i = 0; i < n; ++i) {
1331 fprintf(filedump, "%s %f %f %f %f ",
1332 ids + ENTITY_MAX_ID_SIZE * i,
1333 rects[i].x, rects[i].y, rects[i].w, rects[i].h);
1334 color_hex_to_stream(colors[i], filedump);
1336 switch (actions[i].type) {
1337 case ACTION_NONE: {} break;
1339 case ACTION_TOGGLE_GOAL:
1340 case ACTION_HIDE_LABEL: {
1341 fprintf(filedump, " %d %.*s",
1342 (int)actions[i].type,
1343 ENTITY_MAX_ID_SIZE, actions[i].entity_id);
1345 case ACTION_N: break;
1348 fprintf(filedump, "\n");
1354 const Action *rect_layer_actions(const RectLayer *layer)
1356 return (const Action *)layer->actions.data;