]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/label_layer.c
Add TODO(#999)
[nothing.git] / src / game / level / level_editor / label_layer.c
index 10564e12eef11c293ce43d8eca2e57813d3c90c8..ec5b7f9e417e4373dc16237bdbbaf496f8c892a7 100644 (file)
@@ -1,13 +1,43 @@
+#include <stdio.h>
+
 #include <SDL.h>
 
 #include "system/line_stream.h"
 #include "system/stacktrace.h"
 #include "system/nth_alloc.h"
 #include "system/lt.h"
+#include "system/str.h"
+#include "system/log.h"
+#include "math/point.h"
 #include "label_layer.h"
+#include "dynarray.h"
+#include "color.h"
+#include "game/camera.h"
+#include "color_picker.h"
+#include "ui/edit_field.h"
+
+#define LABEL_LAYER_SELECTION_THICCNESS 5.0f
+
+// TODO(#999): LabelLayer does not support UndoHistory
+
+typedef enum {
+    LABEL_LAYER_IDLE = 0,
+    LABEL_LAYER_MOVE,
+    LABEL_LAYER_EDIT_TEXT,
+    LABEL_LAYER_EDIT_ID
+} LabelLayerState;
 
 struct LabelLayer {
     Lt *lt;
+    LabelLayerState state;
+    Dynarray *ids;
+    Dynarray *positions;
+    Dynarray *colors;
+    Dynarray *texts;
+    int selected;
+    ColorPicker color_picker;
+    Point move_anchor;
+    Edit_field *edit_field;
 };
 
 LayerPtr label_layer_as_layer(LabelLayer *label_layer)
@@ -21,37 +51,580 @@ LayerPtr label_layer_as_layer(LabelLayer *label_layer)
 
 LabelLayer *create_label_layer(void)
 {
-    return nth_calloc(1, sizeof(LabelLayer));
+    Lt *lt = create_lt();
+
+    LabelLayer *label_layer = PUSH_LT(
+        lt, nth_calloc(1, sizeof(LabelLayer)), free);
+    if (label_layer == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+    label_layer->lt = lt;
+
+    label_layer->ids = PUSH_LT(
+        lt,
+        create_dynarray(sizeof(char) * LABEL_LAYER_ID_MAX_SIZE),
+        destroy_dynarray);
+    if (label_layer->ids == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    label_layer->positions = PUSH_LT(lt, create_dynarray(sizeof(Point)), destroy_dynarray);
+    if (label_layer->positions == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    label_layer->colors = PUSH_LT(lt, create_dynarray(sizeof(Color)), destroy_dynarray);
+    if (label_layer->colors == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    label_layer->texts = PUSH_LT(
+        lt,
+        create_dynarray(sizeof(char) * LABEL_LAYER_TEXT_MAX_SIZE),
+        destroy_dynarray);
+    if (label_layer->texts == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    label_layer->color_picker = create_color_picker_from_rgba(COLOR_RED);
+    label_layer->selected = -1;
+
+    label_layer->edit_field = PUSH_LT(
+        lt,
+        create_edit_field(LABELS_SIZE, COLOR_RED),
+        destroy_edit_field);
+    if (label_layer->edit_field == NULL) {
+        RETURN_LT(lt, NULL);
+    }
+
+    return label_layer;
 }
 
 LabelLayer *create_label_layer_from_line_stream(LineStream *line_stream)
 {
     trace_assert(line_stream);
-    return nth_calloc(1, sizeof(LabelLayer));
+    LabelLayer *label_layer = create_label_layer();
+
+    if (label_layer == NULL) {
+        RETURN_LT(label_layer->lt, NULL);
+    }
+
+    const char *line = line_stream_next(line_stream);
+    if (line == NULL) {
+        log_fail("Could not read amount of labels\n");
+        RETURN_LT(label_layer->lt, NULL);
+    }
+
+    size_t n = 0;
+    if (sscanf(line, "%zu", &n) == EOF) {
+        log_fail("Could not parse amount of labels\n");
+        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;
+
+        line = line_stream_next(line_stream);
+        if (line == NULL) {
+            log_fail("Could not read label meta info\n");
+            RETURN_LT(label_layer->lt, NULL);
+        }
+
+        if (sscanf(
+                line,
+                "%"STRINGIFY(LABEL_LAYER_ID_MAX_SIZE)"s%f%f%6s\n",
+                id, &position.x, &position.y, hex) == EOF) {
+            log_fail("Could not parse label meta info\n");
+            RETURN_LT(label_layer->lt, NULL);
+        }
+
+        Color color = hexstr(hex);
+
+        dynarray_push(label_layer->ids, id);
+        dynarray_push(label_layer->positions, &position);
+        dynarray_push(label_layer->colors, &color);
+
+        line = line_stream_next(line_stream);
+        if (line == NULL) {
+            log_fail("Could not read label text\n");
+        }
+
+        char label_text[LABEL_LAYER_TEXT_MAX_SIZE] = {0};
+        memcpy(label_text, line, LABEL_LAYER_TEXT_MAX_SIZE - 1);
+        trim_endline(label_text);
+        dynarray_push(label_layer->texts, &label_text);
+    }
+
+    return label_layer;
 }
 
 void destroy_label_layer(LabelLayer *label_layer)
 {
     trace_assert(label_layer);
-    free(label_layer);
+    destroy_lt(label_layer->lt);
 }
 
 int label_layer_render(const LabelLayer *label_layer,
                        Camera *camera,
-                       float fa)
+                       int active)
 {
     trace_assert(label_layer);
     trace_assert(camera);
-    (void) fa;
+
+    if (active && color_picker_render(&label_layer->color_picker, camera) < 0) {
+        return -1;
+    }
+
+    size_t n = dynarray_count(label_layer->ids);
+    char *ids = dynarray_data(label_layer->ids);
+    Point *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) {
+        if (label_layer->state == LABEL_LAYER_EDIT_TEXT && label_layer->selected == (int) i) {
+            if (edit_field_render_world(
+                    label_layer->edit_field,
+                    camera,
+                    positions[i]) < 0) {
+                return -1;
+            }
+        } else {
+            if (camera_render_text(
+                    camera,
+                    texts + i * LABEL_LAYER_TEXT_MAX_SIZE,
+                    LABELS_SIZE,
+                    color_scale(
+                        colors[i],
+                        rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
+                    positions[i]) < 0) {
+                return -1;
+            }
+        }
+
+        if (label_layer->state == LABEL_LAYER_EDIT_ID && label_layer->selected == (int)i) {
+            if (edit_field_render_world(
+                    label_layer->edit_field,
+                    camera,
+                    vec_sub(
+                        positions[i],
+                        vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
+                return -1;
+            }
+        } else {
+            if (camera_render_text(
+                    camera,
+                    ids + i * LABEL_LAYER_ID_MAX_SIZE,
+                    vec(1.0f, 1.0f),
+                    color_scale(
+                        color_invert(colors[i]),
+                        rgba(1.0f, 1.0f, 1.0f, active ? 1.0f : 0.5f)),
+                    vec_sub(positions[i], vec(0.0f, FONT_CHAR_HEIGHT))) < 0) {
+                return -1;
+            }
+        }
+    }
+
+    if (label_layer->selected >= 0) {
+
+        Rect selection =
+            rect_scale(
+                camera_rect(
+                    camera,
+                    rect_boundary2(
+                        sprite_font_boundary_box(
+                            camera_font(camera),
+                            positions[label_layer->selected],
+                            LABELS_SIZE,
+                            texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE),
+                        sprite_font_boundary_box(
+                            camera_font(camera),
+                            vec_sub(
+                                positions[label_layer->selected],
+                                vec(0.0f, FONT_CHAR_HEIGHT)),
+                            vec(1.0f, 1.0f),
+                            ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE))),
+                LABEL_LAYER_SELECTION_THICCNESS * 0.5f);
+
+
+        if (camera_draw_thicc_rect_screen(
+                camera,
+                selection,
+                colors[label_layer->selected],
+                LABEL_LAYER_SELECTION_THICCNESS) < 0) {
+            return -1;
+        }
+    }
+
     return 0;
 }
 
+static
+int label_layer_element_at(LabelLayer *label_layer,
+                           const Sprite_font *font,
+                           Point position)
+{
+    trace_assert(label_layer);
+
+    const size_t n = 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);
+
+    for (size_t i = 0; i < n; ++i) {
+        Rect boundary = rect_boundary2(
+            sprite_font_boundary_box(
+                font,
+                positions[i],
+                LABELS_SIZE,
+                texts + i * LABEL_LAYER_TEXT_MAX_SIZE),
+            sprite_font_boundary_box(
+                font,
+                vec_sub(
+                    positions[i],
+                    vec(0.0f, FONT_CHAR_HEIGHT)),
+                vec(1.0f, 1.0f),
+                ids + i * LABEL_LAYER_ID_MAX_SIZE));
+
+        if (rect_contains_point(boundary, position)) {
+            return (int) i;
+        }
+    }
+
+    return -1;
+}
+
+static
+void label_layer_delete_nth_label(LabelLayer *label_layer,
+                                  size_t i)
+{
+    trace_assert(label_layer);
+    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);
+}
+
+static
+int label_layer_add_label(LabelLayer *label_layer,
+                           Point position,
+                           Color color)
+{
+    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';
+
+    size_t n = dynarray_count(label_layer->ids);
+
+    dynarray_push(label_layer->ids, id);
+    dynarray_push(label_layer->positions, &position);
+    dynarray_push(label_layer->colors, &color);
+    dynarray_push_empty(label_layer->texts);
+
+    return (int) n;
+}
+
+static
+int label_layer_idle_event(LabelLayer *label_layer,
+                           const SDL_Event *event,
+                           const Camera *camera)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+
+    Color *colors = dynarray_data(label_layer->colors);
+    Point *positions = dynarray_data(label_layer->positions);
+    char *ids = dynarray_data(label_layer->ids);
+    char *texts = dynarray_data(label_layer->texts);
+
+    switch (event->type) {
+    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 int element = label_layer_element_at(
+                label_layer,
+                camera_font(camera),
+                position);
+
+            if (element >= 0) {
+                label_layer->move_anchor = vec_sub(position, positions[element]);
+                label_layer->selected = element;
+                label_layer->state = LABEL_LAYER_MOVE;
+
+                label_layer->color_picker =
+                    create_color_picker_from_rgba(colors[element]);
+            } else {
+                label_layer->selected = label_layer_add_label(
+                    label_layer,
+                    position,
+                    color_picker_rgba(
+                        &label_layer->color_picker));
+                label_layer->state = LABEL_LAYER_EDIT_TEXT;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    LABELS_SIZE,
+                    colors[label_layer->selected]);
+                SDL_StartTextInput();
+            }
+        } break;
+        }
+    } break;
+
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_F2: {
+            if (label_layer->selected >= 0) {
+                label_layer->state = LABEL_LAYER_EDIT_TEXT;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    texts + label_layer->selected * LABEL_LAYER_TEXT_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    LABELS_SIZE,
+                    colors[label_layer->selected]);
+                SDL_StartTextInput();
+            }
+        } break;
+
+        case SDLK_F3: {
+            if (label_layer->selected >= 0) {
+                label_layer->state = LABEL_LAYER_EDIT_ID;
+                edit_field_replace(
+                    label_layer->edit_field,
+                    ids + label_layer->selected * LABEL_LAYER_ID_MAX_SIZE);
+                edit_field_restyle(
+                    label_layer->edit_field,
+                    vec(1.0f, 1.0f),
+                    color_invert(colors[label_layer->selected]));
+                SDL_StartTextInput();
+            }
+        } break;
+
+        case SDLK_DELETE: {
+            if (label_layer->selected >= 0) {
+                label_layer_delete_nth_label(
+                    label_layer,
+                    (size_t) label_layer->selected);
+                label_layer->selected = -1;
+            }
+        } break;
+        }
+    } break;
+    }
+
+    return 0;
+}
+
+static
+int label_layer_move_event(LabelLayer *label_layer,
+                           const SDL_Event *event,
+                           const Camera *camera)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(label_layer->selected >= 0);
+
+    switch (event->type) {
+    case SDL_MOUSEMOTION: {
+        Point *positions = dynarray_data(label_layer->positions);
+        positions[label_layer->selected] =
+            vec_sub(
+                camera_map_screen(
+                    camera,
+                    event->motion.x,
+                    event->motion.y),
+                label_layer->move_anchor);
+    } break;
+
+    case SDL_MOUSEBUTTONUP: {
+        switch (event->button.button) {
+        case SDL_BUTTON_LEFT: {
+            label_layer->state = LABEL_LAYER_IDLE;
+        } break;
+        }
+    } break;
+    }
+
+    return 0;
+}
+
+static
+int label_layer_edit_text_event(LabelLayer *label_layer,
+                                const SDL_Event *event,
+                                const Camera *camera)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(label_layer->selected >= 0);
+
+    switch (event->type) {
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_RETURN: {
+            char *text =
+                (char*)dynarray_data(label_layer->texts) + label_layer->selected * 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;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+
+        case SDLK_ESCAPE: {
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+        }
+    } break;
+    }
+
+    return edit_field_event(label_layer->edit_field, event);
+}
+
+static
+int label_layer_edit_id_event(LabelLayer *label_layer,
+                              const SDL_Event *event,
+                              const Camera *camera)
+{
+    trace_assert(label_layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(label_layer->selected >= 0);
+
+    switch (event->type) {
+    case SDL_KEYDOWN: {
+        switch (event->key.keysym.sym) {
+        case SDLK_RETURN: {
+            char *id =
+                (char*)dynarray_data(label_layer->ids) + label_layer->selected * 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;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+
+        case SDLK_ESCAPE: {
+            label_layer->state = LABEL_LAYER_IDLE;
+            SDL_StopTextInput();
+            return 0;
+        } break;
+        }
+    } break;
+    }
+
+    return edit_field_event(label_layer->edit_field, event);
+}
+
 int label_layer_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(undo_history);
+
+    int changed = 0;
+
+    if (color_picker_event(
+            &label_layer->color_picker,
+            event,
+            &changed) < 0) {
+        return -1;
+    }
+
+    if (changed) {
+        if (label_layer->selected >= 0) {
+            Color *colors = dynarray_data(label_layer->colors);
+            colors[label_layer->selected] =
+                color_picker_rgba(&label_layer->color_picker);
+        }
+        return 0;
+    }
+
+    switch (label_layer->state) {
+    case LABEL_LAYER_IDLE:
+        return label_layer_idle_event(label_layer, event, camera);
+
+    case LABEL_LAYER_MOVE:
+        return label_layer_move_event(label_layer, event, camera);
+
+    case LABEL_LAYER_EDIT_TEXT:
+        return label_layer_edit_text_event(label_layer, event, camera);
+
+    case LABEL_LAYER_EDIT_ID:
+        return label_layer_edit_id_event(label_layer, event, camera);
+    }
+
+    return 0;
+}
+
+size_t label_layer_count(const LabelLayer *label_layer)
+{
+    return dynarray_count(label_layer->ids);
+}
+
+char *label_layer_ids(const LabelLayer *label_layer)
+{
+    return dynarray_data(label_layer->ids);
+}
+
+Point *label_layer_positions(const LabelLayer *label_layer)
+{
+    return dynarray_data(label_layer->positions);
+}
+
+Color *label_layer_colors(const LabelLayer *label_layer)
+{
+    return dynarray_data(label_layer->colors);
+}
+
+char *labels_layer_texts(const LabelLayer *label_layer)
+{
+    return dynarray_data(label_layer->texts);
+}
+
+int label_layer_dump_stream(const LabelLayer *label_layer, FILE *filedump)
+{
+    trace_assert(label_layer);
+    trace_assert(filedump);
+
+    size_t n = dynarray_count(label_layer->ids);
+    char *ids = dynarray_data(label_layer->ids);
+    Point *positions = dynarray_data(label_layer->positions);
+    Color *colors = dynarray_data(label_layer->colors);
+    char *texts = dynarray_data(label_layer->texts);
+
+    fprintf(filedump, "%zd\n", n);
+    for (size_t i = 0; i < n; ++i) {
+        fprintf(filedump, "%s %f %f ",
+                ids + LABEL_LAYER_ID_MAX_SIZE * i,
+                positions[i].x, positions[i].y);
+        color_hex_to_stream(colors[i], filedump);
+        fprintf(filedump, "\n%s\n", texts + i * LABEL_LAYER_TEXT_MAX_SIZE);
+    }
+
     return 0;
 }