#include "system/lt.h"
#include "system/str.h"
#include "system/log.h"
-#include "math/point.h"
+#include "math/vec.h"
#include "label_layer.h"
#include "dynarray.h"
#include "color.h"
#include "game/camera.h"
#include "color_picker.h"
#include "ui/edit_field.h"
+#include "math/extrema.h"
#define LABEL_LAYER_SELECTION_THICCNESS 5.0f
-// TODO(#999): LabelLayer does not support UndoHistory
typedef enum {
LABEL_LAYER_IDLE = 0,
LABEL_LAYER_RECOLOR
} LabelLayerState;
+static int clipboard;
+static char clipboard_text[LABEL_LAYER_TEXT_MAX_SIZE];
+static Color clipboard_color;
+
struct LabelLayer {
Lt *lt;
LabelLayerState state;
Dynarray *positions;
Dynarray *colors;
Dynarray *texts;
- int selected;
+ int selection;
ColorPicker color_picker;
- Point move_anchor;
+ Vec2f move_anchor;
Edit_field *edit_field;
- Point inter_position;
+ Vec2f inter_position;
Color inter_color;
+ int id_name_counter;
+ const char *id_name_prefix;
};
typedef enum {
UNDO_ADD,
UNDO_DELETE,
- UNDO_UPDATE
+ UNDO_UPDATE,
+ UNDO_SWAP
} UndoType;
typedef struct {
UndoType type;
+ LabelLayer *layer;
char id[LABEL_LAYER_ID_MAX_SIZE];
- Point position;
+ Vec2f position;
Color color;
char text[LABEL_LAYER_TEXT_MAX_SIZE];
size_t index;
+ size_t index2;
} UndoContext;
static
-UndoContext create_undo_context(LabelLayer *label_layer, size_t index, UndoType type)
+UndoContext create_undo_swap_context(LabelLayer *label_layer,
+ size_t index, size_t index2)
{
+ trace_assert(label_layer);
+ trace_assert(index < dynarray_count(label_layer->positions));
+ trace_assert(index2 < dynarray_count(label_layer->positions));
+
UndoContext undo_context;
+ undo_context.type = UNDO_SWAP;
+ undo_context.layer = label_layer;
+ undo_context.index = index;
+ undo_context.index2 = index2;
+ return undo_context;
+}
+
+static
+UndoContext create_undo_context(LabelLayer *label_layer, UndoType type)
+{
+ trace_assert(label_layer);
+ trace_assert(type != UNDO_SWAP);
+
+ UndoContext undo_context;
+
+ size_t index = type == UNDO_ADD
+ ? dynarray_count(label_layer->positions) - 1
+ : (size_t)label_layer->selection;
undo_context.type = type;
+ undo_context.layer = label_layer;
dynarray_copy_to(label_layer->ids, &undo_context.id, index);
dynarray_copy_to(label_layer->positions, &undo_context.position, index);
dynarray_copy_to(label_layer->colors, &undo_context.color, index);
}
static
-void label_layer_undo(void *layer, void *context, size_t context_size)
+void label_layer_undo(void *context, size_t context_size)
{
- trace_assert(layer);
trace_assert(context);
trace_assert(sizeof(UndoContext) == context_size);
- LabelLayer *label_layer = layer;
UndoContext *undo_context = context;
+ LabelLayer *label_layer = undo_context->layer;
switch (undo_context->type) {
case UNDO_ADD: {
dynarray_replace_at(label_layer->colors, undo_context->index, &undo_context->color);
dynarray_replace_at(label_layer->texts, undo_context->index, &undo_context->text);
} break;
+
+ case UNDO_SWAP: {
+ dynarray_swap(label_layer->ids, undo_context->index, undo_context->index2);
+ dynarray_swap(label_layer->positions, undo_context->index, undo_context->index2);
+ dynarray_swap(label_layer->colors, undo_context->index, undo_context->index2);
+ dynarray_swap(label_layer->texts, undo_context->index, undo_context->index2);
+ } break;
}
}
+#define UNDO_PUSH(HISTORY, CONTEXT) \
+ do { \
+ UndoContext context = (CONTEXT); \
+ undo_history_push( \
+ HISTORY, \
+ label_layer_undo, \
+ &context, \
+ sizeof(context)); \
+ } while(0)
+
+
LayerPtr label_layer_as_layer(LabelLayer *label_layer)
{
LayerPtr layer = {
return layer;
}
-LabelLayer *create_label_layer(void)
+LabelLayer *create_label_layer(const char *id_name_prefix)
{
Lt *lt = create_lt();
RETURN_LT(lt, NULL);
}
- label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray);
+ label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Vec2f)), destroy_dynarray);
if (label_layer->positions == NULL) {
RETURN_LT(lt, NULL);
}
}
label_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
- label_layer->selected = -1;
+ label_layer->selection = -1;
label_layer->edit_field = PUSH_LT(
lt,
RETURN_LT(lt, NULL);
}
+ label_layer->id_name_prefix = id_name_prefix;
+
return label_layer;
}
-LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
+LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream, const char *id_name_prefix)
{
trace_assert(line_stream);
- LabelLayer *label_layer = create_label_layer();
+ LabelLayer *label_layer = create_label_layer(id_name_prefix);
if (label_layer == NULL) {
RETURN_LT(label_layer->lt, NULL);
for (size_t i = 0; i < n; ++i) {
char hex[7];
char id[LABEL_LAYER_ID_MAX_SIZE];
- Point position;
+ Vec2f position;
line = line_stream_next(line_stream);
if (line == NULL) {
}
int label_layer_render(const LabelLayer *label_layer,
- Camera *camera,
+ const Camera *camera,
int active)
{
trace_assert(label_layer);
size_t n = dynarray_count(label_layer->ids);
char *ids = dynarray_data(label_layer->ids);
- Point *positions = dynarray_data(label_layer->positions);
+ Vec2f *positions = dynarray_data(label_layer->positions);
Color *colors = dynarray_data(label_layer->colors);
char *texts = dynarray_data(label_layer->texts);
/* TODO(#891): LabelLayer doesn't show the final position of Label after the animation */
for (size_t i = 0; i < n; ++i) {
- const Color color = label_layer->state == LABEL_LAYER_RECOLOR && label_layer->selected == (int) i
+ const Color color = label_layer->state == LABEL_LAYER_RECOLOR && label_layer->selection == (int) i
? label_layer->inter_color
: colors[i];
- const Point position =
- label_layer->state == LABEL_LAYER_MOVE && label_layer->selected == (int) i
+ const Vec2f position =
+ label_layer->state == LABEL_LAYER_MOVE && label_layer->selection == (int) i
? label_layer->inter_position
: positions[i];
// Label Text
- if (label_layer->state == LABEL_LAYER_EDIT_TEXT && label_layer->selected == (int) i) {
+ if (label_layer->state == LABEL_LAYER_EDIT_TEXT && label_layer->selection == (int) i) {
if (edit_field_render_world(
label_layer->edit_field,
camera,
}
// Label ID
- if (label_layer->state == LABEL_LAYER_EDIT_ID && label_layer->selected == (int)i) {
+ if (label_layer->state == LABEL_LAYER_EDIT_ID && label_layer->selection == (int)i) {
if (edit_field_render_world(
label_layer->edit_field,
camera,
}
// Label Selection
- if (label_layer->selected == (int) i) {
+ if (active && label_layer->selection == (int) i) {
Rect selection =
rect_scale(
camera_rect(
camera_font(camera),
position,
LABELS_SIZE,
- texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE),
+ texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE),
sprite_font_boundary_box(
camera_font(camera),
vec_sub(
position,
vec(0.0f, FONT_CHAR_HEIGHT)),
vec(1.0f, 1.0f),
- ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE))),
+ ids + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE))),
LABEL_LAYER_SELECTION_THICCNESS * 0.5f);
static
int label_layer_element_at(LabelLayer *label_layer,
const Sprite_font *font,
- Point position)
+ Vec2f position)
{
trace_assert(label_layer);
- const size_t n = dynarray_count(label_layer->texts);
+ const int n = (int) dynarray_count(label_layer->texts);
char *ids = dynarray_data(label_layer->ids);
char *texts = dynarray_data(label_layer->texts);
- Point *positions = dynarray_data(label_layer->positions);
+ Vec2f *positions = dynarray_data(label_layer->positions);
- for (size_t i = 0; i < n; ++i) {
+ for (int i = n - 1; i >= 0; --i) {
Rect boundary = rect_boundary2(
sprite_font_boundary_box(
font,
ids + i * LABEL_LAYER_ID_MAX_SIZE));
if (rect_contains_point(boundary, position)) {
- return (int) i;
+ return i;
}
}
}
static
-void label_layer_delete_nth_label(LabelLayer *label_layer,
- size_t i,
- UndoHistory *undo_history)
+void label_layer_delete_selected_label(LabelLayer *label_layer,
+ UndoHistory *undo_history)
{
trace_assert(label_layer);
+ trace_assert(label_layer->selection >= 0);
+
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_DELETE));
+
+ dynarray_delete_at(label_layer->ids, (size_t)label_layer->selection);
+ dynarray_delete_at(label_layer->positions, (size_t)label_layer->selection);
+ dynarray_delete_at(label_layer->colors, (size_t)label_layer->selection);
+ dynarray_delete_at(label_layer->texts, (size_t)label_layer->selection);
- UndoContext context = create_undo_context(label_layer, i, UNDO_DELETE);
- undo_history_push(
- undo_history,
- label_layer,
- label_layer_undo,
- &context,
- sizeof(context));
-
- dynarray_delete_at(label_layer->ids, i);
- dynarray_delete_at(label_layer->positions, i);
- dynarray_delete_at(label_layer->colors, i);
- dynarray_delete_at(label_layer->texts, i);
+ label_layer->selection = -1;
}
static
int label_layer_add_label(LabelLayer *label_layer,
- Point position,
+ Vec2f position,
Color color,
+ const char *text,
UndoHistory *undo_history)
{
trace_assert(label_layer);
// TODO(#982): id generation code is duplicated in label_layer, point_layer and rect_layer
char id[LABEL_LAYER_ID_MAX_SIZE];
- for (size_t i = 0; i < LABEL_LAYER_ID_MAX_SIZE - 1; ++i) {
- id[i] = (char) ('a' + rand() % ('z' - 'a' + 1));
- }
- id[LABEL_LAYER_ID_MAX_SIZE - 1] = '\0';
+ snprintf(id, LABEL_LAYER_ID_MAX_SIZE, "%s_%d",
+ label_layer->id_name_prefix,
+ label_layer->id_name_counter++);
size_t n = dynarray_count(label_layer->ids);
dynarray_push(label_layer->positions, &position);
dynarray_push(label_layer->colors, &color);
dynarray_push_empty(label_layer->texts);
+ memcpy(
+ dynarray_pointer_at(label_layer->texts, n),
+ text,
+ min_size_t(LABEL_LAYER_ID_MAX_SIZE - 1, strlen(text)));
- UndoContext context = create_undo_context(label_layer, n, UNDO_ADD);
- undo_history_push(
- undo_history,
- label_layer,
- label_layer_undo,
- &context,
- sizeof(context));
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_ADD));
return (int) n;
}
+static
+void label_layer_swap_elements(LabelLayer *label_layer,
+ size_t a, size_t b,
+ UndoHistory *undo_history)
+{
+ trace_assert(label_layer);
+ trace_assert(undo_history);
+ trace_assert(a < dynarray_count(label_layer->positions));
+ trace_assert(b < dynarray_count(label_layer->positions));
+
+ dynarray_swap(label_layer->ids, a, b);
+ dynarray_swap(label_layer->positions, a, b);
+ dynarray_swap(label_layer->colors, a, b);
+ dynarray_swap(label_layer->texts, a, b);
+
+ UNDO_PUSH(undo_history, create_undo_swap_context(label_layer, a, b));
+}
+
static
int label_layer_idle_event(LabelLayer *label_layer,
const SDL_Event *event,
trace_assert(event);
trace_assert(camera);
- int changed = 0;
+ int changed = 0;
if (color_picker_event(
&label_layer->color_picker,
event,
}
if (changed) {
- label_layer->state = LABEL_LAYER_RECOLOR;
- label_layer->inter_color = color_picker_rgba(&label_layer->color_picker);
+ if (label_layer->selection >= 0) {
+ label_layer->state = LABEL_LAYER_RECOLOR;
+ label_layer->inter_color = color_picker_rgba(&label_layer->color_picker);
+ }
return 0;
}
Color *colors = dynarray_data(label_layer->colors);
- Point *positions = dynarray_data(label_layer->positions);
+ Vec2f *positions = dynarray_data(label_layer->positions);
char *ids = dynarray_data(label_layer->ids);
char *texts = dynarray_data(label_layer->texts);
case SDL_MOUSEBUTTONDOWN: {
switch (event->button.button) {
case SDL_BUTTON_LEFT: {
- const Point position = camera_map_screen(
+ const Vec2f position = camera_map_screen(
camera,
event->button.x,
event->button.y);
if (element >= 0) {
label_layer->move_anchor = vec_sub(position, positions[element]);
- label_layer->selected = element;
+ label_layer->selection = element;
label_layer->state = LABEL_LAYER_MOVE;
label_layer->inter_position = positions[element];
label_layer->color_picker =
create_color_picker_from_rgba(colors[element]);
} else {
- label_layer->selected = label_layer_add_label(
+ label_layer->selection = label_layer_add_label(
label_layer,
position,
color_picker_rgba(
&label_layer->color_picker),
+ "",
undo_history);
label_layer->state = LABEL_LAYER_EDIT_TEXT;
edit_field_replace(
label_layer->edit_field,
- texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
+ texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE);
edit_field_restyle(
label_layer->edit_field,
LABELS_SIZE,
- colors[label_layer->selected]);
+ colors[label_layer->selection]);
SDL_StartTextInput();
}
} break;
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
+ case SDLK_UP: {
+ if ((event->key.keysym.mod & KMOD_SHIFT)
+ && (label_layer->selection >= 0)
+ && ((size_t)(label_layer->selection + 1) < dynarray_count(label_layer->positions))) {
+ label_layer_swap_elements(
+ label_layer,
+ (size_t) label_layer->selection,
+ (size_t) label_layer->selection + 1,
+ undo_history);
+ label_layer->selection++;
+ }
+ } break;
+
+ case SDLK_DOWN: {
+ if ((event->key.keysym.mod & KMOD_SHIFT)
+ && (label_layer->selection > 0)
+ && ((size_t) label_layer->selection < dynarray_count(label_layer->positions))) {
+ label_layer_swap_elements(
+ label_layer,
+ (size_t) label_layer->selection,
+ (size_t) label_layer->selection - 1,
+ undo_history);
+ label_layer->selection--;
+ }
+ } break;
+
case SDLK_F2: {
- if (label_layer->selected >= 0) {
+ if (label_layer->selection >= 0) {
label_layer->state = LABEL_LAYER_EDIT_TEXT;
edit_field_replace(
label_layer->edit_field,
- texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
+ texts + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE);
edit_field_restyle(
label_layer->edit_field,
LABELS_SIZE,
- colors[label_layer->selected]);
+ colors[label_layer->selection]);
SDL_StartTextInput();
}
} break;
case SDLK_F3: {
- if (label_layer->selected >= 0) {
+ if (label_layer->selection >= 0) {
label_layer->state = LABEL_LAYER_EDIT_ID;
edit_field_replace(
label_layer->edit_field,
- ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE);
+ ids + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE);
edit_field_restyle(
label_layer->edit_field,
vec(1.0f, 1.0f),
- color_invert(colors[label_layer->selected]));
+ color_invert(colors[label_layer->selection]));
SDL_StartTextInput();
}
} break;
case SDLK_DELETE: {
- if (label_layer->selected >= 0) {
- label_layer_delete_nth_label(
+ if (label_layer->selection >= 0) {
+ label_layer_delete_selected_label(
+ label_layer,
+ undo_history);
+ label_layer->selection = -1;
+ }
+ } break;
+
+ case SDLK_c: {
+ if ((event->key.keysym.mod & KMOD_LCTRL) && label_layer->selection >= 0) {
+ clipboard = 1;
+ dynarray_copy_to(label_layer->texts, clipboard_text, (size_t)label_layer->selection);
+ dynarray_copy_to(label_layer->colors, &clipboard_color, (size_t)label_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);
+
+ label_layer_add_label(
label_layer,
- (size_t) label_layer->selected,
+ position,
+ clipboard_color,
+ clipboard_text,
undo_history);
- label_layer->selected = -1;
}
} break;
}
trace_assert(label_layer);
trace_assert(event);
trace_assert(camera);
- trace_assert(label_layer->selected >= 0);
+ trace_assert(label_layer->selection >= 0);
+
+ Vec2f *positions = dynarray_data(label_layer->positions);
switch (event->type) {
case SDL_MOUSEMOTION: {
case SDL_MOUSEBUTTONUP: {
switch (event->button.button) {
case SDL_BUTTON_LEFT: {
- // TODO: Why pass label_layer->selected if we already pass label_layer
- UndoContext context = create_undo_context(label_layer, (size_t) label_layer->selected, UNDO_UPDATE);
- undo_history_push(
- undo_history,
- label_layer,
- label_layer_undo,
- &context,
- sizeof(context));
+ const float distance = vec_length(
+ vec_sub(label_layer->inter_position,
+ positions[label_layer->selection]));
+
+ if (distance > 1e-6) {
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
+ dynarray_replace_at(
+ label_layer->positions,
+ (size_t)label_layer->selection,
+ &label_layer->inter_position);
+ }
- dynarray_replace_at(
- label_layer->positions,
- (size_t)label_layer->selected,
- &label_layer->inter_position);
label_layer->state = LABEL_LAYER_IDLE;
} break;
}
static
int label_layer_edit_text_event(LabelLayer *label_layer,
const SDL_Event *event,
- const Camera *camera)
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(label_layer);
trace_assert(event);
trace_assert(camera);
- trace_assert(label_layer->selected >= 0);
+ trace_assert(label_layer->selection >= 0);
switch (event->type) {
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
case SDLK_RETURN: {
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
char *text =
- (char*)dynarray_data(label_layer->texts) + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE;
+ (char*)dynarray_data(label_layer->texts) + label_layer->selection * LABEL_LAYER_TEXT_MAX_SIZE;
memset(text, 0, LABEL_LAYER_TEXT_MAX_SIZE);
memcpy(text, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_TEXT_MAX_SIZE - 1);
label_layer->state = LABEL_LAYER_IDLE;
static
int label_layer_edit_id_event(LabelLayer *label_layer,
const SDL_Event *event,
- const Camera *camera)
+ const Camera *camera,
+ UndoHistory *undo_history)
{
trace_assert(label_layer);
trace_assert(event);
trace_assert(camera);
- trace_assert(label_layer->selected >= 0);
+ trace_assert(undo_history);
+ trace_assert(label_layer->selection >= 0);
switch (event->type) {
case SDL_KEYDOWN: {
switch (event->key.keysym.sym) {
case SDLK_RETURN: {
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
char *id =
- (char*)dynarray_data(label_layer->ids) + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE;
+ (char*)dynarray_data(label_layer->ids) + label_layer->selection * LABEL_LAYER_ID_MAX_SIZE;
memset(id, 0, LABEL_LAYER_ID_MAX_SIZE);
memcpy(id, edit_field_as_text(label_layer->edit_field), LABEL_LAYER_ID_MAX_SIZE - 1);
label_layer->state = LABEL_LAYER_IDLE;
trace_assert(event);
trace_assert(camera);
trace_assert(undo_history);
- trace_assert(label_layer->selected >= 0);
+ trace_assert(label_layer->selection >= 0);
int changed = 0;
color_picker_rgba(&label_layer->color_picker);
if (!color_picker_drag(&label_layer->color_picker)) {
+ UNDO_PUSH(undo_history, create_undo_context(label_layer, UNDO_UPDATE));
+
dynarray_replace_at(
label_layer->colors,
- (size_t) label_layer->selected,
+ (size_t) label_layer->selection,
&label_layer->inter_color);
label_layer->state = LABEL_LAYER_IDLE;
}
return label_layer_move_event(label_layer, event, camera, undo_history);
case LABEL_LAYER_EDIT_TEXT:
- return label_layer_edit_text_event(label_layer, event, camera);
+ return label_layer_edit_text_event(label_layer, event, camera, undo_history);
case LABEL_LAYER_EDIT_ID:
- return label_layer_edit_id_event(label_layer, event, camera);
+ return label_layer_edit_id_event(label_layer, event, camera, undo_history);
case LABEL_LAYER_RECOLOR:
return label_layer_recolor_event(label_layer, event, camera, undo_history);
return dynarray_data(label_layer->ids);
}
-Point *label_layer_positions(const LabelLayer *label_layer)
+Vec2f *label_layer_positions(const LabelLayer *label_layer)
{
return dynarray_data(label_layer->positions);
}
size_t n = dynarray_count(label_layer->ids);
char *ids = dynarray_data(label_layer->ids);
- Point *positions = dynarray_data(label_layer->positions);
+ Vec2f *positions = dynarray_data(label_layer->positions);
Color *colors = dynarray_data(label_layer->colors);
char *texts = dynarray_data(label_layer->texts);