X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fgame%2Flevel%2Flevel_editor%2Fpoint_layer.c;h=50f6d77535ddb40a7145cc78f94bf0babba691a2;hb=b677738fc607173746b2ce5ba1888ecf7c79ef55;hp=a44a9eab818c84e1c4bd8f4fd8abfb4676c86b15;hpb=1677ad8806c18cabe0f7048edf0868999ce5938d;p=nothing.git diff --git a/src/game/level/level_editor/point_layer.c b/src/game/level/level_editor/point_layer.c index a44a9eab..50f6d775 100644 --- a/src/game/level/level_editor/point_layer.c +++ b/src/game/level/level_editor/point_layer.c @@ -13,31 +13,150 @@ #include "ui/edit_field.h" #include "./point_layer.h" #include "math/extrema.h" +#include "math/mat3x3.h" #include "./color_picker.h" +#include "undo_history.h" #define POINT_LAYER_ELEMENT_RADIUS 10.0f #define POINT_LAYER_ID_TEXT_SIZE vec(2.0f, 2.0f) #define POINT_LAYER_ID_TEXT_COLOR COLOR_BLACK -// TODO(#985): PointLayer cannot move its points +static int point_clipboard = 0; +static Color point_clipboard_color; + +// TODO(#1140): PointLayer does not support snapping + typedef enum { POINT_LAYER_IDLE = 0, POINT_LAYER_EDIT_ID, - POINT_LAYER_MOVE + POINT_LAYER_MOVE, + POINT_LAYER_RECOLOR } PointLayerState; struct PointLayer { Lt *lt; PointLayerState state; - Dynarray/**/ *positions; + Dynarray/**/ *positions; Dynarray/**/ *colors; Dynarray/**/ *ids; - Edit_field *edit_field; - int selected; + int selection; ColorPicker color_picker; + + Vec2f inter_position; + Color inter_color; + Edit_field *edit_field; + + int id_name_counter; + const char *id_name_prefix; }; +typedef enum { + POINT_UNDO_ADD, + POINT_UNDO_DELETE, + POINT_UNDO_UPDATE, + POINT_UNDO_SWAP +} PointUndoType; + +typedef struct { + PointUndoType type; + PointLayer *layer; + Vec2f position; + Color color; + char id[ID_MAX_SIZE]; + size_t index; + size_t index2; +} PointUndoContext; + +static +PointUndoContext create_point_undo_swap_context(PointLayer *point_layer, + size_t index, size_t index2) +{ + trace_assert(point_layer); + trace_assert(index < dynarray_count(point_layer->positions)); + trace_assert(index2 < dynarray_count(point_layer->positions)); + + PointUndoContext undo_context; + undo_context.type = POINT_UNDO_SWAP; + undo_context.layer = point_layer; + undo_context.index = index; + undo_context.index2 = index2; + return undo_context; +} + +static +PointUndoContext create_point_undo_context(PointLayer *point_layer, + PointUndoType type) +{ + trace_assert(type != POINT_UNDO_SWAP); + + (void) create_point_undo_swap_context; + + PointUndoContext undo_context; + + size_t index = + type == POINT_UNDO_ADD + ? dynarray_count(point_layer->positions) - 1 + : (size_t) point_layer->selection; + + undo_context.type = type; + undo_context.layer = point_layer; + dynarray_copy_to(point_layer->positions, &undo_context.position, index); + dynarray_copy_to(point_layer->colors, &undo_context.color, index); + dynarray_copy_to(point_layer->ids, &undo_context.id, index); + undo_context.index = index; + + return undo_context; +} + +static +void point_layer_undo(void *context, size_t context_size) +{ + trace_assert(context); + trace_assert(sizeof(PointUndoContext) == context_size); + + PointUndoContext *undo_context = context; + PointLayer *point_layer = undo_context->layer; + + switch (undo_context->type) { + case POINT_UNDO_ADD: { + dynarray_pop(point_layer->positions, NULL); + dynarray_pop(point_layer->colors, NULL); + dynarray_pop(point_layer->ids, NULL); + point_layer->selection = -1; + } break; + + case POINT_UNDO_DELETE: { + dynarray_insert_before(point_layer->positions, undo_context->index, &undo_context->position); + dynarray_insert_before(point_layer->colors, undo_context->index, &undo_context->color); + dynarray_insert_before(point_layer->ids, undo_context->index, &undo_context->id); + point_layer->selection = -1; + } break; + + case POINT_UNDO_UPDATE: { + dynarray_replace_at(point_layer->positions, undo_context->index, &undo_context->position); + dynarray_replace_at(point_layer->colors, undo_context->index, &undo_context->color); + dynarray_replace_at(point_layer->ids, undo_context->index, &undo_context->id); + } break; + + case POINT_UNDO_SWAP: { + dynarray_swap(point_layer->positions, undo_context->index, undo_context->index2); + dynarray_swap(point_layer->colors, undo_context->index, undo_context->index2); + dynarray_swap(point_layer->ids, undo_context->index, undo_context->index2); + } break; + } +} + +#define POINT_UNDO_PUSH(HISTORY, CONTEXT) \ + do { \ + PointUndoContext context = (CONTEXT); \ + undo_history_push( \ + HISTORY, \ + point_layer_undo, \ + &context, \ + sizeof(context)); \ + } while(0) + LayerPtr point_layer_as_layer(PointLayer *point_layer) { LayerPtr layer = { @@ -47,7 +166,7 @@ LayerPtr point_layer_as_layer(PointLayer *point_layer) return layer; } -PointLayer *create_point_layer(void) +PointLayer *create_point_layer(const char *id_name_prefix) { Lt *lt = create_lt(); @@ -59,7 +178,7 @@ PointLayer *create_point_layer(void) point_layer->state = POINT_LAYER_IDLE; - point_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray); + point_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Vec2f)), destroy_dynarray); if (point_layer->positions == NULL) { RETURN_LT(lt, NULL); } @@ -84,14 +203,17 @@ PointLayer *create_point_layer(void) RETURN_LT(lt, NULL); } + point_layer->id_name_prefix = id_name_prefix; + return point_layer; } -PointLayer *create_point_layer_from_line_stream(LineStream *line_stream) +PointLayer *create_point_layer_from_line_stream(LineStream *line_stream, + const char *id_name_prefix) { trace_assert(line_stream); - PointLayer *point_layer = create_point_layer(); + PointLayer *point_layer = create_point_layer(id_name_prefix); size_t count = 0; if (sscanf( @@ -114,16 +236,16 @@ PointLayer *create_point_layer_from_line_stream(LineStream *line_stream) RETURN_LT(point_layer->lt, NULL); } const Color color = hexstr(color_name); - const Point point = vec(x, y); + const Vec2f point = vec(x, y); dynarray_push(point_layer->colors, &color); dynarray_push(point_layer->positions, &point); dynarray_push(point_layer->ids, id); } - point_layer->selected = -1; + point_layer->selection = -1; - point_layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f)); + point_layer->color_picker = create_color_picker_from_rgba(COLOR_RED); return point_layer; } @@ -134,37 +256,48 @@ void destroy_point_layer(PointLayer *point_layer) RETURN_LT0(point_layer->lt); } +static inline +Triangle element_shape(Vec2f position, float scale) +{ + return triangle_mat3x3_product( + equilateral_triangle(), + mat3x3_product( + trans_mat_vec(position), + scale_mat(scale))); +} + int point_layer_render(const PointLayer *point_layer, - Camera *camera, + const Camera *camera, int active) { trace_assert(point_layer); trace_assert(camera); const int n = (int) dynarray_count(point_layer->positions); - Point *positions = dynarray_data(point_layer->positions); + Vec2f *positions = dynarray_data(point_layer->positions); Color *colors = dynarray_data(point_layer->colors); char *ids = dynarray_data(point_layer->ids); for (int i = 0; i < n; ++i) { - const Triangle t = triangle_mat3x3_product( - equilateral_triangle(), - mat3x3_product( - trans_mat(positions[i].x, positions[i].y), - scale_mat(POINT_LAYER_ELEMENT_RADIUS))); - const Color color = color_scale( - colors[i], + point_layer->state == POINT_LAYER_RECOLOR && i == point_layer->selection + ? point_layer->inter_color + : colors[i], rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)); - if (i == point_layer->selected) { - const Triangle t0 = triangle_mat3x3_product( - equilateral_triangle(), - mat3x3_product( - trans_mat(positions[i].x, positions[i].y), - scale_mat(15.0f))); + const Vec2f position = + point_layer->state == POINT_LAYER_MOVE && i == point_layer->selection + ? point_layer->inter_position + : positions[i]; - if (camera_fill_triangle(camera, t0, color_invert(color)) < 0) { + // Selection Layer + if (active && i == point_layer->selection) { + if (camera_fill_triangle( + camera, + element_shape( + position, + POINT_LAYER_ELEMENT_RADIUS + 5.0f), + color_invert(color)) < 0) { return -1; } @@ -174,23 +307,26 @@ int point_layer_render(const PointLayer *point_layer, ids + ID_MAX_SIZE * i, POINT_LAYER_ID_TEXT_SIZE, POINT_LAYER_ID_TEXT_COLOR, - positions[i]) < 0) { + position) < 0) { return -1; } } - if (camera_fill_triangle(camera, t, color) < 0) { + if (camera_fill_triangle( + camera, + element_shape( + position, + POINT_LAYER_ELEMENT_RADIUS), + color) < 0) { return -1; } - } if (point_layer->state == POINT_LAYER_EDIT_ID) { - /* TODO(#855): PointLayer edit field is not scaled on zoom */ if (edit_field_render_world( point_layer->edit_field, camera, - positions[point_layer->selected]) < 0) { + positions[point_layer->selection]) < 0) { return -1; } } @@ -205,14 +341,14 @@ int point_layer_render(const PointLayer *point_layer, static int point_layer_element_at(const PointLayer *point_layer, - Point position) + Vec2f position) { trace_assert(point_layer); int n = (int) dynarray_count(point_layer->positions); - Point *positions = dynarray_data(point_layer->positions); + Vec2f *positions = dynarray_data(point_layer->positions); - for (int i = 0; i < n; ++i) { + for (int i = n - 1; i >= 0; --i) { if (vec_length(vec_sub(positions[i], position)) < POINT_LAYER_ELEMENT_RADIUS) { return i; } @@ -223,29 +359,61 @@ int point_layer_element_at(const PointLayer *point_layer, static int point_layer_add_element(PointLayer *point_layer, - Point position, - Color color) + Vec2f position, + Color color, + UndoHistory *undo_history) { trace_assert(point_layer); + trace_assert(undo_history); char id[ID_MAX_SIZE]; - for (size_t i = 0; i < ID_MAX_SIZE - 1; ++i) { - id[i] = (char) ('a' + rand() % ('z' - 'a' + 1)); - } - id[ID_MAX_SIZE - 1] = '\0'; + snprintf(id, ID_MAX_SIZE, "%s_%d", + point_layer->id_name_prefix, + point_layer->id_name_counter++); dynarray_push(point_layer->positions, &position); dynarray_push(point_layer->colors, &color); dynarray_push(point_layer->ids, id); + POINT_UNDO_PUSH( + undo_history, + create_point_undo_context(point_layer, POINT_UNDO_ADD)); + return 0; } +static +void point_layer_swap_elements(PointLayer *point_layer, + size_t a, size_t b, + UndoHistory *undo_history) +{ + trace_assert(point_layer); + trace_assert(undo_history); + trace_assert(a < dynarray_count(point_layer->positions)); + trace_assert(b < dynarray_count(point_layer->positions)); + + dynarray_swap(point_layer->positions, a, b); + dynarray_swap(point_layer->colors, a, b); + dynarray_swap(point_layer->ids, a, b); + + POINT_UNDO_PUSH( + undo_history, + create_point_undo_swap_context(point_layer, a, b)); +} + static void point_layer_delete_nth_element(PointLayer *point_layer, - size_t i) + size_t i, + UndoHistory *undo_history) { trace_assert(point_layer); + + POINT_UNDO_PUSH( + undo_history, + create_point_undo_context( + point_layer, + POINT_UNDO_DELETE)); + dynarray_delete_at(point_layer->positions, i); dynarray_delete_at(point_layer->colors, i); dynarray_delete_at(point_layer->ids, i); @@ -254,7 +422,8 @@ void point_layer_delete_nth_element(PointLayer *point_layer, static int point_layer_idle_event(PointLayer *point_layer, const SDL_Event *event, - const Camera *camera) + const Camera *camera, + UndoHistory *undo_history) { trace_assert(point_layer); trace_assert(event); @@ -264,17 +433,16 @@ int point_layer_idle_event(PointLayer *point_layer, if (color_picker_event( &point_layer->color_picker, event, + camera, &selected) < 0) { return -1; } if (selected) { - if (point_layer->selected >= 0) { - Color *colors = dynarray_data(point_layer->colors); - colors[point_layer->selected] = - color_picker_rgba(&point_layer->color_picker); + if (point_layer->selection >= 0) { + point_layer->inter_color = color_picker_rgba(&point_layer->color_picker); + point_layer->state = POINT_LAYER_RECOLOR; } - return 0; } @@ -282,19 +450,25 @@ int point_layer_idle_event(PointLayer *point_layer, case SDL_MOUSEBUTTONDOWN: { switch (event->button.button) { case SDL_BUTTON_LEFT: { - const Point position = camera_map_screen(camera, event->button.x, event->button.y); + const Vec2f position = camera_map_screen(camera, event->button.x, event->button.y); - point_layer->selected = point_layer_element_at( + point_layer->selection = point_layer_element_at( point_layer, position); - if (point_layer->selected < 0) { + if (point_layer->selection < 0) { point_layer_add_element( - point_layer, position, color_picker_rgba(&point_layer->color_picker)); + point_layer, + position, + color_picker_rgba(&point_layer->color_picker), + undo_history); } else { Color *colors = dynarray_data(point_layer->colors); + Vec2f *positions = dynarray_data(point_layer->positions); + point_layer->state = POINT_LAYER_MOVE; point_layer->color_picker = - create_color_picker_from_rgba(colors[point_layer->selected]); + create_color_picker_from_rgba(colors[point_layer->selection]); + point_layer->inter_position = positions[point_layer->selection]; } } break; } @@ -302,23 +476,73 @@ int point_layer_idle_event(PointLayer *point_layer, case SDL_KEYDOWN: { switch (event->key.keysym.sym) { + case SDLK_UP: { + if ((event->key.keysym.mod & KMOD_SHIFT) + && (point_layer->selection >= 0) + && ((size_t)(point_layer->selection + 1) < dynarray_count(point_layer->positions))) { + point_layer_swap_elements( + point_layer, + (size_t) point_layer->selection, + (size_t) point_layer->selection + 1, + undo_history); + point_layer->selection++; + } + } break; + + case SDLK_DOWN: { + if ((event->key.keysym.mod & KMOD_SHIFT) + && (point_layer->selection > 0) + && ((size_t) point_layer->selection < dynarray_count(point_layer->positions))) { + point_layer_swap_elements( + point_layer, + (size_t) point_layer->selection, + (size_t) point_layer->selection - 1, + undo_history); + point_layer->selection--; + } + } break; + case SDLK_DELETE: { - if (0 <= point_layer->selected && point_layer->selected < (int) dynarray_count(point_layer->positions)) { - point_layer_delete_nth_element(point_layer, (size_t)point_layer->selected); - point_layer->selected = -1; + if (0 <= point_layer->selection && point_layer->selection < (int) dynarray_count(point_layer->positions)) { + point_layer_delete_nth_element( + point_layer, + (size_t)point_layer->selection, + undo_history); + point_layer->selection = -1; } } break; case SDLK_F2: { - if (point_layer->selected >= 0) { + if (point_layer->selection >= 0) { char *ids = dynarray_data(point_layer->ids); point_layer->state = POINT_LAYER_EDIT_ID; edit_field_replace( point_layer->edit_field, - ids + ID_MAX_SIZE * point_layer->selected); + ids + ID_MAX_SIZE * point_layer->selection); SDL_StartTextInput(); } } break; + + case SDLK_c: { + if ((event->key.keysym.mod & KMOD_LCTRL) && point_layer->selection >= 0) { + point_clipboard = 1; + dynarray_copy_to(point_layer->colors, &point_clipboard_color, (size_t)point_layer->selection); + } + } break; + + case SDLK_v: { + if ((event->key.keysym.mod & KMOD_LCTRL) && point_clipboard) { + int x, y; + SDL_GetMouseState(&x, &y); + Vec2f position = camera_map_screen(camera, x, y); + + point_layer_add_element( + point_layer, + position, + point_clipboard_color, + undo_history); + } + } break; } } break; } @@ -329,7 +553,8 @@ int point_layer_idle_event(PointLayer *point_layer, static int point_layer_edit_id_event(PointLayer *point_layer, const SDL_Event *event, - const Camera *camera) + const Camera *camera, + UndoHistory *undo_history) { trace_assert(point_layer); trace_assert(event); @@ -339,11 +564,18 @@ int point_layer_edit_id_event(PointLayer *point_layer, case SDL_KEYDOWN: { switch(event->key.keysym.sym) { case SDLK_RETURN: { - char *ids = dynarray_data(point_layer->ids); + POINT_UNDO_PUSH( + undo_history, + create_point_undo_context( + point_layer, + POINT_UNDO_UPDATE)); + + char *id = dynarray_pointer_at(point_layer->ids, (size_t) point_layer->selection); const char *text = edit_field_as_text(point_layer->edit_field); - size_t n = max_size_t(strlen(text), ID_MAX_SIZE - 1); - memcpy(ids + point_layer->selected * ID_MAX_SIZE, text, n); - *(ids + point_layer->selected * ID_MAX_SIZE + n) = '\0'; + size_t n = min_size_t(strlen(text), ID_MAX_SIZE - 1); + memcpy(id, text, n); + memset(id + n, 0, ID_MAX_SIZE - n); + point_layer->state = POINT_LAYER_IDLE; SDL_StopTextInput(); return 0; @@ -364,49 +596,131 @@ int point_layer_edit_id_event(PointLayer *point_layer, static int point_layer_move_event(PointLayer *point_layer, const SDL_Event *event, - const Camera *camera) + const Camera *camera, + UndoHistory *undo_history) { trace_assert(point_layer); trace_assert(event); trace_assert(camera); - trace_assert(point_layer->selected >= 0); + trace_assert(point_layer->selection >= 0); + + Vec2f *positions = dynarray_data(point_layer->positions); switch (event->type) { case SDL_MOUSEBUTTONUP: { switch (event->button.button) { case SDL_BUTTON_LEFT: { point_layer->state = POINT_LAYER_IDLE; + + const float distance = vec_length( + vec_sub(point_layer->inter_position, + positions[point_layer->selection])); + + if (distance > 1e-6) { + POINT_UNDO_PUSH( + undo_history, + create_point_undo_context( + point_layer, + POINT_UNDO_UPDATE)); + + dynarray_replace_at( + point_layer->positions, + (size_t) point_layer->selection, + &point_layer->inter_position); + } } break; } } break; case SDL_MOUSEMOTION: { - Point *positions = dynarray_data(point_layer->positions); - positions[point_layer->selected] = - camera_map_screen(camera, event->motion.x, event->motion.y); + const Uint8 *state = SDL_GetKeyboardState(NULL); + const Vec2f mouse_pos = camera_map_screen(camera, event->motion.x, event->motion.y); + const Vec2f point_pos = positions[point_layer->selection]; + + if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) { + point_layer->inter_position = mouse_pos; + } else { + const float dx = fabsf(point_pos.x - mouse_pos.x); + const float dy = fabsf(point_pos.y - mouse_pos.y); + + if (dx > dy) { + point_layer->inter_position = vec(mouse_pos.x, point_pos.y); + } else { + point_layer->inter_position = vec(point_pos.x, mouse_pos.y); + } + } } break; } return 0; } +static +int point_layer_recolor_event(PointLayer *point_layer, + const SDL_Event *event, + const Camera *camera, + UndoHistory *undo_history) +{ + trace_assert(point_layer); + trace_assert(event); + trace_assert(camera); + trace_assert(undo_history); + trace_assert(point_layer->selection >= 0); + + int selected = 0; + if (color_picker_event( + &point_layer->color_picker, + event, + camera, + &selected) < 0) { + return -1; + } + + if (selected) { + point_layer->inter_color = color_picker_rgba(&point_layer->color_picker); + + if (!color_picker_drag(&point_layer->color_picker)) { + POINT_UNDO_PUSH( + undo_history, + create_point_undo_context( + point_layer, + POINT_UNDO_UPDATE)); + + dynarray_replace_at( + point_layer->colors, + (size_t) point_layer->selection, + &point_layer->inter_color); + + point_layer->state = POINT_LAYER_IDLE; + } + } + + + return 0; +} + int point_layer_event(PointLayer *point_layer, const SDL_Event *event, - const Camera *camera) + const Camera *camera, + UndoHistory *undo_history) { trace_assert(point_layer); trace_assert(event); trace_assert(camera); + trace_assert(undo_history); switch (point_layer->state) { case POINT_LAYER_IDLE: - return point_layer_idle_event(point_layer, event, camera); + return point_layer_idle_event(point_layer, event, camera, undo_history); case POINT_LAYER_EDIT_ID: - return point_layer_edit_id_event(point_layer, event, camera); + return point_layer_edit_id_event(point_layer, event, camera, undo_history); case POINT_LAYER_MOVE: - return point_layer_move_event(point_layer, event, camera); + return point_layer_move_event(point_layer, event, camera, undo_history); + + case POINT_LAYER_RECOLOR: + return point_layer_recolor_event(point_layer, event, camera, undo_history); } return 0; @@ -418,7 +732,7 @@ size_t point_layer_count(const PointLayer *point_layer) return dynarray_count(point_layer->positions); } -const Point *point_layer_positions(const PointLayer *point_layer) +const Vec2f *point_layer_positions(const PointLayer *point_layer) { trace_assert(point_layer); return dynarray_data(point_layer->positions); @@ -444,7 +758,7 @@ int point_layer_dump_stream(const PointLayer *point_layer, size_t n = dynarray_count(point_layer->ids); char *ids = dynarray_data(point_layer->ids); - Point *positions = dynarray_data(point_layer->positions); + Vec2f *positions = dynarray_data(point_layer->positions); Color *colors = dynarray_data(point_layer->colors); fprintf(filedump, "%zd\n", n);