X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fgame%2Flevel%2Flevel_editor%2Fpoint_layer.c;h=50f6d77535ddb40a7145cc78f94bf0babba691a2;hb=b677738fc607173746b2ce5ba1888ecf7c79ef55;hp=ea8a37a6f97d60bc5726a174372f2ef2f83e6b7e;hpb=637df4bca99b0fede006cd423e17be85610921d0;p=nothing.git diff --git a/src/game/level/level_editor/point_layer.c b/src/game/level/level_editor/point_layer.c index ea8a37a6..50f6d775 100644 --- a/src/game/level/level_editor/point_layer.c +++ b/src/game/level/level_editor/point_layer.c @@ -13,27 +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 + +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_EDIT_ID, + POINT_LAYER_MOVE, + POINT_LAYER_RECOLOR } PointLayerState; struct PointLayer { Lt *lt; PointLayerState state; - Dynarray/**/ *points; + 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 = { @@ -43,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(); @@ -55,8 +178,8 @@ PointLayer *create_point_layer(void) point_layer->state = POINT_LAYER_IDLE; - point_layer->points = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray); - if (point_layer->points == NULL) { + point_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Vec2f)), destroy_dynarray); + if (point_layer->positions == NULL) { RETURN_LT(lt, NULL); } @@ -73,21 +196,24 @@ PointLayer *create_point_layer(void) point_layer->edit_field = PUSH_LT( lt, create_edit_field( - vec(5.0f, 5.0f), - rgba(0.0f, 0.0f, 0.0f, 1.0f)), + POINT_LAYER_ID_TEXT_SIZE, + POINT_LAYER_ID_TEXT_COLOR), destroy_edit_field); if (point_layer->edit_field == NULL) { 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( @@ -110,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->points, &point); + 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; } @@ -130,53 +256,77 @@ 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->points); - Point *points = dynarray_data(point_layer->points); + const int n = (int) dynarray_count(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(points[i].x, points[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(points[i].x, points[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]; + + // 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; + } - if (camera_fill_triangle(camera, t0, color_invert(color)) < 0) { + if (point_layer->state != POINT_LAYER_EDIT_ID && + camera_render_text( + camera, + ids + ID_MAX_SIZE * i, + POINT_LAYER_ID_TEXT_SIZE, + POINT_LAYER_ID_TEXT_COLOR, + 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; } - - /* TODO(#854): The ids of PointLayer are not displayed constantly */ } if (point_layer->state == POINT_LAYER_EDIT_ID) { - /* TODO(#855): PointLayer edit field is not scaled on zoom */ - if (edit_field_render_screen( + if (edit_field_render_world( point_layer->edit_field, camera, - camera_point(camera, points[point_layer->selected])) < 0) { + positions[point_layer->selection]) < 0) { return -1; } } @@ -189,10 +339,91 @@ int point_layer_render(const PointLayer *point_layer, return 0; } +static +int point_layer_element_at(const PointLayer *point_layer, + Vec2f position) +{ + trace_assert(point_layer); + + int n = (int) dynarray_count(point_layer->positions); + Vec2f *positions = dynarray_data(point_layer->positions); + + for (int i = n - 1; i >= 0; --i) { + if (vec_length(vec_sub(positions[i], position)) < POINT_LAYER_ELEMENT_RADIUS) { + return i; + } + } + + return -1; +} + +static +int point_layer_add_element(PointLayer *point_layer, + Vec2f position, + Color color, + UndoHistory *undo_history) +{ + trace_assert(point_layer); + trace_assert(undo_history); + + char id[ID_MAX_SIZE]; + 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, + 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); +} + 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); @@ -202,11 +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->selection >= 0) { + point_layer->inter_color = color_picker_rgba(&point_layer->color_picker); + point_layer->state = POINT_LAYER_RECOLOR; + } return 0; } @@ -214,53 +450,99 @@ int point_layer_idle_event(PointLayer *point_layer, case SDL_MOUSEBUTTONDOWN: { switch (event->button.button) { case SDL_BUTTON_LEFT: { - const int n = (int) dynarray_count(point_layer->points); - const Point *points = dynarray_data(point_layer->points); - const Point point = camera_map_screen(camera, event->button.x, event->button.y); - const Color color = color_picker_rgba(&point_layer->color_picker); - - for (int i = 0; i < n; ++i) { - if (vec_length(vec_sub(points[i], point)) < POINT_LAYER_ELEMENT_RADIUS) { - point_layer->selected = i; - return 0; - } - } - - char id[ID_MAX_SIZE]; - - for (size_t i = 0; i < ID_MAX_SIZE - 1; ++i) { - id[i] = (char) ('a' + rand() % ('z' - 'a' + 1)); + const Vec2f position = camera_map_screen(camera, event->button.x, event->button.y); + + point_layer->selection = point_layer_element_at( + point_layer, position); + + if (point_layer->selection < 0) { + point_layer_add_element( + 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->selection]); + point_layer->inter_position = positions[point_layer->selection]; } - id[ID_MAX_SIZE - 1] = '\0'; - - dynarray_push(point_layer->points, &point); - dynarray_push(point_layer->colors, &color); - dynarray_push(point_layer->ids, id); } break; } } break; 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->points)) { - dynarray_delete_at(point_layer->points, (size_t) point_layer->selected); - dynarray_delete_at(point_layer->colors, (size_t) point_layer->selected); - dynarray_delete_at(point_layer->ids, (size_t) point_layer->selected); + 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; } - point_layer->selected = -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; } @@ -271,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); @@ -281,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; @@ -303,20 +593,134 @@ int point_layer_edit_id_event(PointLayer *point_layer, return edit_field_event(point_layer->edit_field, event); } +static +int point_layer_move_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(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: { + 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, undo_history); + + case POINT_LAYER_RECOLOR: + return point_layer_recolor_event(point_layer, event, camera, undo_history); } return 0; @@ -325,13 +729,13 @@ int point_layer_event(PointLayer *point_layer, size_t point_layer_count(const PointLayer *point_layer) { trace_assert(point_layer); - return dynarray_count(point_layer->points); + return dynarray_count(point_layer->positions); } -const Point *point_layer_points(const PointLayer *point_layer) +const Vec2f *point_layer_positions(const PointLayer *point_layer) { trace_assert(point_layer); - return dynarray_data(point_layer->points); + return dynarray_data(point_layer->positions); } const Color *point_layer_colors(const PointLayer *point_layer) @@ -354,14 +758,14 @@ 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 *points = dynarray_data(point_layer->points); + Vec2f *positions = dynarray_data(point_layer->positions); Color *colors = dynarray_data(point_layer->colors); fprintf(filedump, "%zd\n", n); for (size_t i = 0; i < n; ++i) { fprintf(filedump, "%s %f %f ", ids + ID_MAX_SIZE * i, - points[i].x, points[i].y); + positions[i].x, positions[i].y); color_hex_to_stream(colors[i], filedump); fprintf(filedump, "\n"); }