+#include <errno.h>
+
#include "game/camera.h"
#include "system/lt.h"
#include "system/stacktrace.h"
#include "color_picker.h"
#include "system/str.h"
#include "ui/edit_field.h"
+#include "undo_history.h"
+#include "game/level/action.h"
+#include "action_picker.h"
-#define RECT_LAYER_ID_MAX_SIZE 36
-#define RECT_LAYER_SELECTION_THICCNESS 5.0f
+#define RECT_LAYER_SELECTION_THICCNESS 10.0f
+#define RECT_LAYER_ID_LABEL_SIZE vec(3.0f, 3.0f)
#define CREATE_AREA_THRESHOLD 10.0
+#define RECT_LAYER_GRID_ROWS 3
+#define RECT_LAYER_GRID_COLUMNS 4
+
+static int clipboard = 0;
+static Rect clipboard_rect;
+static Color clipboard_color;
typedef enum {
RECT_LAYER_IDLE = 0,
RECT_LAYER_CREATE,
+ // TODO(#955): Rectangles in Level Editor have only one resize anchor to work with
RECT_LAYER_RESIZE,
RECT_LAYER_MOVE,
RECT_LAYER_ID_RENAME,
+ RECT_LAYER_RECOLOR
} RectLayerState;
struct RectLayer {
Dynarray *ids;
Dynarray *rects;
Dynarray *colors;
+ Dynarray *actions;
ColorPicker color_picker;
- Vec create_begin;
- Vec create_end;
+ ActionPicker action_picker;
+ Vec2f create_begin;
+ Vec2f create_end;
int selection;
- Vec move_anchor;
+ Vec2f move_anchor; // The mouse offset from the left-top
+ // corner of the rect during moving it
Edit_field *id_edit_field;
+ Color inter_color;
+ Rect inter_rect;
+ int id_name_counter;
+ const char *id_name_prefix;
+ Grid *grid;
};
-typedef int (*EventHandler)(RectLayer *layer, const SDL_Event *event, const Camera *camera);
+typedef enum {
+ UNDO_ADD,
+ UNDO_DELETE,
+ UNDO_UPDATE,
+ UNDO_SWAP
+} UndoType;
+
+// Delete, Update
+typedef struct {
+ UndoType type;
+ RectLayer *layer;
+ size_t index;
+ Rect rect;
+ Color color;
+ Action action;
+ char id[ENTITY_MAX_ID_SIZE];
+} UndoElementContext;
+
+// Add
+typedef struct {
+ UndoType type;
+ RectLayer *layer;
+ size_t index;
+} UndoAddContext;
+
+// Swap
+typedef struct {
+ UndoType type;
+ RectLayer *layer;
+ size_t index1;
+ size_t index2;
+} UndoSwapContext;
+
+typedef union {
+ UndoType type;
+ UndoAddContext add;
+ UndoElementContext element;
+ UndoSwapContext swap;
+} UndoContext;
+
+static
+UndoContext create_undo_add_context(RectLayer *layer, size_t index)
+{
+ trace_assert(layer);
+ trace_assert(index < dynarray_count(layer->rects));
+
+ UndoContext undo_context;
+ undo_context.add.type = UNDO_ADD;
+ undo_context.add.layer = layer;
+ undo_context.add.index = index;
+ return undo_context;
+}
+
+static
+UndoContext create_undo_element_context(RectLayer *layer)
+{
+ trace_assert(layer);
+ size_t index = (size_t) layer->selection;
+ trace_assert(index < dynarray_count(layer->rects));
+
+ UndoContext undo_context;
+ undo_context.element.layer = layer;
+ undo_context.element.index = index;
+ dynarray_copy_to(layer->rects, &undo_context.element.rect, index);
+ dynarray_copy_to(layer->colors, &undo_context.element.color, index);
+ dynarray_copy_to(layer->ids, undo_context.element.id, index);
+ dynarray_copy_to(layer->actions, &undo_context.element.action, index);
+ return undo_context;
+}
+
+static
+UndoContext create_undo_update_context(RectLayer *rect_layer)
+{
+ UndoContext undo_context = create_undo_element_context(rect_layer);
+ undo_context.type = UNDO_UPDATE;
+ return undo_context;
+}
+
+static
+UndoContext create_undo_delete_context(RectLayer *rect_layer)
+{
+ UndoContext undo_context = create_undo_element_context(rect_layer);
+ undo_context.type = UNDO_DELETE;
+ return undo_context;
+}
+
+static
+UndoContext create_undo_swap_context(RectLayer *rect_layer, size_t index1, size_t index2)
+{
+ UndoContext undo_context;
+ undo_context.swap.type = UNDO_SWAP;
+ undo_context.swap.layer = rect_layer;
+ undo_context.swap.index1 = index1;
+ undo_context.swap.index2 = index2;
+ return undo_context;
+}
+
+static
+void rect_layer_undo(void *context, size_t context_size)
+{
+ trace_assert(context);
+ trace_assert(sizeof(UndoContext) == context_size);
+
+ UndoContext *undo_context = context;
+
+ switch (undo_context->type) {
+ case UNDO_ADD: {
+ RectLayer *layer = undo_context->add.layer;
+ dynarray_delete_at(layer->rects, undo_context->add.index);
+ dynarray_delete_at(layer->colors, undo_context->add.index);
+ dynarray_delete_at(layer->ids, undo_context->add.index);
+ dynarray_delete_at(layer->actions, undo_context->add.index);
+ layer->selection = -1;
+ } break;
+
+ case UNDO_DELETE: {
+ RectLayer *layer = undo_context->element.layer;
+ dynarray_insert_before(layer->rects, undo_context->element.index, &undo_context->element.rect);
+ dynarray_insert_before(layer->colors, undo_context->element.index, &undo_context->element.color);
+ dynarray_insert_before(layer->ids, undo_context->element.index, &undo_context->element.id);
+ dynarray_insert_before(layer->actions, undo_context->element.index, &undo_context->element.action);
+ layer->selection = -1;
+ } break;
+
+ case UNDO_UPDATE: {
+ RectLayer *layer = undo_context->element.layer;
+ dynarray_replace_at(layer->rects, undo_context->element.index, &undo_context->element.rect);
+ dynarray_replace_at(layer->colors, undo_context->element.index, &undo_context->element.color);
+ dynarray_replace_at(layer->ids, undo_context->element.index, &undo_context->element.id);
+ dynarray_replace_at(layer->actions, undo_context->element.index, &undo_context->element.action);
+ } break;
-static int rect_layer_add_rect(RectLayer *layer, Rect rect, Color color)
+ case UNDO_SWAP: {
+ RectLayer *layer = undo_context->element.layer;
+ dynarray_swap(layer->rects, undo_context->swap.index1, undo_context->swap.index2);
+ dynarray_swap(layer->colors, undo_context->swap.index1, undo_context->swap.index2);
+ dynarray_swap(layer->ids, undo_context->swap.index1, undo_context->swap.index2);
+ dynarray_swap(layer->actions, undo_context->swap.index1, undo_context->swap.index2);
+ } break;
+ }
+}
+
+#define UNDO_PUSH(HISTORY, CONTEXT) \
+ do { \
+ UndoContext context = (CONTEXT); \
+ undo_history_push( \
+ HISTORY, \
+ rect_layer_undo, \
+ &context, \
+ sizeof(context)); \
+ } while(0)
+
+static int rect_layer_add_rect(RectLayer *layer,
+ Rect rect,
+ Color color,
+ UndoHistory *undo_history)
{
trace_assert(layer);
return -1;
}
- char id[RECT_LAYER_ID_MAX_SIZE];
- for (size_t i = 0; i < RECT_LAYER_ID_MAX_SIZE - 1; ++i) {
- id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
- }
- id[RECT_LAYER_ID_MAX_SIZE - 1] = '\0';
-
+ char id[ENTITY_MAX_ID_SIZE];
+ snprintf(id, ENTITY_MAX_ID_SIZE, "%s_%d",
+ layer->id_name_prefix,
+ layer->id_name_counter++);
if (dynarray_push(layer->ids, id)) {
return -1;
}
+ dynarray_push_empty(layer->actions);
+
+ UNDO_PUSH(
+ undo_history,
+ create_undo_add_context(
+ layer,
+ dynarray_count(layer->rects) - 1));
+
return 0;
}
-static int rect_layer_rect_at(RectLayer *layer, Vec position)
+static int rect_layer_rect_at(RectLayer *layer, Vec2f position)
{
trace_assert(layer);
- const size_t n = dynarray_count(layer->rects);
+ int n = (int) dynarray_count(layer->rects);
Rect *rects = dynarray_data(layer->rects);
- for (size_t i = 0; i < n; ++i) {
+ for (int i = n - 1; i >= 0; --i) {
if (rect_contains_point(rects[i], position)) {
return (int) i;
}
return -1;
}
-static Rect rect_layer_resize_anchor(const RectLayer *layer, size_t i)
+static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
+ UndoHistory *undo_history)
{
- Rect *rects = dynarray_data(layer->rects);
- return rect(rects[i].x + rects[i].w,
- rects[i].y + rects[i].h,
- RECT_LAYER_SELECTION_THICCNESS * 2.0f,
- RECT_LAYER_SELECTION_THICCNESS * 2.0f);
+ trace_assert(layer);
+ trace_assert(a < dynarray_count(layer->rects));
+ trace_assert(b < dynarray_count(layer->rects));
+
+ dynarray_swap(layer->rects, a, b);
+ dynarray_swap(layer->colors, a, b);
+ dynarray_swap(layer->ids, a, b);
+ dynarray_swap(layer->actions, a, b);
+
+ UNDO_PUSH(undo_history, create_undo_swap_context(layer, a, b));
+}
+
+static Rect rect_layer_resize_anchor(const Camera *camera, Rect boundary_rect)
+{
+ const Rect overlay_rect = camera_rect(camera, boundary_rect);
+ return rect(
+ overlay_rect.x + overlay_rect.w,
+ overlay_rect.y + overlay_rect.h,
+ RECT_LAYER_SELECTION_THICCNESS * 2.0,
+ RECT_LAYER_SELECTION_THICCNESS * 2.0);
}
-static int rect_layer_delete_rect_at(RectLayer *layer, size_t i)
+static int rect_layer_delete_rect_at(RectLayer *layer,
+ size_t i,
+ UndoHistory *undo_history)
{
trace_assert(layer);
+ UNDO_PUSH(undo_history, create_undo_delete_context(layer));
+
dynarray_delete_at(layer->rects, i);
dynarray_delete_at(layer->colors, i);
dynarray_delete_at(layer->ids, i);
+ dynarray_delete_at(layer->actions, i);
return 0;
}
-static int rect_layer_event_idle(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static int rect_layer_event_idle(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
trace_assert(camera);
+ int color_changed = 0;
+ if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
+ return -1;
+ }
+
+ if (color_changed) {
+ if (layer->selection >= 0) {
+ dynarray_copy_to(layer->colors, &layer->inter_color, (size_t)layer->selection);
+ layer->state = RECT_LAYER_RECOLOR;
+ }
+ return 0;
+ }
+
switch (event->type) {
case SDL_MOUSEBUTTONDOWN: {
switch (event->button.button) {
case SDL_BUTTON_LEFT: {
- Point position = camera_map_screen(
+ Vec2f position = camera_map_screen(
camera,
event->button.x,
event->button.y);
int rect_at_position =
rect_layer_rect_at(layer, position);
- if (rect_at_position >= 0) {
- Rect *rects = dynarray_data(layer->rects);
+ Rect *rects = dynarray_data(layer->rects);
+ Color *colors = dynarray_data(layer->colors);
+
+ if (layer->selection >= 0 && rect_contains_point(
+ rect_layer_resize_anchor(
+ camera,
+ rects[layer->selection]),
+ vec(
+ (float) event->button.x,
+ (float) event->button.y))) {
+ layer->state = RECT_LAYER_RESIZE;
+ dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) layer->selection);
+ } else if (rect_at_position >= 0) {
layer->selection = rect_at_position;
layer->state = RECT_LAYER_MOVE;
layer->move_anchor =
vec(
rects[layer->selection].x,
rects[layer->selection].y));
- } else if (layer->selection >= 0 && rect_contains_point(
- rect_layer_resize_anchor(
- layer,
- (size_t)layer->selection),
- position)) {
- layer->state = RECT_LAYER_RESIZE;
+ layer->color_picker =
+ create_color_picker_from_rgba(colors[rect_at_position]);
+
+ dynarray_copy_to(layer->rects, &layer->inter_rect, (size_t) rect_at_position);
} else {
layer->selection = rect_at_position;
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
+ case SDLK_UP: {
+ if ((event->key.keysym.mod & KMOD_SHIFT)
+ && (layer->selection >= 0)
+ && ((size_t)(layer->selection + 1) < dynarray_count(layer->rects))) {
+ rect_layer_swap_elements(
+ layer,
+ (size_t) layer->selection,
+ (size_t) layer->selection + 1,
+ undo_history);
+ layer->selection++;
+ }
+ } break;
+
+ case SDLK_DOWN: {
+ if ((event->key.keysym.mod & KMOD_SHIFT)
+ && (layer->selection > 0)
+ && ((size_t) layer->selection < dynarray_count(layer->rects))) {
+ rect_layer_swap_elements(
+ layer,
+ (size_t) layer->selection,
+ (size_t) layer->selection - 1,
+ undo_history);
+ layer->selection--;
+ }
+ } break;
+
case SDLK_DELETE: {
if (layer->selection >= 0) {
- rect_layer_delete_rect_at(layer, (size_t) layer->selection);
+ rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
layer->selection = -1;
}
} break;
case SDLK_F2: {
if (layer->selection >= 0) {
const char *ids = dynarray_data(layer->ids);
+ Color *colors = dynarray_data(layer->colors);
+
+ edit_field_restyle(
+ layer->id_edit_field,
+ RECT_LAYER_ID_LABEL_SIZE,
+ color_invert(colors[layer->selection]));
+
layer->state = RECT_LAYER_ID_RENAME;
edit_field_replace(
layer->id_edit_field,
- ids + layer->selection * RECT_LAYER_ID_MAX_SIZE);
+ ids + layer->selection * ENTITY_MAX_ID_SIZE);
SDL_StartTextInput();
}
} break;
+
+ case SDLK_c: {
+ if ((event->key.keysym.mod & KMOD_LCTRL) && layer->selection >= 0) {
+ clipboard = 1;
+ dynarray_copy_to(layer->rects, &clipboard_rect, (size_t)layer->selection);
+ dynarray_copy_to(layer->colors, &clipboard_color, (size_t)layer->selection);
+ }
+ } break;
+
+ case SDLK_v: {
+ if ((event->key.keysym.mod & KMOD_LCTRL) && clipboard) {
+ int x, y;
+ SDL_GetMouseState(&x, &y);
+ Vec2f position = camera_map_screen(camera, x, y);
+
+ rect_layer_add_rect(
+ layer,
+ rect(position.x, position.y,
+ clipboard_rect.w, clipboard_rect.h),
+ clipboard_color,
+ undo_history);
+ }
+ } break;
}
} break;
}
return 0;
}
-static int rect_layer_event_create(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static int rect_layer_event_create(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
rect_layer_add_rect(
layer,
real_rect,
- color_picker_rgba(&layer->color_picker));
+ color_picker_rgba(&layer->color_picker),
+ undo_history);
} else {
log_info("The area is too small %f. Such small box won't be created.\n", area);
}
return 0;
}
-static int rect_layer_event_resize(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static int rect_layer_event_resize(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
trace_assert(camera);
+ trace_assert(layer->selection >= 0);
switch (event->type) {
case SDL_MOUSEMOTION: {
- Rect *rects = dynarray_data(layer->rects);
- trace_assert(layer->selection >= 0);
- rects[layer->selection] = rect_from_points(
- vec(rects[layer->selection].x, rects[layer->selection].y),
+ layer->inter_rect = rect_from_points(
+ vec(layer->inter_rect.x, layer->inter_rect.y),
vec_sum(
camera_map_screen(
camera,
case SDL_MOUSEBUTTONUP: {
layer->state = RECT_LAYER_IDLE;
+ UNDO_PUSH(undo_history, create_undo_update_context(layer));
+ dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
} break;
}
return 0;
}
-static int rect_layer_event_move(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static int rect_layer_event_move(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
trace_assert(camera);
+ trace_assert(layer->selection >= 0);
+
+ Rect *rects = dynarray_data(layer->rects);
switch (event->type) {
case SDL_MOUSEMOTION: {
- Point position = vec_sub(
+ const Uint8 *state = SDL_GetKeyboardState(NULL);
+ const Vec2f mouse_pos = vec_sub(
camera_map_screen(
camera,
event->button.x,
event->button.y),
layer->move_anchor);
- Rect *rects = dynarray_data(layer->rects);
+ if (!(state[SDL_SCANCODE_LCTRL] || state[SDL_SCANCODE_RCTRL])) {
+ layer->inter_rect.x = mouse_pos.x;
+ layer->inter_rect.y = mouse_pos.y;
+ } else {
+ const Vec2f rect_pos = rect_position(rects[layer->selection]);
- trace_assert(layer->selection >= 0);
+ const float dx = fabsf(rect_pos.x - mouse_pos.x);
+ const float dy = fabsf(rect_pos.y - mouse_pos.y);
- rects[layer->selection].x = position.x;
- rects[layer->selection].y = position.y;
+ if (dx > dy) {
+ layer->inter_rect.x = mouse_pos.x;
+ layer->inter_rect.y = rect_pos.y;
+ } else {
+ layer->inter_rect.x = rect_pos.x;
+ layer->inter_rect.y = mouse_pos.y;
+ }
+ }
} break;
case SDL_MOUSEBUTTONUP: {
layer->state = RECT_LAYER_IDLE;
+
+ float distance = vec_length(
+ vec_sub(rect_position(layer->inter_rect),
+ rect_position(rects[layer->selection])));
+
+ if (distance > 1e-6) {
+ UNDO_PUSH(undo_history, create_undo_update_context(layer));
+ dynarray_replace_at(layer->rects, (size_t) layer->selection, &layer->inter_rect);
+ }
} break;
}
return 0;
}
-static int rect_layer_event_id_rename(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static int rect_layer_event_id_rename(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
trace_assert(camera);
+ trace_assert(layer->selection >= 0);
switch (event->type) {
- case SDL_TEXTINPUT: {
- if (edit_field_text_input(layer->id_edit_field, &event->text) < 0) {
- return -1;
- }
- } break;
-
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
case SDLK_RETURN: {
- char *id =
- (char *)dynarray_data(layer->ids) + layer->selection * RECT_LAYER_ID_MAX_SIZE;
- memset(id, 0, RECT_LAYER_ID_MAX_SIZE);
- memcpy(id, edit_field_as_text(layer->id_edit_field), RECT_LAYER_ID_MAX_SIZE - 1);
+ UNDO_PUSH(undo_history, create_undo_update_context(layer));
+
+ char *id = dynarray_pointer_at(layer->ids, (size_t)layer->selection);
+ memset(id, 0, ENTITY_MAX_ID_SIZE);
+ memcpy(id, edit_field_as_text(layer->id_edit_field), ENTITY_MAX_ID_SIZE - 1);
layer->state = RECT_LAYER_IDLE;
+ SDL_StopTextInput();
} break;
- }
- if (edit_field_keyboard(layer->id_edit_field, &event->key) < 0) {
- return -1;
- }
- } break;
-
- case SDL_KEYUP: {
- if (edit_field_keyboard(layer->id_edit_field, &event->key) < 0) {
- return -1;
+ case SDLK_ESCAPE: {
+ layer->state = RECT_LAYER_IDLE;
+ SDL_StopTextInput();
+ } break;
}
} break;
}
- return 0;
+
+ return edit_field_event(layer->id_edit_field, event);
}
LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
return layer;
}
-RectLayer *create_rect_layer(void)
+RectLayer *create_rect_layer(const char *id_name_prefix)
{
Lt *lt = create_lt();
layer->ids = PUSH_LT(
lt,
- create_dynarray(sizeof(char) * RECT_LAYER_ID_MAX_SIZE),
+ create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE),
destroy_dynarray);
if (layer->ids == NULL) {
RETURN_LT(lt, NULL);
RETURN_LT(lt, NULL);
}
+ layer->actions = PUSH_LT(
+ lt,
+ create_dynarray(sizeof(Action)),
+ destroy_dynarray);
+ if (layer->actions == NULL) {
+ RETURN_LT(lt, NULL);
+ }
+
layer->id_edit_field = PUSH_LT(
lt,
create_edit_field(
- vec(3.0f, 3.0f),
+ RECT_LAYER_ID_LABEL_SIZE,
COLOR_BLACK),
destroy_edit_field);
if (layer->id_edit_field == NULL) {
RETURN_LT(lt, NULL);
}
+ layer->grid =
+ PUSH_LT(
+ lt,
+ nth_calloc(
+ 1,
+ sizeof(Grid) + sizeof(Widget*) * RECT_LAYER_GRID_ROWS * RECT_LAYER_GRID_COLUMNS),
+ free);
+ if (layer->grid == NULL) {
+ RETURN_LT(lt, NULL);
+ }
+ layer->grid->rows = RECT_LAYER_GRID_ROWS;
+ layer->grid->columns = RECT_LAYER_GRID_COLUMNS;
+ grid_put_widget(layer->grid, &layer->action_picker.widget, 0, RECT_LAYER_GRID_COLUMNS - 1);
+
layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
layer->selection = -1;
+ layer->id_name_prefix = id_name_prefix;
return layer;
}
-RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream)
+RectLayer *create_rect_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix)
{
trace_assert(line_stream);
- RectLayer *layer = create_rect_layer();
+ RectLayer *layer = create_rect_layer(id_name_prefix);
if (layer == NULL) {
return NULL;
}
char hex[7];
Rect rect;
- char id[RECT_LAYER_ID_MAX_SIZE];
+ char id[ENTITY_MAX_ID_SIZE];
+ int n = 0;
if (sscanf(line,
- "%"STRINGIFY(RECT_LAYER_ID_MAX_SIZE)"s%f%f%f%f%6s\n",
+ "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s%f%f%f%f%6s%n",
id,
&rect.x, &rect.y,
&rect.w, &rect.h,
- hex) < 0) {
+ hex, &n) <= 0) {
+ log_fail("%s\n", strerror(errno));
RETURN_LT(layer->lt, NULL);
}
+ line += n;
Color color = hexstr(hex);
-
dynarray_push(layer->rects, &rect);
dynarray_push(layer->ids, id);
dynarray_push(layer->colors, &color);
+
+ Action action = {0};
+
+ if (sscanf(line, "%d%n", (int*)&action.type, &n) > 0) {
+ line += n;
+ switch (action.type) {
+ case ACTION_NONE: break;
+
+ case ACTION_TOGGLE_GOAL:
+ case ACTION_HIDE_LABEL: {
+ if (sscanf(line, "%"STRINGIFY(ENTITY_MAX_ID_SIZE)"s", action.entity_id) <= 0) {
+ log_fail("%s\n", strerror(errno));
+ RETURN_LT(layer->lt, NULL);
+ }
+ } break;
+
+ case ACTION_N: break;
+ }
+ }
+
+ dynarray_push(layer->actions, &action);
}
return layer;
RETURN_LT0(layer->lt);
}
-int rect_layer_render(const RectLayer *layer, Camera *camera, int active)
+int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
{
trace_assert(layer);
trace_assert(camera);
Color *colors = dynarray_data(layer->colors);
const char *ids = dynarray_data(layer->ids);
+ // The Rectangles
for (size_t i = 0; i < n; ++i) {
- if (layer->selection == (int) i) {
- if (active) {
- const Color color = color_invert(colors[i]);
+ Rect rect = rects[i];
+ Color color = colors[i];
- if (camera_fill_rect(
- camera,
- rect_scale(rects[i], RECT_LAYER_SELECTION_THICCNESS),
- color) < 0) {
- return -1;
- }
+ if (layer->selection == (int) i) {
+ if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
+ rect = layer->inter_rect;
+ }
- if (camera_fill_rect(
- camera,
- rect_layer_resize_anchor(layer, i),
- color) < 0) {
- return -1;
- }
+ if (layer->state == RECT_LAYER_RECOLOR) {
+ color = layer->inter_color;
}
}
+ // Main Rectangle
if (camera_fill_rect(
camera,
- rects[i],
+ rect,
color_scale(
- colors[i],
+ color,
rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f))) < 0) {
return -1;
}
+ }
+
+ // Selection Overlay
+ if (active && layer->selection >= 0) {
+ Rect rect = rects[layer->selection];
+ Color color = colors[layer->selection];
+
+ if (layer->state == RECT_LAYER_RESIZE || layer->state == RECT_LAYER_MOVE) {
+ rect = layer->inter_rect;
+ }
+
+ if (layer->state == RECT_LAYER_RECOLOR) {
+ color = layer->inter_color;
+ }
+
+ const Rect overlay_rect =
+ rect_pad(
+ camera_rect(camera, rect),
+ -RECT_LAYER_SELECTION_THICCNESS * 0.5f);
+ const Color overlay_color = color_invert(color);
- if (camera_render_text(
+ // Selection
+ if (camera_draw_thicc_rect_screen(
camera,
- ids + i * RECT_LAYER_ID_MAX_SIZE,
- vec(3.0f, 3.0f),
- color_invert(colors[i]),
- rect_position(rects[i])) < 0) {
+ overlay_rect,
+ overlay_color,
+ RECT_LAYER_SELECTION_THICCNESS) < 0) {
return -1;
}
- }
- const Color color = color_picker_rgba(&layer->color_picker);
- if (layer->state == RECT_LAYER_CREATE) {
- if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
+ const Vec2f rect_id_pos = vec_sub(
+ rect_position(rect),
+ vec_mult(
+ RECT_LAYER_ID_LABEL_SIZE,
+ vec(0.0f, FONT_CHAR_HEIGHT)));
+
+ // Rectangle Id
+ if (layer->state == RECT_LAYER_ID_RENAME) {
+ // ID renaming Edit Field
+ if (edit_field_render_world(
+ layer->id_edit_field,
+ camera,
+ rect_id_pos) < 0) {
+ return -1;
+ }
+ } else {
+ // Id text
+ if (camera_render_text(
+ camera,
+ ids + layer->selection * ENTITY_MAX_ID_SIZE,
+ RECT_LAYER_ID_LABEL_SIZE,
+ color_invert(color),
+ rect_id_pos) < 0) {
+ return -1;
+ }
+ }
+
+ // Resize Anchor
+ if (camera_fill_rect_screen(
+ camera,
+ rect_layer_resize_anchor(camera, rect),
+ overlay_color) < 0) {
return -1;
}
}
- if (layer->state == RECT_LAYER_ID_RENAME) {
- if (edit_field_render(layer->id_edit_field, camera, vec(400.0f, 400.0f)) < 0) {
+ // Proto Rectangle
+ const Color color = color_picker_rgba(&layer->color_picker);
+ if (layer->state == RECT_LAYER_CREATE) {
+ if (camera_fill_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
return -1;
}
}
return 0;
}
-int rect_layer_event(RectLayer *layer, const SDL_Event *event, const Camera *camera)
+static
+int rect_layer_event_recolor(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(layer);
trace_assert(event);
+ trace_assert(camera);
+ trace_assert(undo_history);
+ trace_assert(layer->selection >= 0);
- int selected = 0;
- if (color_picker_event(&layer->color_picker, event, &selected) < 0) {
+ int color_changed = 0;
+ if (color_picker_event(&layer->color_picker, event, camera, &color_changed) < 0) {
return -1;
}
- if (selected) {
- return 0;
+ if (color_changed) {
+ layer->inter_color = color_picker_rgba(&layer->color_picker);
+
+ if (!color_picker_drag(&layer->color_picker)) {
+ UNDO_PUSH(undo_history, create_undo_update_context(layer));
+ dynarray_replace_at(layer->colors, (size_t) layer->selection, &layer->inter_color);
+ layer->state = RECT_LAYER_IDLE;
+ }
+ }
+
+ return 0;
+}
+
+int rect_layer_event(RectLayer *layer,
+ const SDL_Event *event,
+ const Camera *camera,
+ UndoHistory *undo_history)
+{
+ trace_assert(layer);
+ trace_assert(event);
+ trace_assert(undo_history);
+
+ switch (event->type) {
+ case SDL_WINDOWEVENT: {
+ switch (event->window.event) {
+ case SDL_WINDOWEVENT_RESIZED: {
+ grid_relayout(layer->grid, rect(0.0f, 0.0f,
+ (float) event->window.data1,
+ (float) event->window.data2));
+ } break;
+ }
+ } break;
}
switch (layer->state) {
case RECT_LAYER_IDLE:
- return rect_layer_event_idle(layer, event, camera);
+ return rect_layer_event_idle(layer, event, camera, undo_history);
case RECT_LAYER_CREATE:
- return rect_layer_event_create(layer, event, camera);
+ return rect_layer_event_create(layer, event, camera, undo_history);
case RECT_LAYER_RESIZE:
- return rect_layer_event_resize(layer, event, camera);
+ return rect_layer_event_resize(layer, event, camera, undo_history);
case RECT_LAYER_MOVE:
- return rect_layer_event_move(layer, event, camera);
+ return rect_layer_event_move(layer, event, camera, undo_history);
case RECT_LAYER_ID_RENAME:
- return rect_layer_event_id_rename(layer, event, camera);
+ return rect_layer_event_id_rename(layer, event, camera, undo_history);
+
+ case RECT_LAYER_RECOLOR:
+ return rect_layer_event_recolor(layer, event, camera, undo_history);
}
return 0;
char *ids = dynarray_data(layer->ids);
Rect *rects = dynarray_data(layer->rects);
Color *colors = dynarray_data(layer->colors);
+ Action *actions = dynarray_data(layer->actions);
fprintf(filedump, "%zd\n", n);
for (size_t i = 0; i < n; ++i) {
fprintf(filedump, "%s %f %f %f %f ",
- ids + RECT_LAYER_ID_MAX_SIZE * i,
+ ids + ENTITY_MAX_ID_SIZE * i,
rects[i].x, rects[i].y, rects[i].w, rects[i].h);
color_hex_to_stream(colors[i], filedump);
+
+ switch (actions[i].type) {
+ case ACTION_NONE: {} break;
+
+ case ACTION_TOGGLE_GOAL:
+ case ACTION_HIDE_LABEL: {
+ fprintf(filedump, " %d %.*s",
+ (int)actions[i].type,
+ ENTITY_MAX_ID_SIZE, actions[i].entity_id);
+ } break;
+ case ACTION_N: break;
+ }
+
fprintf(filedump, "\n");
}
return 0;
}
+
+const Action *rect_layer_actions(const RectLayer *layer)
+{
+ return dynarray_data(layer->actions);
+}