diff --git a/src/COMMON.h b/src/COMMON.h index a784c62..3f57212 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -108,17 +108,35 @@ static inline s32 map_next_id_get(const std::map& map) { s32 id = 0; for (const auto& [key, _] : map) if (key != id) break; else ++id; return id; } +/* Swaps elements in a map */ +/* If neither key exists, do nothing */ +/* If one key exists, change its ID */ +/* If both keys exist, swap */ template static inline void map_swap(Map& map, const Key& key1, const Key& key2) { + if (key1 == key2) + return; + auto it1 = map.find(key1); auto it2 = map.find(key2); - if (it1 == map.end() || it2 == map.end()) - return; - using std::swap; - swap(it1->second, it2->second); -} + if (it1 != map.end() && it2 != map.end()) + { + using std::swap; + swap(it1->second, it2->second); + } + else if (it1 != map.end()) + { + map[key2] = std::move(it1->second); + map.erase(it1); + } + else if (it2 != map.end()) + { + map[key1] = std::move(it2->second); + map.erase(it2); + } +}; #define DEFINE_ENUM_TO_STRING_FN(fn_name, arr, count) \ static inline const char* fn_name(s32 index) { \ diff --git a/src/STRINGS.h b/src/STRINGS.h index 465a2fb..23ee225 100644 --- a/src/STRINGS.h +++ b/src/STRINGS.h @@ -62,13 +62,15 @@ #define STRING_IMGUI_ANIMATIONS_REMOVE "Remove" #define STRING_IMGUI_ANIMATIONS_DUPLICATE "Duplicate" #define STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT "Set as Default" -#define STRING_IMGUI_ANIMATIONS_DEFAULT_ANIMATION_FORMAT "%s (*)" +#define STRING_IMGUI_ANIMATIONS_DEFAULT_ANIMATION_FORMAT "(*) %s " +#define STRING_IMGUI_ANIMATIONS_DRAG_DROP "Animation Drag/Drop" #define STRING_IMGUI_EVENTS "Events" #define STRING_IMGUI_EVENTS_EVENT_LABEL "##Event" #define STRING_IMGUI_EVENTS_ADD "Add" #define STRING_IMGUI_EVENTS_REMOVE "Remove" #define STRING_IMGUI_EVENT_FORMAT "#%i %s" +#define STRING_IMGUI_EVENTS_DRAG_DROP "Event Drag/Drop" #define STRING_IMGUI_SPRITESHEETS "Spritesheets" #define STRING_IMGUI_SPRITESHEETS_ADD "Add" @@ -76,6 +78,7 @@ #define STRING_IMGUI_SPRITESHEETS_RELOAD "Reload" #define STRING_IMGUI_SPRITESHEETS_REPLACE "Replace" #define STRING_IMGUI_SPRITESHEET_FORMAT "#%i %s" +#define STRING_IMGUI_SPRITESHEETS_DRAG_DROP "Spritesheet Drag/Drop" #define STRING_IMGUI_FRAME_PROPERTIES "Frame Properties" #define STRING_IMGUI_FRAME_PROPERTIES_CROP_POSITION "Crop Position" @@ -116,12 +119,15 @@ #define STRING_IMGUI_ANIMATION_PREVIEW_AXIS_COLOR "Color" #define STRING_IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM "Root Transform" #define STRING_IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT "Show Pivot" +#define STRING_IMGUI_ANIMATION_PREVIEW_POSITION "##Position" +#define STRING_IMGUI_ANIMATION_PREVIEW_POSITION_FORMAT "Position: {%5.0f, %5.0f}" #define STRING_IMGUI_SPRITESHEET_EDITOR "Spritesheet Editor" #define STRING_IMGUI_SPRITESHEET_EDITOR_LABEL "##Animation Preview" #define STRING_IMGUI_SPRITESHEET_EDITOR_SETTINGS "##Animation Preview Settings" #define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS "##Grid Settings" #define STRING_IMGUI_SPRITESHEET_EDITOR_GRID "Grid" +#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SNAP "Snap" #define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SIZE "Size" #define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET "Offset" #define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_COLOR "Color" @@ -131,6 +137,8 @@ #define STRING_IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR "Background Color" #define STRING_IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW "Center View" #define STRING_IMGUI_SPRITESHEET_EDITOR_BORDER "Border" +#define STRING_IMGUI_SPRITESHEET_EDITOR_POSITION "##Position" +#define STRING_IMGUI_SPRITESHEET_EDITOR_POSITION_FORMAT "Position: {%5.0f, %5.0f}" #define STRING_IMGUI_TIMELINE "Timeline" #define STRING_IMGUI_TIMELINE_HEADER "##Header" @@ -178,6 +186,8 @@ #define STRING_IMGUI_TIMELINE_VIEWER "Viewer" #define STRING_IMGUI_TIMELINE_CHILD "Timeline" #define STRING_IMGUI_TIMELINE_MAIN "Main" +#define STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP "Frame Drag/Drop" +#define STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP "Item Drag/Drop" #define STRING_IMGUI_TOOLS "Tools" #define STRING_IMGUI_TOOLS_PAN "##Pan" @@ -190,7 +200,7 @@ #define STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE "Duplicates the selected animation." #define STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE "Removes the selected animation." #define STRING_IMGUI_TOOLTIP_ANIMATIONS_SELECT "Select the animation to edit.\nYou can also click the name to edit it." -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT "Sets the selected animation as the default.\nDefault animations are marked with \"(*)\"." +#define STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT "Sets the selected animation as the default.\nThe default animation is marked with \"(*)\"." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS "Toggles the display of the X/Y axes on the animation preview." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS_COLOR "Changes the color of the X/Y axes on the animation preview." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR "Changes the background color of the animation preview." @@ -200,7 +210,7 @@ #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_OFFSET "Changes the animation preview grid's offset, in pixels." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE "Changes the animation preview grid's size, in pixels." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM "Changes the animation preview zoom level." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM "Toggle the properties of the Root element of an animation being able to change the properties (such as position or scale) of all of the animation's elements." +#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM "Toggle the properties of the Root element of an animation being able to change the properties (such as position or scale) of all of the animation's elements.\nNOTE: Scale/rotation currently not implemented. Matrix math is hard." #define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT "Toggle the pivots of layer animation elements being visible on the animation preview." #define STRING_IMGUI_TOOLTIP_EVENTS_ADD "Add a new event." #define STRING_IMGUI_TOOLTIP_EVENTS_REMOVE "Removes the selected event." @@ -251,16 +261,17 @@ #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BACKGROUND_COLOR "Changes the background color of the spritesheet editor." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_CENTER_VIEW "Centers the spritesheet editor's pan." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID "Toggles grid visibility on the spritesheet editor." +#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SNAP "When using the crop tool, will snap points to the grid." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_COLOR "Changes the spritesheet editor grid color." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_OFFSET "Changes the spritesheet editor grid's offset, in pixels." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SIZE "Changes the spritesheet editor grid's size, in pixels." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_ZOOM "Changes the spritesheet editor zoom level." #define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BORDER "Toggles a border appearing around the confines of the spritesheet." #define STRING_IMGUI_TOOLTIP_TOOLS_PAN "Use the pan tool.\nWill shift the view as the cursor is dragged." -#define STRING_IMGUI_TOOLTIP_TOOLS_MOVE "Use the move tool.\nWill move selected elements as the cursor is dragged." -#define STRING_IMGUI_TOOLTIP_TOOLS_ROTATE "Use the rotate tool.\nWill rotate selected elements as the cursor is dragged." -#define STRING_IMGUI_TOOLTIP_TOOLS_SCALE "Use the scale tool.\nWill scale the selected elements as the cursor is dragged." -#define STRING_IMGUI_TOOLTIP_TOOLS_CROP "Use the crop tool.\nWill set the crop of the selected elements as the cursor is dragged." +#define STRING_IMGUI_TOOLTIP_TOOLS_MOVE "Use the move tool.\nWill move selected elements as the cursor is dragged.\n(Animation Preview only.)" +#define STRING_IMGUI_TOOLTIP_TOOLS_ROTATE "Use the rotate tool.\nWill rotate selected elements as the cursor is dragged.\n(Animation Preview only.)" +#define STRING_IMGUI_TOOLTIP_TOOLS_SCALE "Use the scale tool.\nWill scale the selected elements as the cursor is dragged.\n(Animation Preview only.)" +#define STRING_IMGUI_TOOLTIP_TOOLS_CROP "Use the crop tool.\nWill set the crop of the selected elements as the cursor is dragged.\n(Spritesheet Editor only.)" #define STRING_OPENGL_VERSION "#version 330" diff --git a/src/anm2.cpp b/src/anm2.cpp index 607d5f7..02248e0 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -37,7 +37,6 @@ anm2_serialize(Anm2* self, const char* path) if (!self || !path) return false; - /* Update creation date on first version */ if (self->version == 0) anm2_created_on_set(self); @@ -45,8 +44,6 @@ anm2_serialize(Anm2* self, const char* path) /* Increment anm2's version */ self->version++; - /* Set the anm2's date to the system time */ - /* AnimatedActor */ animatedActorElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); document.InsertFirstChild(animatedActorElement); @@ -283,14 +280,14 @@ anm2_serialize(Anm2* self, const char* path) /* Triggers */ triggersElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]); - for (auto & trigger : animation.triggers.items) + for (auto & frame : animation.triggers.frames) { XMLElement* triggerElement; /* Trigger */ triggerElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]); - triggerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], trigger.eventID); /* EventID */ - triggerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_AT_FRAME], trigger.atFrame); /* AtFrame */ + triggerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], frame.eventID); /* EventID */ + triggerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_AT_FRAME], frame.atFrame); /* AtFrame */ triggersElement->InsertEndChild(triggerElement); } @@ -321,35 +318,27 @@ anm2_serialize(Anm2* self, const char* path) bool anm2_deserialize(Anm2* self, Resources* resources, const char* path) { - XMLDocument document; - XMLError error; - const XMLElement* element; - const XMLElement* root; - Anm2Spritesheet* lastSpritesheet = NULL; - Anm2Layer* lastLayer = NULL; - Anm2Null* lastNull = NULL; - Anm2Event* lastEvent = NULL; - Anm2Animation* lastAnimation = NULL; - Anm2LayerAnimation* lastLayerAnimation = NULL; - Anm2NullAnimation* lastNullAnimation = NULL; - Anm2Frame* lastFrame = NULL; - Anm2Trigger* lastTrigger = NULL; + XMLDocument xmlDocument; + XMLError xmlError; + const XMLElement* xmlElement; + const XMLElement* xmlRoot; + Anm2Animation* animation = NULL; + Anm2Layer* layer = NULL; + Anm2Null* null = NULL; + Anm2Item* item = NULL; + Anm2Event* event = NULL; + Anm2Frame* frame = NULL; + Anm2Spritesheet* spritesheet = NULL; Anm2Element anm2Element = ANM2_ELEMENT_ANIMATED_ACTOR; Anm2Attribute anm2Attribute = ANM2_ATTRIBUTE_ID; - Anm2AnimationType animationType = ANM2_ROOT_ANIMATION; - Anm2Null tempNull; - Anm2Layer tempLayer; - Anm2Spritesheet tempSpritesheet; - Anm2Event tempEvent; - char lastSpritesheetPath[PATH_MAX]; *self = Anm2{}; - error = document.LoadFile(path); + xmlError = xmlDocument.LoadFile(path); - if (error != XML_SUCCESS) + if (xmlError != XML_SUCCESS) { - printf(STRING_ERROR_ANM2_READ, path, document.ErrorStr()); + printf(STRING_ERROR_ANM2_READ, path, xmlDocument.ErrorStr()); return false; } @@ -357,297 +346,252 @@ anm2_deserialize(Anm2* self, Resources* resources, const char* path) strncpy(self->path, path, PATH_MAX - 1); working_directory_from_path_set(path); - root = document.FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); - element = root; + xmlRoot = xmlDocument.FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); + xmlElement = xmlRoot; - while (element) + while (xmlElement) { - const XMLAttribute* attribute; - const XMLElement* child; - s32 id; + + const XMLAttribute* xmlAttribute = NULL; + const XMLElement* xmlChild = NULL; + s32 id = 0; /* Elements */ - anm2Element = anm2_element_from_string(element->Name()); - + anm2Element = anm2_element_from_string(xmlElement->Name()); + switch (anm2Element) { case ANM2_ELEMENT_SPRITESHEET: - lastSpritesheet = &tempSpritesheet; + id = map_next_id_get(self->spritesheets); + self->spritesheets[id] = Anm2Spritesheet{}; + spritesheet = &self->spritesheets[id]; break; case ANM2_ELEMENT_LAYER: - lastLayer = &tempLayer; + id = map_next_id_get(self->layers); + self->layers[id] = Anm2Layer{}; + layer = &self->layers[id]; break; case ANM2_ELEMENT_NULL: - lastNull = &tempNull; + id = map_next_id_get(self->nulls); + self->nulls[id] = Anm2Null{}; + null = &self->nulls[id]; break; case ANM2_ELEMENT_EVENT: - lastEvent = &tempEvent; + id = map_next_id_get(self->events); + self->events[id] = Anm2Event{}; + event = &self->events[id]; break; case ANM2_ELEMENT_ANIMATION: id = map_next_id_get(self->animations); self->animations[id] = Anm2Animation{}; - lastAnimation = &self->animations[id]; + animation = &self->animations[id]; break; case ANM2_ELEMENT_ROOT_ANIMATION: - animationType = ANM2_ROOT_ANIMATION; + item = &animation->rootAnimation; break; case ANM2_ELEMENT_LAYER_ANIMATION: - animationType = ANM2_LAYER_ANIMATION; - lastLayerAnimation = NULL; + id = map_next_id_get(animation->layerAnimations); + animation->layerAnimations[id] = Anm2Item{}; + item = &animation->layerAnimations[id]; break; case ANM2_ELEMENT_NULL_ANIMATION: - animationType = ANM2_NULL_ANIMATION; - lastNullAnimation = NULL; + id = map_next_id_get(animation->nullAnimations); + animation->nullAnimations[id] = Anm2Item{}; + item = &animation->nullAnimations[id]; + break; + case ANM2_ELEMENT_TRIGGERS: + item = &animation->triggers; break; case ANM2_ELEMENT_FRAME: - switch (animationType) - { - case ANM2_ROOT_ANIMATION: - lastAnimation->rootAnimation.frames.push_back(Anm2Frame{}); - lastFrame = &lastAnimation->rootAnimation.frames.back(); - break; - case ANM2_LAYER_ANIMATION: - if (!lastLayerAnimation) break; - lastLayerAnimation->frames.push_back(Anm2Frame{}); - lastFrame = &lastLayerAnimation->frames.back(); - break; - case ANM2_NULL_ANIMATION: - if (!lastNullAnimation) break; - lastNullAnimation->frames.push_back(Anm2Frame{}); - lastFrame = &lastNullAnimation->frames.back(); - break; - default: - break; - } - break; case ANM2_ELEMENT_TRIGGER: - lastAnimation->triggers.items.push_back(Anm2Trigger{}); - lastTrigger = &lastAnimation->triggers.items.back(); - break; + item->frames.push_back(Anm2Frame{}); + frame = &item->frames.back(); default: break; } /* Attributes */ - attribute = element->FirstAttribute(); + xmlAttribute = xmlElement->FirstAttribute(); - while (attribute) + while (xmlAttribute) { - anm2Attribute = anm2_attribute_from_string(attribute->Name()); - + anm2Attribute = anm2_attribute_from_string(xmlAttribute->Name()); + switch (anm2Attribute) { case ANM2_ATTRIBUTE_CREATED_BY: - strncpy(self->createdBy, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(self->createdBy, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ATTRIBUTE_CREATED_ON: - strncpy(self->createdOn, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(self->createdOn, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ATTRIBUTE_VERSION: - self->version = atoi(attribute->Value()); + self->version = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_FPS: - self->fps = atoi(attribute->Value()); + self->fps = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_ID: - id = atoi(attribute->Value()); - switch (anm2Element) - { - case ANM2_ELEMENT_SPRITESHEET: - self->spritesheets[id] = tempSpritesheet; - lastSpritesheet = &self->spritesheets[id]; - break; - case ANM2_ELEMENT_LAYER: - self->layers[id] = tempLayer; - lastLayer = &self->layers[id]; - break; - case ANM2_ELEMENT_NULL: - self->nulls[id] = tempNull; - lastNull = &self->nulls[id]; - break; - case ANM2_ELEMENT_EVENT: - self->events[id] = tempEvent; - lastEvent = &self->events[id]; - break; - default: - break; - } + break; + case ANM2_ATTRIBUTE_LAYER_ID: + map_swap(animation->layerAnimations, id, atoi(xmlAttribute->Value())); + break; + case ANM2_ATTRIBUTE_NULL_ID: + map_swap(animation->nullAnimations, id, atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_PATH: - /* Make path lowercase */ - strncpy(lastSpritesheetPath, attribute->Value(), PATH_MAX - 1); + strncpy(spritesheet->path, xmlAttribute->Value(), PATH_MAX - 1); break; case ANM2_ATTRIBUTE_NAME: switch (anm2Element) { case ANM2_ELEMENT_LAYER: - strncpy(lastLayer->name, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(layer->name, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ELEMENT_NULL: - strncpy(lastNull->name, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(null->name, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ELEMENT_ANIMATION: - strncpy(lastAnimation->name, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(animation->name, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ELEMENT_EVENT: - strncpy(lastEvent->name, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(event->name, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; default: break; } break; case ANM2_ATTRIBUTE_SPRITESHEET_ID: - lastLayer->spritesheetID = atoi(attribute->Value()); + layer->spritesheetID = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_SHOW_RECT: - switch (anm2Element) - { - case ANM2_ELEMENT_NULL: - lastNull->isShowRect = string_to_bool(attribute->Value()); - break; - default: - break; - } + null->isShowRect = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_DEFAULT_ANIMATION: - strncpy(self->defaultAnimation, attribute->Value(), ANM2_STRING_MAX - 1); + strncpy(self->defaultAnimation, xmlAttribute->Value(), ANM2_STRING_MAX - 1); break; case ANM2_ATTRIBUTE_FRAME_NUM: - lastAnimation->frameNum = atoi(attribute->Value()); + animation->frameNum = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_LOOP: - lastAnimation->isLoop = string_to_bool(attribute->Value()); + animation->isLoop = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_POSITION: - lastFrame->position.x = atof(attribute->Value()); + frame->position.x = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_POSITION: - lastFrame->position.y = atof(attribute->Value()); + frame->position.y = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_PIVOT: - lastFrame->pivot.x = atof(attribute->Value()); + frame->pivot.x = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_PIVOT: - lastFrame->pivot.y = atof(attribute->Value()); + frame->pivot.y = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_CROP: - lastFrame->crop.x = atof(attribute->Value()); + frame->crop.x = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_CROP: - lastFrame->crop.y = atof(attribute->Value()); + frame->crop.y = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_WIDTH: - lastFrame->size.x = atof(attribute->Value()); + frame->size.x = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_HEIGHT: - lastFrame->size.y = atof(attribute->Value()); + frame->size.y = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_SCALE: - lastFrame->scale.x = atof(attribute->Value()); + frame->scale.x = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_SCALE: - lastFrame->scale.y = atof(attribute->Value()); + frame->scale.y = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_DELAY: - lastFrame->delay = atoi(attribute->Value()); + frame->delay = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_VISIBLE: switch (anm2Element) { case ANM2_ELEMENT_FRAME: - lastFrame->isVisible = string_to_bool(attribute->Value()); + frame->isVisible = string_to_bool(xmlAttribute->Value()); break; - case ANM2_LAYER_ANIMATION: - lastLayerAnimation->isVisible = string_to_bool(attribute->Value()); - break; - case ANM2_NULL_ANIMATION: - lastNullAnimation->isVisible = string_to_bool(attribute->Value()); + case ANM2_ELEMENT_ROOT_ANIMATION: + case ANM2_ELEMENT_LAYER_ANIMATION: + case ANM2_ELEMENT_NULL_ANIMATION: + item->isVisible = string_to_bool(xmlAttribute->Value()); break; default: break; } break; case ANM2_ATTRIBUTE_RED_TINT: - lastFrame->tintRGBA.r = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->tintRGBA.r = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_TINT: - lastFrame->tintRGBA.g = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->tintRGBA.g = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_TINT: - lastFrame->tintRGBA.b = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->tintRGBA.b = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_ALPHA_TINT: - lastFrame->tintRGBA.a = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->tintRGBA.a = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_RED_OFFSET: - lastFrame->offsetRGB.r = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->offsetRGB.r = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_OFFSET: - lastFrame->offsetRGB.g = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->offsetRGB.g = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_OFFSET: - lastFrame->offsetRGB.b = COLOR_INT_TO_FLOAT(atoi(attribute->Value())); + frame->offsetRGB.b = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_ROTATION: - lastFrame->rotation = atof(attribute->Value()); + frame->rotation = atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_INTERPOLATED: - lastFrame->isInterpolated = string_to_bool(attribute->Value()); - break; - case ANM2_ATTRIBUTE_LAYER_ID: - id = atoi(attribute->Value()); - lastAnimation->layerAnimations[id] = Anm2LayerAnimation{}; - lastLayerAnimation = &lastAnimation->layerAnimations[id]; - break; - case ANM2_ATTRIBUTE_NULL_ID: - id = atoi(attribute->Value()); - lastAnimation->nullAnimations[id] = Anm2NullAnimation{}; - lastNullAnimation = &lastAnimation->nullAnimations[id]; + frame->isInterpolated = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_EVENT_ID: - lastTrigger->eventID = atoi(attribute->Value()); + frame->eventID = atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_AT_FRAME: - lastTrigger->atFrame = atoi(attribute->Value()); + frame->atFrame = atoi(xmlAttribute->Value()); break; default: break; } - attribute = attribute->Next(); + xmlAttribute = xmlAttribute->Next(); } /* Load spritesheet textures */ if (anm2Element == ANM2_ELEMENT_SPRITESHEET) - { - strncpy(lastSpritesheet->path, lastSpritesheetPath, PATH_MAX); - anm2_spritesheet_texture_load(self, resources, lastSpritesheetPath , id); - } + anm2_spritesheet_texture_load(self, resources, spritesheet->path , id); /* Iterate through children */ - child = element->FirstChildElement(); + xmlChild = xmlElement->FirstChildElement(); - if (child) + if (xmlChild) { - element = child; + xmlElement = xmlChild; continue; } /* Iterate through siblings */ - while (element) + while (xmlElement) { - const XMLElement* next; + const XMLElement* xmlNext; - next = element->NextSiblingElement(); + xmlNext = xmlElement->NextSiblingElement(); - if (next) + if (xmlNext) { - element = next; + xmlElement = xmlNext; break; } /* If no siblings, return to parent. If no parent, end parsing */ - element = element->Parent() ? element->Parent()->ToElement() : NULL; + xmlElement = xmlElement->Parent() ? xmlElement->Parent()->ToElement() : NULL; } } @@ -665,7 +609,7 @@ anm2_layer_add(Anm2* self) self->layers[id] = Anm2Layer{}; for (auto & [animationID, animation] : self->animations) - animation.layerAnimations[id] = Anm2LayerAnimation{}; + animation.layerAnimations[id] = Anm2Item{}; } /* Removes a layer from the anm2 given the index/id */ @@ -674,8 +618,8 @@ anm2_layer_remove(Anm2* self, s32 id) { self->layers.erase(id); - for (auto& animationPair : self->animations) - animationPair.second.layerAnimations.erase(id); + for (auto & [animationID, animation] : self->animations) + animation.layerAnimations.erase(id); } /* Adds a new null to the anm2 */ @@ -687,7 +631,7 @@ anm2_null_add(Anm2* self) self->nulls[id] = Anm2Null{}; for (auto & [animationID, animation] : self->animations) - animation.nullAnimations[id] = Anm2NullAnimation{}; + animation.nullAnimations[id] = Anm2Item{}; } /* Removes a null from the anm2 given the index/id */ @@ -696,8 +640,8 @@ anm2_null_remove(Anm2* self, s32 id) { self->nulls.erase(id); - for (auto& animationPair : self->animations) - animationPair.second.nullAnimations.erase(id); + for (auto & [animationID, animation] : self->animations) + animation.nullAnimations.erase(id); } /* Adds a new animation to the anm2, makes sure to keep the layeranimations/nullsanimation check */ @@ -710,13 +654,13 @@ anm2_animation_add(Anm2* self) /* match layers */ for (auto & [layerID, layer] : self->layers) { - animation.layerAnimations[layerID] = Anm2LayerAnimation{}; + animation.layerAnimations[layerID] = Anm2Item{}; } /* match nulls */ for (auto & [nullID, null] : self->nulls) { - animation.nullAnimations[nullID] = Anm2NullAnimation{}; + animation.nullAnimations[nullID] = Anm2Item{}; } /* add a root frame */ @@ -757,69 +701,104 @@ anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const char* path resources->textures[id] = texture; } +Anm2Animation* +anm2_animation_from_id(Anm2* self, s32 animationID) +{ + auto it = self->animations.find(animationID); + if (it == self->animations.end()) + return NULL; + return &it->second; +} + +/* Returns the item from a anm2 reference. */ +Anm2Item* +anm2_item_from_reference(Anm2* self, Anm2Reference* reference, s32 animationID) +{ + Anm2Animation* animation = anm2_animation_from_id(self, animationID); + + if (!animation) + return NULL; + + switch (reference->type) + { + case ANM2_ROOT: + return &animation->rootAnimation; + case ANM2_LAYER: + { + auto it = animation->layerAnimations.find(reference->id); + if (it == animation->layerAnimations.end()) + return NULL; + return &it->second; + } + case ANM2_NULL: + { + auto it = animation->nullAnimations.find(reference->id); + if (it == animation->nullAnimations.end()) + return NULL; + return &it->second; + } + case ANM2_TRIGGERS: + return &animation->triggers; + default: + return NULL; + } +} + +/* Gets the frame from the reference's properties */ +Anm2Frame* +anm2_frame_from_reference(Anm2* self, Anm2Reference* reference, s32 animationID) +{ + Anm2Item* item = anm2_item_from_reference(self, reference, animationID); + + if (!item) + return NULL; + + if (reference->index < 0 || reference->index >= (s32)item->frames.size()) + return NULL; + + return &item->frames[reference->index]; +} + /* Creates/fetches a frame from a given time. */ /* Returns true/false if frame will be valid or not. */ -bool -anm2_frame_from_time(Anm2* self, Anm2Animation* animation, Anm2Frame* frame, Anm2AnimationType type, s32 id, f32 time) +void +anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, s32 animationID, f32 time) { + Anm2Animation* animation = anm2_animation_from_id(self, animationID); + /* Out of range */ if (time < 0 || time > animation->frameNum) - return false; + return; + + Anm2Item* item = anm2_item_from_reference(self, &reference, animationID); + + if (!item) + return; - Anm2RootAnimation* rootAnimation; - Anm2LayerAnimation* layerAnimation; - Anm2NullAnimation* nullAnimation; Anm2Frame* nextFrame = NULL; - std::vector* frames = NULL; - f32 delayCurrent = 0; - f32 delayNext = 0; - bool isTimeMatchedFrame = false; + s32 delayCurrent = 0; + s32 delayNext = 0; - switch (type) + for (s32 i = 0; i < (s32)item->frames.size(); i++) { - case ANM2_ROOT_ANIMATION: - frames = &animation->rootAnimation.frames; - break; - case ANM2_LAYER_ANIMATION: - if (id < 0 || id >= (s32)animation->layerAnimations.size()) - return false; - frames = &animation->layerAnimations[id].frames; - break; - case ANM2_NULL_ANIMATION: - if (id < 0 || id >= (s32)animation->nullAnimations.size()) - return false; - frames = &animation->nullAnimations[id].frames; - break; - default: - return false; - } - - for (s32 i = 0; i < (s32)frames->size(); i++) - { - *frame = (*frames)[i]; + *frame = item->frames[i]; delayNext += frame->delay; /* If a frame is within the time constraints, it's a time matched frame, break */ /* Otherwise, the last found frame parsed will be used. */ if (time >= delayCurrent && time < delayNext) { - if (i + 1 < (s32)frames->size()) - nextFrame = &(*frames)[i + 1]; + if (i + 1 < (s32)item->frames.size()) + nextFrame = &item->frames[i + 1]; else nextFrame = NULL; - - isTimeMatchedFrame = true; break; } delayCurrent += frame->delay; } - /* No valid frame found */ - if (!isTimeMatchedFrame) - return false; - - /* interpolate only if there's a frame following */ + /* Interpolate only if there's a frame following */ if (frame->isInterpolated && nextFrame) { f32 interpolationTime = (time - delayCurrent) / (delayNext - delayCurrent); @@ -830,6 +809,67 @@ anm2_frame_from_time(Anm2* self, Anm2Animation* animation, Anm2Frame* frame, Anm frame->offsetRGB = glm::mix(frame->offsetRGB, nextFrame->offsetRGB, interpolationTime);; frame->tintRGBA = glm::mix(frame->tintRGBA, nextFrame->tintRGBA, interpolationTime);; } +} - return true; +/* Will try adding a frame to the anm2 given the specified reference */ +Anm2Frame* +anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 animationID, s32 time) +{ + Anm2Animation* animation = anm2_animation_from_id(self, animationID); + Anm2Item* item = anm2_item_from_reference(self, reference, animationID); + + if (!animation || !item) + return NULL; + + if (item) + { + Anm2Frame frame = Anm2Frame{}; + s32 index = -1; + + if (reference->type == ANM2_TRIGGERS) + { + /* don't add redudant triggers (i.e. at same time) */ + for (auto & frameCheck : item->frames) + { + if (frameCheck.atFrame == time) + return NULL; + } + + frame.atFrame = time; + index = item->frames.size(); + } + else + { + s32 delay = 0; + s32 frameDelayCount = 0; + + /* Add up all delay to see where this new frame might lie */ + for (auto & frameCheck : item->frames) + frameDelayCount += frameCheck.delay; + + /* If adding the smallest frame would be over the length, don't bother */ + if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) + return NULL; + + /* Will insert next to frame if frame exists */ + Anm2Frame* checkFrame = anm2_frame_from_reference(self, reference, animationID); + + if (checkFrame) + { + /* Will shrink frame delay to fit */ + if (frameDelayCount + checkFrame->delay > animation->frameNum) + frame.delay = animation->frameNum - frameDelayCount; + + index = reference->index + 1; + } + else + index = (s32)item->frames.size(); + } + + item->frames.insert(item->frames.begin() + index, frame); + + return &item->frames[index]; + } + + return NULL; } \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index f742b94..b79e925 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -14,6 +14,7 @@ #define ANM2_FPS_MAX 120 #define ANM2_FRAME_NUM_MIN 1 #define ANM2_FRAME_NUM_MAX 1000000 +#define ANM2_FRAME_DELAY_MIN 1 /* Elements */ #define ANM2_ELEMENT_LIST \ @@ -108,14 +109,14 @@ static const char* ANM2_ATTRIBUTE_STRINGS[] = { DEFINE_STRING_TO_ENUM_FN(anm2_attribute_from_string, Anm2Attribute, ANM2_ATTRIBUTE_STRINGS, ANM2_ATTRIBUTE_COUNT) -#define ANM2_ANIMATION_TYPE_COUNT (ANM2_ANIMATION_TRIGGERS + 1) -enum Anm2AnimationType +#define ANM2_COUNT (ANM2_TRIGGER + 1) +enum Anm2Type { ANM2_NONE, - ANM2_ROOT_ANIMATION, - ANM2_LAYER_ANIMATION, - ANM2_NULL_ANIMATION, - ANM2_TRIGGER + ANM2_ROOT, + ANM2_LAYER, + ANM2_NULL, + ANM2_TRIGGERS }; struct Anm2Spritesheet @@ -140,18 +141,14 @@ struct Anm2Event char name[ANM2_STRING_MAX] = STRING_ANM2_NEW_EVENT; }; -struct Anm2Trigger -{ - s32 eventID = -1; - s32 atFrame = -1; -}; - struct Anm2Frame { bool isInterpolated = false; bool isVisible = true; f32 rotation = 1.0f; - s32 delay = 1; + s32 delay = ANM2_FRAME_DELAY_MIN; + s32 atFrame = -1; + s32 eventID = -1; vec2 crop = {0.0f, 0.0f}; vec2 pivot = {0.0f, 0.0f}; vec2 position = {0.0f, 0.0f}; @@ -161,39 +158,21 @@ struct Anm2Frame vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; }; -struct Anm2LayerAnimation -{ - bool isVisible = true; - std::vector frames; -}; - -struct Anm2NullAnimation -{ - bool isVisible = true; - std::vector frames; -}; - -struct Anm2RootAnimation +struct Anm2Item { bool isVisible = true; std::vector frames; }; -struct Anm2Triggers -{ - bool isVisible = true; - std::vector items; -}; - struct Anm2Animation { s32 frameNum = ANM2_FRAME_NUM_MIN; char name[ANM2_STRING_MAX] = STRING_ANM2_NEW_ANIMATION; bool isLoop = true; - Anm2RootAnimation rootAnimation; - std::map layerAnimations; - std::map nullAnimations; - Anm2Triggers triggers; + Anm2Item rootAnimation; + std::map layerAnimations; + std::map nullAnimations; + Anm2Item triggers; }; struct Anm2 @@ -211,6 +190,15 @@ struct Anm2 std::map animations; }; +struct Anm2Reference +{ + Anm2Type type = ANM2_NONE; + s32 id = -1; + s32 index = -1; + + auto operator<=>(const Anm2Reference&) const = default; +}; + void anm2_layer_add(Anm2* self); void anm2_layer_remove(Anm2* self, s32 id); void anm2_null_add(Anm2* self); @@ -222,4 +210,8 @@ void anm2_created_on_set(Anm2* self); s32 anm2_animation_add(Anm2* self); void anm2_animation_remove(Anm2* self, s32 id); void anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const char* path, s32 id); -bool anm2_frame_from_time(Anm2* self, Anm2Animation* animation, Anm2Frame* frame, Anm2AnimationType type, s32 id, f32 time); +Anm2Animation* anm2_animation_from_id(Anm2* self, s32 animationID); +Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference, s32 animationID); +Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference, s32 animationID); +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 animationID, s32 time); +void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, s32 animationID, f32 time); \ No newline at end of file diff --git a/src/dialog.cpp b/src/dialog.cpp index bb85de2..48c4a52 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -19,9 +19,10 @@ _dialog_callback(void* userdata, const char* const* filelist, s32 filter) } void -dialog_init(Dialog* self, Anm2* anm2, Resources* resources, SDL_Window* window) +dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window) { self->anm2 = anm2; + self->reference = reference; self->resources = resources; self->window = window; } @@ -76,6 +77,7 @@ dialog_tick(Dialog* self) switch (self->type) { case DIALOG_ANM2_OPEN: + *self->reference = Anm2Reference{}; resources_textures_free(self->resources); anm2_deserialize(self->anm2, self->resources, self->path); window_title_from_anm2_set(self->window, self->anm2); diff --git a/src/dialog.h b/src/dialog.h index ab7bbac..5031557 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -26,6 +26,7 @@ enum DialogType struct Dialog { Anm2* anm2 = NULL; + Anm2Reference* reference = NULL; Resources* resources = NULL; SDL_Window* window = NULL; s32 replaceID = -1; @@ -34,7 +35,7 @@ struct Dialog bool isSelected = false; }; -void dialog_init(Dialog* self, Anm2* anm2, Resources* resources, SDL_Window* window); +void dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window); void dialog_anm2_open(Dialog* self); void dialog_png_open(Dialog* self); void dialog_png_replace(Dialog* self); diff --git a/src/editor.cpp b/src/editor.cpp index 353329c..7db7bc4 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,8 +1,8 @@ #include "editor.h" -static void _editor_grid_set(Editor* self); +static s32 _editor_grid_set(Editor* self); -static void +static s32 _editor_grid_set(Editor* self) { std::vector vertices; @@ -34,19 +34,32 @@ _editor_grid_set(Editor* self) vertices.push_back(normY); } - self->gridVertexCount = (s32)vertices.size(); - glBindVertexArray(self->gridVAO); glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + + return (s32)vertices.size(); } void -editor_init(Editor* self, Resources* resources, Settings* settings) +editor_init +( + Editor* self, + Anm2* anm2, + Anm2Reference* reference, + s32* animationID, + s32* spritesheetID, + Resources* resources, + Settings* settings +) { + self->anm2 = anm2; + self->reference = reference; + self->animationID = animationID; + self->spritesheetID = spritesheetID; self->resources = resources; self->settings = settings; @@ -140,9 +153,9 @@ editor_draw(Editor* self) glClear(GL_COLOR_BUFFER_BIT); - if (self->spritesheetID > -1) + if (*self->spritesheetID > -1) { - Texture* texture = &self->resources->textures[self->spritesheetID]; + Texture* texture = &self->resources->textures[*self->spritesheetID]; glm::mat4 spritesheetTransform = editorTransform; glm::vec2 ndcScale = glm::vec2(texture->size.x, texture->size.y) / (EDITOR_SIZE * 0.5f); @@ -186,12 +199,16 @@ editor_draw(Editor* self) glUseProgram(0); } - if (self->isFrame) + Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + + /* Draw the layer frame's crop and pivot */ + if (frame && self->reference->type == ANM2_LAYER) { + /* Rect */ glm::mat4 rectTransform = editorTransform; - glm::vec2 rectNDCPos = self->frame.crop / (EDITOR_SIZE / 2.0f); - glm::vec2 rectNDCScale = self->frame.size / (EDITOR_SIZE * 0.5f); + glm::vec2 rectNDCPos = frame->crop / (EDITOR_SIZE / 2.0f); + glm::vec2 rectNDCScale = frame->size / (EDITOR_SIZE / 2.0f); rectTransform = glm::translate(rectTransform, glm::vec3(rectNDCPos, 0.0f)); rectTransform = glm::scale(rectTransform, glm::vec3(rectNDCScale, 1.0f)); @@ -208,9 +225,10 @@ editor_draw(Editor* self) glBindVertexArray(0); glUseProgram(0); + /* Pivot */ glm::mat4 pivotTransform = editorTransform; - glm::vec2 pivotNDCPos = self->frame.pivot / (EDITOR_SIZE / 2.0f); - glm::vec2 pivotNDCScale = ATLAS_SIZES[TEXTURE_PIVOT] / (EDITOR_SIZE * 0.5f); + glm::vec2 pivotNDCPos = ((frame->crop + frame->pivot) - (EDITOR_PIVOT_SIZE / 2.0f)) / (EDITOR_SIZE / 2.0f); + glm::vec2 pivotNDCScale = EDITOR_PIVOT_SIZE / (EDITOR_SIZE / 2.0f); pivotTransform = glm::translate(pivotTransform, glm::vec3(pivotNDCPos, 0.0f)); pivotTransform = glm::scale(pivotTransform, glm::vec3(pivotNDCScale, 1.0f)); @@ -244,16 +262,21 @@ editor_draw(Editor* self) if (self->settings->editorIsGrid) { - if - ( - (ivec2(self->settings->editorGridSizeX, self->settings->editorGridSizeY) != self->oldGridSize) || - (ivec2(self->settings->editorGridOffsetX, self->settings->editorGridOffsetY) != self->oldGridOffset) - ) - _editor_grid_set(self); + static ivec2 previousGridSize = {-1, -1}; + static ivec2 previousGridOffset = {-1, -1}; + static s32 gridVertexCount = -1; + ivec2 gridSize = ivec2(self->settings->editorGridSizeX, self->settings->editorGridSizeY); + ivec2 gridOffset = ivec2(self->settings->editorGridOffsetX, self->settings->editorGridOffsetY); + + if (previousGridSize != gridSize || previousGridOffset != gridOffset) + { + gridVertexCount = _editor_grid_set(self); + previousGridSize = gridSize; + previousGridOffset = gridOffset; + } glUseProgram(shaderLine); glBindVertexArray(self->gridVAO); - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(editorTransform)); glUniform4f @@ -262,7 +285,7 @@ editor_draw(Editor* self) self->settings->editorGridColorR, self->settings->editorGridColorG, self->settings->editorGridColorB, self->settings->editorGridColorA ); - glDrawArrays(GL_LINES, 0, self->gridVertexCount); + glDrawArrays(GL_LINES, 0, gridVertexCount); glBindVertexArray(0); glUseProgram(0); @@ -275,8 +298,6 @@ void editor_tick(Editor* self) { self->settings->editorZoom = CLAMP(self->settings->editorZoom, EDITOR_ZOOM_MIN, EDITOR_ZOOM_MAX); - self->oldGridSize = glm::vec2(self->settings->editorGridSizeX, self->settings->editorGridSizeY); - self->oldGridOffset = glm::vec2(self->settings->editorGridOffsetX, self->settings->editorGridOffsetY); } void diff --git a/src/editor.h b/src/editor.h index fe86b4c..e272071 100644 --- a/src/editor.h +++ b/src/editor.h @@ -13,12 +13,17 @@ #define EDITOR_GRID_OFFSET_MAX 100 static const vec2 EDITOR_SIZE = {5000, 5000}; +static const vec2 EDITOR_PIVOT_SIZE = {4, 4}; static const vec4 EDITOR_TEXTURE_TINT = COLOR_OPAQUE; static const vec4 EDITOR_BORDER_TINT = COLOR_OPAQUE; static const vec4 EDITOR_FRAME_TINT = COLOR_RED; struct Editor { + Anm2* anm2 = NULL; + Anm2Reference* reference = NULL; + s32* animationID = NULL; + s32* spritesheetID = NULL; Resources* resources = NULL; Settings* settings = NULL; GLuint fbo; @@ -31,16 +36,19 @@ struct Editor GLuint textureVBO; GLuint borderVAO; GLuint borderVBO; - s32 gridVertexCount = -1; - s32 spritesheetID = -1; - s32 oldSpritesheetID = -1; - ivec2 oldGridSize = {-1, -1}; - ivec2 oldGridOffset = {-1, -1}; - Anm2Frame frame; - bool isFrame = false; }; -void editor_init(Editor* self, Resources* resources, Settings* settings); +void editor_init +( + Editor* self, + Anm2* anm2, + Anm2Reference* reference, + s32* animationID, + s32* spritesheetID, + Resources* resources, + Settings* settings +); + void editor_draw(Editor* self); void editor_tick(Editor* self); void editor_free(Editor* self); \ No newline at end of file diff --git a/src/imgui.cpp b/src/imgui.cpp index 95c1579..4f9a094 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,8 +1,8 @@ #include "imgui.h" static void _imgui_tooltip(const char* tooltip); -static void _imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type); -static void _imgui_timeline_element(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type); +static void _imgui_timeline_item_frames(Imgui* self, Anm2Reference reference, s32* index); +static void _imgui_timeline_item(Imgui* self, Anm2Reference reference, s32* index); static void _imgui_timeline(Imgui* self); static void _imgui_animations(Imgui* self); static void _imgui_events(Imgui* self); @@ -19,50 +19,41 @@ static void _imgui_tooltip(const char* tooltip) ImGui::SetTooltip("%s", tooltip); } -/* Displays the element's frames */ +/* Displays the item's frames */ static void -_imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type) +_imgui_timeline_item_frames(Imgui* self, Anm2Reference reference, s32* index) { - Anm2Animation* animation = &self->anm2->animations[self->animationID]; + static s32 draggedFrameIndex = -1; + static s32 hoveredFrameIndex = -1; + ImVec2 frameStartPos; ImVec2 framePos; ImVec2 frameFinishPos; - Anm2LayerAnimation* layerAnimation = NULL; - Anm2NullAnimation* nullAnimation = NULL; - Anm2RootAnimation* rootAnimation = NULL; - Anm2Triggers* triggers = NULL; ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec4 frameColor; ImVec4 hoveredColor; ImVec4 activeColor; - void* frames = NULL; + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); + Anm2Item* item = anm2_item_from_reference(self->anm2, &reference, *self->animationID); - switch (type) + switch (reference.type) { - case ANM2_ROOT_ANIMATION: - rootAnimation = (Anm2RootAnimation*)element; - frames = &rootAnimation->frames; + case ANM2_ROOT: frameColor = IMGUI_TIMELINE_ROOT_FRAME_COLOR; hoveredColor = IMGUI_TIMELINE_ROOT_HIGHLIGHT_COLOR; activeColor = IMGUI_TIMELINE_ROOT_ACTIVE_COLOR; break; - case ANM2_LAYER_ANIMATION: - layerAnimation = (Anm2LayerAnimation*)element; - frames = &layerAnimation->frames; + case ANM2_LAYER: frameColor = IMGUI_TIMELINE_LAYER_FRAME_COLOR; hoveredColor = IMGUI_TIMELINE_LAYER_HIGHLIGHT_COLOR; activeColor = IMGUI_TIMELINE_LAYER_ACTIVE_COLOR; break; - case ANM2_NULL_ANIMATION: - nullAnimation = (Anm2NullAnimation*)element; - frames = &nullAnimation->frames; + case ANM2_NULL: frameColor = IMGUI_TIMELINE_NULL_FRAME_COLOR; hoveredColor = IMGUI_TIMELINE_NULL_HIGHLIGHT_COLOR; activeColor = IMGUI_TIMELINE_NULL_ACTIVE_COLOR; break; - case ANM2_TRIGGER: - triggers = (Anm2Triggers*)element; - frames = &triggers->items; + case ANM2_TRIGGERS: frameColor = IMGUI_TIMELINE_TRIGGERS_FRAME_COLOR; hoveredColor = IMGUI_TIMELINE_TRIGGERS_HIGHLIGHT_COLOR; activeColor = IMGUI_TIMELINE_TRIGGERS_ACTIVE_COLOR; @@ -76,13 +67,24 @@ _imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, if (animation->frameNum > 0) { ImVec2 frameListSize = {IMGUI_TIMELINE_FRAME_SIZE.x * animation->frameNum, IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE.y}; + ImVec2 mousePosRelative; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAMES, frameListSize, true); - framePos = ImGui::GetCursorPos(); + /* will deselect frame if hovering and click; but, if it's later clicked, this won't have any effect */ + if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + *self->reference = Anm2Reference{}; + + vec2 mousePos = VEC2_IMVEC2(ImGui::GetMousePos()); + vec2 windowPos = VEC2_IMVEC2(ImGui::GetWindowPos()); + + f32 scrollX = ImGui::GetScrollX(); + f32 mousePosRelativeX = mousePos.x - windowPos.x - scrollX; + + frameStartPos = ImGui::GetCursorPos(); for (s32 i = 0; i < animation->frameNum; i++) { @@ -104,94 +106,89 @@ _imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, ImGui::PopID(); } - frameFinishPos = ImGui::GetCursorPos(); - - if (type == ANM2_TRIGGER) + for (auto [i, frame] : std::views::enumerate(item->frames)) { - std::vector* elementTriggers = (std::vector*)frames; + reference.index = i; - for (auto [i, trigger] : std::views::enumerate(*elementTriggers)) + TextureType textureType; + f32 frameWidth = IMGUI_TIMELINE_FRAME_SIZE.x * frame.delay; + ImVec2 frameSize = ImVec2(frameWidth, IMGUI_TIMELINE_FRAME_SIZE.y); + + if (reference.type == ANM2_TRIGGERS) { - ImVec2 triggerPos = framePos; - triggerPos.x = framePos.x + (IMGUI_TIMELINE_FRAME_SIZE.x * trigger.atFrame); - - ImGui::SetCursorPos(triggerPos); - - ImVec4 buttonColor = self->frameIndex == i && self->frameVector == elementTriggers ? activeColor : frameColor; - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoveredColor); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor); - ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); - - ImGui::PushID(i); - if (ImGui::Button(STRING_IMGUI_TIMELINE_TRIGGER_LABEL, IMGUI_TIMELINE_FRAME_SIZE)) - { - self->frameIndex = i; - self->frameVector = elementTriggers; - self->animationType = type; - self->timelineElementIndex = *index; - } - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(4); - - ImGui::SetCursorPos(ImVec2(triggerPos.x + 1.0f, (triggerPos.y + (IMGUI_TIMELINE_FRAME_SIZE.y / 2)) - IMGUI_ICON_SMALL_SIZE.y / 2)); - - ImGui::Image(self->resources->atlas.id, IMGUI_ICON_SMALL_SIZE, IMVEC2_ATLAS_UV_GET(TEXTURE_TRIGGER)); - - ImGui::PopID(); + framePos.x = frameStartPos.x + (IMGUI_TIMELINE_FRAME_SIZE.x * frame.atFrame); + textureType = TEXTURE_TRIGGER; } - } - else - { - std::vector* elementFrames = (std::vector*)frames; + else + textureType = frame.isInterpolated ? TEXTURE_CIRCLE : TEXTURE_SQUARE; + + ImGui::SetCursorPos(framePos); - for (auto [i, frame] : std::views::enumerate(*elementFrames)) + ImVec4 buttonColor = *self->reference == reference ? activeColor : frameColor; + + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoveredColor); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor); + ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); + + ImGui::PushID(i); + + if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize)) { - TextureType textureType = frame.isInterpolated ? TEXTURE_CIRCLE : TEXTURE_SQUARE; - - f32 frameWidth = IMGUI_TIMELINE_FRAME_SIZE.x * frame.delay; - ImVec2 frameSize = ImVec2(frameWidth, IMGUI_TIMELINE_FRAME_SIZE.y); - - ImGui::SetCursorPos(framePos); - - ImVec4 buttonColor = self->frameIndex == i && self->frameVector == elementFrames ? activeColor : frameColor; - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoveredColor); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor); - ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); + s32 frameTime = (s32)(mousePosRelativeX / IMGUI_TIMELINE_FRAME_SIZE.x); - ImGui::PushID(i); + *self->reference = reference; - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize)) - { - self->frameIndex = i; - self->frameVector = elementFrames; - self->animationType = type; - self->timelineElementIndex = *index; + self->preview->time = frameTime; - if (type == ANM2_LAYER_ANIMATION) - self->spritesheetID = self->anm2->layers[*id].spritesheetID; - } - - ImGui::PopStyleVar(); - ImGui::PopStyleColor(4); - - ImGui::SetCursorPos(ImVec2(framePos.x + 1.0f, (framePos.y + (frameSize.y / 2)) - IMGUI_ICON_SMALL_SIZE.y / 2)); - - ImGui::Image(self->resources->atlas.id, IMGUI_ICON_SMALL_SIZE, IMVEC2_ATLAS_UV_GET(textureType)); - - ImGui::PopID(); - - framePos.x += frameWidth; + if (reference.type == ANM2_LAYER) + *self->spritesheetID = self->anm2->layers[reference.id].spritesheetID; } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + *self->reference = reference; + + ImGui::SetDragDropPayload(STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP, &reference, sizeof(Anm2Reference)); + ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize); + ImGui::SetCursorPos(ImVec2(1.0f, (IMGUI_TIMELINE_FRAME_SIZE.y / 2) - (TEXTURE_SIZE_SMALL.y / 2))); + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + ImGui::EndDragDropSource(); + } + + if (self->reference->id == reference.id) + { + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP)) + { + Anm2Reference checkReference = *(Anm2Reference*)payload->Data; + if (checkReference != reference) + { + self->isSwap = true; + self->swapReference = reference; + } + } + ImGui::EndDragDropTarget(); + } + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(4); + + ImGui::SetCursorPos(ImVec2(framePos.x + 1.0f, (framePos.y + (IMGUI_TIMELINE_FRAME_SIZE.y / 2)) - TEXTURE_SIZE_SMALL.y / 2)); + + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + + ImGui::PopID(); + + if (reference.type != ANM2_TRIGGERS) + framePos.x += frameWidth; } ImGui::EndChild(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); + ImGui::PopStyleVar(2); ImGui::SetCursorPosX(cursorPos.x); ImGui::SetCursorPosY(cursorPos.y + IMGUI_TIMELINE_FRAME_SIZE.y); @@ -199,316 +196,229 @@ _imgui_timeline_element_frames(Imgui* self, void* element, s32* id, s32* index, else ImGui::Dummy(IMGUI_DUMMY_SIZE); - *index = *index + 1; + (*index)++; ImGui::PopID(); } -/* Displays each element of the timeline of a selected animation */ +/* Displays each item of the timeline of a selected animation */ static void -_imgui_timeline_element(Imgui* self, void* element, s32* id, s32* index, Anm2AnimationType type) +_imgui_timeline_item(Imgui* self, Anm2Reference reference, s32* index) { - static s32 selectedSpritesheetIndex = -1; - Anm2Animation* animation = &self->anm2->animations[self->animationID]; - Anm2Layer* layer = NULL; - Anm2LayerAnimation* layerAnimation = NULL; - Anm2Null* null = NULL; - Anm2NullAnimation* nullAnimation = NULL; - Anm2RootAnimation* rootAnimation = NULL; - Anm2Triggers* triggers = NULL; - void* frames = NULL; - ImVec2 framePos; - ImVec2 frameFinishPos; - TextureType textureType = TEXTURE_ERROR; - bool isSelected = *index == self->timelineElementIndex; - bool isArrows = false; - bool* isShowRect = NULL; - bool* isVisible = NULL; - char nameBuffer[ANM2_STRING_MAX] = STRING_EMPTY; - char nameVisible[ANM2_STRING_FORMATTED_MAX] = STRING_EMPTY; - char* namePointer = NULL; - s32* spritesheetID = NULL; - bool isChangeable = type != ANM2_ROOT_ANIMATION && type != ANM2_TRIGGER; - f32 cursorPosY = ImGui::GetCursorPosY(); - ImVec4 color; + static s32 textEntryItemIndex = -1; + static s32 textEntrySpritesheetIndex = -1; - switch (type) + TextureType textureType = TEXTURE_ERROR; + bool isArrows = false; + s32* spritesheetID = NULL; + bool* isShowRect = NULL; + char* namePointer = NULL; + Anm2Null* null = NULL; + Anm2Layer* layer = NULL; + char nameBuffer[ANM2_STRING_MAX] = STRING_EMPTY; + char nameVisible[ANM2_STRING_FORMATTED_MAX] = STRING_EMPTY; + + bool isChangeable = reference.type != ANM2_ROOT && reference.type != ANM2_TRIGGERS; + bool isSelected = self->reference->id == reference.id && self->reference->type == reference.type; + bool isTextEntry = textEntryItemIndex == *index && isChangeable; + bool isSpritesheetTextEntry = textEntrySpritesheetIndex == *index; + + f32 cursorPosY = ImGui::GetCursorPosY(); + ImVec4 color; + + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); + Anm2Item* item = anm2_item_from_reference(self->anm2, &reference, *self->animationID); + + switch (reference.type) + { + case ANM2_ROOT: + textureType = TEXTURE_ROOT; + color = IMGUI_TIMELINE_ROOT_COLOR; + strncpy(nameVisible, STRING_IMGUI_TIMELINE_ROOT, ANM2_STRING_FORMATTED_MAX); + break; + case ANM2_LAYER: + textureType = TEXTURE_LAYER; + color = IMGUI_TIMELINE_LAYER_COLOR; + layer = &self->anm2->layers[reference.id]; + spritesheetID = &layer->spritesheetID; + namePointer = layer->name; + snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); + snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, reference.id, namePointer); + break; + case ANM2_NULL: + textureType = TEXTURE_NULL; + color = IMGUI_TIMELINE_NULL_COLOR; + null = &self->anm2->nulls[reference.id]; + isShowRect = &null->isShowRect; + namePointer = null->name; + snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); + snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, reference.id, namePointer); + break; + case ANM2_TRIGGERS: + textureType = TEXTURE_TRIGGERS; + color = IMGUI_TIMELINE_TRIGGERS_COLOR; + strncpy(nameVisible, STRING_IMGUI_TIMELINE_TRIGGERS, ANM2_STRING_FORMATTED_MAX); + break; + default: + break; + } + + ImGui::PushID(*index); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, color); + ImGui::BeginChild(nameVisible, IMGUI_TIMELINE_ELEMENT_SIZE, true, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + ImGui::PopStyleColor(); + + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + + ImGui::SameLine(); + + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_NAME_LABEL, IMGUI_TIMELINE_ELEMENT_NAME_SIZE); + + if (isTextEntry) + { + if (ImGui::InputText(STRING_IMGUI_TIMELINE_ANIMATION_LABEL, nameBuffer, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) { - case ANM2_ROOT_ANIMATION: - rootAnimation = (Anm2RootAnimation*)element; - textureType = TEXTURE_ROOT; - strncpy(nameVisible, STRING_IMGUI_TIMELINE_ROOT, ANM2_STRING_FORMATTED_MAX); - isVisible = &rootAnimation->isVisible; - frames = &rootAnimation->frames; - color = IMGUI_TIMELINE_ROOT_COLOR; - break; - case ANM2_LAYER_ANIMATION: - layerAnimation = (Anm2LayerAnimation*)element; - layer = &self->anm2->layers[*id]; - textureType = TEXTURE_LAYER; - isVisible = &layerAnimation->isVisible; - spritesheetID = &layer->spritesheetID; - namePointer = layer->name; - snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); - snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, *id, namePointer); - frames = &layerAnimation->frames; - color = IMGUI_TIMELINE_LAYER_COLOR; - break; - case ANM2_NULL_ANIMATION: - nullAnimation = (Anm2NullAnimation*)element; - null = &self->anm2->nulls[*id]; - textureType = TEXTURE_NULL; - isVisible = &nullAnimation->isVisible; - isShowRect = &null->isShowRect; - namePointer = null->name; - snprintf(nameBuffer, ANM2_STRING_MAX, "%s", namePointer); - snprintf(nameVisible, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_ELEMENT_FORMAT, *id, namePointer); - frames = &nullAnimation->frames; - color = IMGUI_TIMELINE_NULL_COLOR; - break; - case ANM2_TRIGGER: - triggers = (Anm2Triggers*)element; - textureType = TEXTURE_TRIGGER; - strncpy(nameVisible, STRING_IMGUI_TIMELINE_TRIGGERS, ANM2_STRING_FORMATTED_MAX); - isVisible = &triggers->isVisible; - frames = &triggers->items; - color = IMGUI_TIMELINE_TRIGGERS_COLOR; - break; - default: - break; + strncpy(namePointer, nameBuffer, ANM2_STRING_MAX); + textEntryItemIndex = -1; } - ImGui::PushID(*index); + if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + textEntryItemIndex = -1; + } + else + { + if (ImGui::Selectable(nameVisible, isSelected)) + { + *self->reference = reference; + self->reference->index = -1; + + if (reference.type == ANM2_LAYER) + *self->spritesheetID = self->anm2->layers[reference.id].spritesheetID; + } + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + textEntryItemIndex = *index; + } + + if (isChangeable && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + *self->reference = reference; + self->reference->index = -1; ImGui::PushStyleColor(ImGuiCol_ChildBg, color); - ImGui::BeginChild(nameVisible, IMGUI_TIMELINE_ELEMENT_SIZE, true, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); + ImGui::SetDragDropPayload(STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP, &reference, sizeof(Anm2Frame)); ImGui::PopStyleColor(); - - /* Shift arrows */ - if (isChangeable) + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + ImGui::SameLine(); + ImGui::Text(nameVisible); + ImGui::EndDragDropSource(); + } + + if (self->reference->type == reference.type && ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP)) { - bool isSwap = false; - bool isReversed = (type == ANM2_LAYER_ANIMATION); - - auto arrows_draw = [&](auto it, auto begin, auto end, - auto& map, - auto& allAnimationMaps, // vector of maps across all animations - bool* didSwap, bool isReversed) + Anm2Reference checkReference = *(Anm2Reference*)payload->Data; + if (checkReference != reference) { - bool canMoveUp = isReversed ? (std::next(it) != end) : (it != begin); - bool canMoveDown = isReversed ? (it != begin) : (std::next(it) != end); - - isArrows = canMoveUp || canMoveDown; - - if (isArrows) - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SHIFT_ARROWS_LABEL, IMGUI_TIMELINE_SHIFT_ARROWS_SIZE); - - if (canMoveUp) - { - auto target = isReversed ? std::next(it) : std::prev(it); - if - ( - target != map.end() && - ImGui::ImageButton(STRING_IMGUI_TIMELINE_ELEMENT_UP, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_UP]), IMVEC2_ATLAS_UV_GET(TEXTURE_UP)) - ) - { - map_swap(map, it->first, target->first); - - for (auto& animation : self->anm2->animations) - { - if (type == ANM2_LAYER_ANIMATION) - map_swap(animation.second.layerAnimations, it->first, target->first); - else if (type == ANM2_NULL_ANIMATION) - map_swap(animation.second.nullAnimations, it->first, target->first); - } - - *didSwap = true; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_UP); - } - - if (canMoveDown) - { - if (!canMoveUp) - { - ImGui::Dummy(IMGUI_ICON_BUTTON_SIZE); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.x); - } - else - ImGui::SameLine(); - - auto target = isReversed ? std::prev(it) : std::next(it); - if - ( - target != map.end() && - ImGui::ImageButton(STRING_IMGUI_TIMELINE_ELEMENT_DOWN, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_DOWN]), IMVEC2_ATLAS_UV_GET(TEXTURE_DOWN)) - ) - { - map_swap(map, it->first, target->first); - - for (auto& animation : self->anm2->animations) - { - if (type == ANM2_LAYER_ANIMATION) - map_swap(animation.second.layerAnimations, it->first, target->first); - else if (type == ANM2_NULL_ANIMATION) - map_swap(animation.second.nullAnimations, it->first, target->first); - } - - *didSwap = true; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_DOWN); - } - - if (isArrows) - { - ImGui::EndChild(); - ImGui::SameLine(); - } - }; - - if (type == ANM2_LAYER_ANIMATION) - { - auto it = std::find_if(self->anm2->layers.begin(), self->anm2->layers.end(), - [&](const auto& pair) { return &pair.second == layer; }); - - if (it != self->anm2->layers.end()) - { - arrows_draw(it, self->anm2->layers.begin(), self->anm2->layers.end(), - self->anm2->layers, self->anm2->animations, &isSwap, isReversed); - } - } - - if (type == ANM2_NULL_ANIMATION) - { - auto it = std::find_if(self->anm2->nulls.begin(), self->anm2->nulls.end(), - [&](const auto& pair) { return &pair.second == null; }); - - if (it != self->anm2->nulls.end()) - { - arrows_draw(it, self->anm2->nulls.begin(), self->anm2->nulls.end(), - self->anm2->nulls, self->anm2->animations, &isSwap, isReversed); - } + self->isSwap = true; + self->swapReference = reference; } } + ImGui::EndDragDropTarget(); + } - ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + switch (reference.type) + { + case ANM2_ROOT: + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT); + break; + case ANM2_LAYER: + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER); + break; + case ANM2_NULL: + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL); + break; + case ANM2_TRIGGERS: + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS); + break; + default: + break; + } + + ImGui::EndChild(); + + /* IsVisible */ + ImVec2 cursorPos; + TextureType visibleTextureType = item->isVisible ? TEXTURE_VISIBLE : TEXTURE_INVISIBLE; + + ImGui::SameLine(); + cursorPos = ImGui::GetCursorPos(); + ImGui::SetCursorPosX(cursorPos.x + ImGui::GetContentRegionAvail().x - IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2); + + if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_VISIBLE, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[visibleTextureType]), IMVEC2_ATLAS_UV_GET(visibleTextureType))) + item->isVisible = !item->isVisible; + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE); + + ImGui::SetCursorPos(cursorPos); + + /* Spritesheet IDs */ + if (spritesheetID) + { + char spritesheetIDName[ANM2_STRING_FORMATTED_MAX]; + + if (*spritesheetID == -1) + snprintf(spritesheetIDName, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_SPRITESHEET_UNKNOWN); + else + snprintf(spritesheetIDName, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_SPRITESHEET_FORMAT, *spritesheetID); + + ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE); + + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); ImGui::SameLine(); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_NAME_LABEL, IMGUI_TIMELINE_ELEMENT_NAME_SIZE); - - if (isSelected && isChangeable) + if (isSpritesheetTextEntry) { - if (ImGui::InputText(STRING_IMGUI_TIMELINE_ANIMATION_LABEL, nameBuffer, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - strncpy(namePointer, nameBuffer, ANM2_STRING_MAX); + if (ImGui::InputInt(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, spritesheetID, 0, 0, ImGuiInputTextFlags_None)) + textEntrySpritesheetIndex = -1; + + if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + textEntrySpritesheetIndex = -1; } else { - if (ImGui::Selectable(nameVisible, isSelected)) - { - self->frameVector = frames; - self->animationType = type; - self->timelineElementIndex = *index; + ImGui::Selectable(spritesheetIDName); - if (type == ANM2_LAYER_ANIMATION) - self->spritesheetID = *spritesheetID; - } + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + textEntrySpritesheetIndex = *index; } + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET); + + ImGui::EndChild(); + } - switch (type) - { - case ANM2_ROOT_ANIMATION: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT); - break; - case ANM2_LAYER_ANIMATION: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER); - break; - case ANM2_NULL_ANIMATION: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL); - break; - case ANM2_TRIGGER: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS); - break; - default: - break; - } + /* ShowRect */ + if (isShowRect) + { + TextureType rectTextureType = *isShowRect ? TEXTURE_RECT : TEXTURE_RECT_HIDE; - ImGui::EndChild(); - - /* IsVisible */ - if (isVisible) - { - ImVec2 cursorPos; - TextureType visibleTextureType = *isVisible ? TEXTURE_VISIBLE : TEXTURE_INVISIBLE; - - ImGui::SameLine(); - - cursorPos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(cursorPos.x + ImGui::GetContentRegionAvail().x - IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2); - - if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_VISIBLE, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[visibleTextureType]), IMVEC2_ATLAS_UV_GET(visibleTextureType))) - *isVisible = !*isVisible; - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE); - - ImGui::SetCursorPos(cursorPos); - } + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - ((IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2) * 4)); - /* Spritesheet IDs */ - if (spritesheetID) - { - char spritesheetIDName[ANM2_STRING_FORMATTED_MAX]; + if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_RECT, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[rectTextureType]), IMVEC2_ATLAS_UV_GET(rectTextureType))) + *isShowRect = !*isShowRect; - if (*spritesheetID == -1) - snprintf(spritesheetIDName, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_SPRITESHEET_UNKNOWN); - else - snprintf(spritesheetIDName, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_TIMELINE_SPRITESHEET_FORMAT, *spritesheetID); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_RECT); + } + + ImGui::EndChild(); + + (*index)++; - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE); - - ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); - ImGui::SameLine(); + ImGui::PopID(); - if (selectedSpritesheetIndex == *index) - { - if (ImGui::InputInt(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, spritesheetID, 0, 0, ImGuiInputTextFlags_None)) - selectedSpritesheetIndex = -1; - } - else - { - if (ImGui::Selectable(spritesheetIDName)) - selectedSpritesheetIndex = *index; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET); - - ImGui::EndChild(); - } - - /* ShowRect */ - if (isShowRect) - { - TextureType rectTextureType = *isShowRect ? TEXTURE_RECT : TEXTURE_RECT_HIDE; - - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - ((IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2) * 4)); - - if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_RECT, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[rectTextureType]), IMVEC2_ATLAS_UV_GET(rectTextureType))) - *isShowRect = !*isShowRect; - - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_RECT); - } - - if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - { - self->timelineElementIndex = -1; - selectedSpritesheetIndex = -1; - } - - ImGui::EndChild(); - - *index = *index + 1; - - ImGui::PopID(); - - ImGui::SetCursorPosY(cursorPosY + IMGUI_TIMELINE_ELEMENT_SIZE.y); + ImGui::SetCursorPosY(cursorPosY + IMGUI_TIMELINE_ELEMENT_SIZE.y); } /* Timeline */ @@ -518,20 +428,22 @@ _imgui_timeline(Imgui* self) /* -- Timeline -- */ ImGui::Begin(STRING_IMGUI_TIMELINE); - if (self->animationID != -1) + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); + + if (animation) { ImVec2 cursorPos; ImVec2 mousePos; ImVec2 mousePosRelative; s32 index = 0; - Anm2Animation* animation = &self->anm2->animations[self->animationID]; ImVec2 frameSize = IMGUI_TIMELINE_FRAME_SIZE; ImVec2 pickerPos; ImVec2 lineStart; ImVec2 lineEnd; ImDrawList* drawList; - static f32 elementScrollX = 0; - static f32 elementScrollY = 0; + static f32 itemScrollX = 0; + static f32 itemScrollY = 0; + static bool isPickerDragging; ImVec2 frameIndicesSize = {frameSize.x * animation->frameNum, IMGUI_TIMELINE_FRAME_INDICES_SIZE.y}; s32 idDefault = 0; const char* buttonText = self->preview->isPlaying ? STRING_IMGUI_TIMELINE_PAUSE : STRING_IMGUI_TIMELINE_PLAY; @@ -545,7 +457,7 @@ _imgui_timeline(Imgui* self) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); ImGui::BeginChild(STRING_IMGUI_TIMELINE_CHILD, timelineSize, true); windowSize = ImGui::GetWindowSize(); - + cursorPos = ImGui::GetCursorPos(); drawList = ImGui::GetWindowDrawList(); @@ -554,23 +466,41 @@ _imgui_timeline(Imgui* self) ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_FRAMES, {0, 0}, false, ImGuiWindowFlags_HorizontalScrollbar); ImGui::PopStyleVar(); ImGui::PopStyleVar(); - elementScrollX = ImGui::GetScrollX(); - elementScrollY = ImGui::GetScrollY(); - - _imgui_timeline_element_frames(self, &animation->rootAnimation, &idDefault, &index, ANM2_ROOT_ANIMATION); + itemScrollX = ImGui::GetScrollX(); + itemScrollY = ImGui::GetScrollY(); + + _imgui_timeline_item_frames(self, Anm2Reference{ANM2_ROOT, 0, 0}, &index); for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) { s32 id = it->first; - Anm2LayerAnimation& layer = it->second; - _imgui_timeline_element_frames(self, &layer, &id, &index, ANM2_LAYER_ANIMATION); + _imgui_timeline_item_frames(self, Anm2Reference{ANM2_LAYER, id, 0}, &index); } for (auto & [id, null] : animation->nullAnimations) - _imgui_timeline_element_frames(self, &null, (s32*)&id, &index, ANM2_NULL_ANIMATION); + _imgui_timeline_item_frames(self, Anm2Reference{ANM2_NULL, id, 0}, &index); - _imgui_timeline_element_frames(self, &animation->triggers, &idDefault, &index, ANM2_TRIGGER); + _imgui_timeline_item_frames(self, Anm2Reference{ANM2_TRIGGERS, 0, 0}, &index); + + if (self->isSwap) + { + Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference, *self->animationID); + Anm2Frame* aFrame; + Anm2Frame* bFrame; + Anm2Frame oldFrame; + + aFrame = anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + bFrame = anm2_frame_from_reference(self->anm2, &self->swapReference, *self->animationID); + + oldFrame = *aFrame; + *aFrame = *bFrame; + *bFrame = oldFrame; + + self->isSwap = false; + self->reference->index = self->swapReference.index; + self->swapReference = Anm2Reference{}; + } ImGui::EndChild(); @@ -596,33 +526,30 @@ _imgui_timeline(Imgui* self) ImGui::PushClipRect(clipRectMin, clipRectMax, true); ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAME_INDICES, {0, IMGUI_TIMELINE_FRAME_SIZE.y}); - ImGui::SetScrollX(elementScrollX); + ImGui::SetScrollX(itemScrollX); - ImVec2 elementsRectMin = ImGui::GetWindowPos(); - ImVec2 elementsRectMax = ImVec2(elementsRectMin.x + frameIndicesSize.x, elementsRectMin.y + frameIndicesSize.y); + ImVec2 itemsRectMin = ImGui::GetWindowPos(); + ImVec2 itemsRectMax = ImVec2(itemsRectMin.x + frameIndicesSize.x, itemsRectMin.y + frameIndicesSize.y); cursorPos = ImGui::GetCursorScreenPos(); mousePos = ImGui::GetMousePos(); mousePosRelative = ImVec2(ImGui::GetMousePos().x - cursorPos.x, ImGui::GetMousePos().y - cursorPos.y); isMouseInElementsRegion = - mousePos.x >= elementsRectMin.x && mousePos.x < elementsRectMax.x && - mousePos.y >= elementsRectMin.y && mousePos.y < elementsRectMax.y; + mousePos.x >= itemsRectMin.x && mousePos.x < itemsRectMax.x && + mousePos.y >= itemsRectMin.y && mousePos.y < itemsRectMax.y; - if (isMouseInElementsRegion && ImGui::IsMouseDown(0) && !self->preview->isPlaying) + if ((isMouseInElementsRegion && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) || isPickerDragging) { - if (mousePosRelative.x >= 0 && mousePosRelative.x < frameIndicesSize.x) - { - s32 index = (s32)(mousePosRelative.x / frameSize.x); - if (index >= 0 && index < animation->frameNum) - self->preview->time = (f32)index; - } - else if (mousePosRelative.x < 0) - self->preview->time = 0; - else - self->preview->time = (f32)(animation->frameNum - 1); + s32 frameIndex = CLAMP((s32)(mousePosRelative.x / frameSize.x), 0, (f32)(animation->frameNum - 1)); + self->preview->time = frameIndex; + + isPickerDragging = true; } + if (isPickerDragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + isPickerDragging = false; + for (s32 i = 0; i < animation->frameNum; i++) { ImVec2 imagePos = ImGui::GetCursorScreenPos(); @@ -677,7 +604,8 @@ _imgui_timeline(Imgui* self) IMVEC2_ATLAS_UV_GET(TEXTURE_PICKER) ); - drawList->AddRectFilled( + drawList->AddRectFilled + ( ImVec2(lineStart.x - IMGUI_PICKER_LINE_SIZE, lineStart.y), ImVec2(lineStart.x + IMGUI_PICKER_LINE_SIZE, lineEnd.y), IMGUI_PICKER_LINE_COLOR @@ -701,26 +629,47 @@ _imgui_timeline(Imgui* self) ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_LIST, IMGUI_TIMELINE_ELEMENT_LIST_SIZE, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::PopStyleVar(); ImGui::PopStyleVar(); - ImGui::SetScrollY(elementScrollY); + ImGui::SetScrollY(itemScrollY); - _imgui_timeline_element(self, &animation->rootAnimation, &idDefault, &index, ANM2_ROOT_ANIMATION); + index = 0; + + _imgui_timeline_item(self, Anm2Reference{ANM2_ROOT, 0, 0}, &index); for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) { s32 id = it->first; - Anm2LayerAnimation& layer = it->second; - - _imgui_timeline_element(self, &layer, &id, &index, ANM2_LAYER_ANIMATION); + _imgui_timeline_item(self, Anm2Reference{ANM2_LAYER, id, 0}, &index); } for (auto & [id, null] : animation->nullAnimations) - _imgui_timeline_element(self, &null, (s32*)&id, &index, ANM2_NULL_ANIMATION); + _imgui_timeline_item(self, Anm2Reference{ANM2_NULL, id, 0}, &index); - _imgui_timeline_element(self, &animation->triggers, &idDefault, &index, ANM2_TRIGGER); + _imgui_timeline_item(self, Anm2Reference{ANM2_TRIGGERS, 0, 0}, &index); + + if (self->isSwap) + { + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); + + switch (self->reference->type) + { + case ANM2_LAYER: + map_swap(self->anm2->layers, self->reference->id, self->swapReference.id); + map_swap(animation->layerAnimations, self->reference->id, self->swapReference.id); + break; + case ANM2_NULL: + map_swap(self->anm2->nulls, self->reference->id, self->swapReference.id); + map_swap(animation->nullAnimations, self->reference->id, self->swapReference.id); + break; + default: + break; + } + + self->isSwap = false; + self->reference->id = self->swapReference.id; + self->swapReference = Anm2Reference{}; + } ImGui::EndChild(); - - ImGui::EndChild(); /* Buttons */ @@ -747,142 +696,41 @@ _imgui_timeline(Imgui* self) ImGui::SameLine(); - if - ( - ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_REMOVE) && - self->animationID != -1 - ) + if (ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_REMOVE)) { - switch (self->animationType) + switch (self->reference->type) { - case ANM2_LAYER_ANIMATION: - anm2_layer_remove(self->anm2, self->animationID); + case ANM2_LAYER: + anm2_layer_remove(self->anm2, self->reference->id); break; - case ANM2_NULL_ANIMATION: - anm2_null_remove(self->anm2, self->animationID); + case ANM2_NULL: + anm2_null_remove(self->anm2, self->reference->id); break; default: break; } - self->animationID = -1; - self->timelineElementIndex = -1; - self->animationType = ANM2_NONE; + *self->reference = Anm2Reference{}; } _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE); ImGui::SameLine(); if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_ADD)) - { - if (self->frameVector) - { - Anm2Animation* animation = &self->anm2->animations[self->animationID]; - std::vector* addFrameVector; - std::vector* addTriggerVector; - Anm2Frame frame = Anm2Frame{}; - Anm2Trigger trigger = Anm2Trigger{}; - s32 delay = 0; - s32 frameDelayCount = 0; - s32 index = -1; - - switch (self->animationType) - { - case ANM2_ROOT_ANIMATION: - case ANM2_LAYER_ANIMATION: - case ANM2_NULL_ANIMATION: - addFrameVector = (std::vector*)self->frameVector; - for (auto& frameCheck : *addFrameVector) - frameDelayCount += frameCheck.delay; - - if (frameDelayCount + frame.delay > animation->frameNum) - break; - - if (self->frameIndex > -1) - { - frame = (*addFrameVector)[self->frameIndex]; - - if (frameDelayCount + frame.delay > animation->frameNum) - frame.delay = animation->frameNum - frameDelayCount; - - if (frame.delay <= 0) - break; - - index = self->frameIndex + 1; - - addFrameVector->insert(addFrameVector->begin() + index, frame); - } - else - { - index = (s32)addFrameVector->size(); - addFrameVector->push_back(frame); - } - - self->frameIndex = index; - break; - case ANM2_TRIGGER: - addTriggerVector = (std::vector*)self->frameVector; - - if (self->preview->time > -1) - { - index = (s32)self->preview->time; - bool isTrigger = false; - - for (const auto & existingTrigger : *addTriggerVector) - if (existingTrigger.atFrame == index) - { - isTrigger = true; - break; - } - - if (isTrigger) - break; - - trigger.atFrame = index; - addTriggerVector->push_back(trigger); - - self->frameIndex = index; - } - else - break; - - break; - default: - break; - } - } - } + anm2_frame_add(self->anm2, self->reference, *self->animationID, (s32)self->preview->time); _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD); ImGui::SameLine(); if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_REMOVE)) { - if (self->frameVector && self->frameIndex > -1) + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + + if (frame) { - std::vector* removeFrameVector; - std::vector* removeTriggerVector; - - switch (self->animationType) - { - case ANM2_ROOT_ANIMATION: - case ANM2_LAYER_ANIMATION: - case ANM2_NULL_ANIMATION: - removeFrameVector = (std::vector*)self->frameVector; - removeFrameVector->erase(removeFrameVector->begin() + self->frameIndex); - break; - case ANM2_TRIGGER: - removeTriggerVector = (std::vector*)self->frameVector; - removeTriggerVector->erase(removeTriggerVector->begin() + self->frameIndex); - break; - break; - default: - break; - } - - self->frameIndex = -1; - self->frameVector = NULL; - self->animationType = ANM2_NONE; + Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference, *self->animationID); + item->frames.erase(item->frames.begin() + index); + self->reference->index = -1; } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE); @@ -962,19 +810,12 @@ _imgui_taskbar(Imgui* self) { if (ImGui::Selectable(STRING_IMGUI_FILE_NEW)) { - self->animationID = -1; - self->spritesheetID = -1; - self->eventID = -1; + *self->reference = Anm2Reference{}; anm2_new(self->anm2); } if (ImGui::Selectable(STRING_IMGUI_FILE_OPEN)) - { - self->animationID = -1; - self->eventID = -1; - self->spritesheetID = -1; dialog_anm2_open(self->dialog); - } if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE)) { @@ -985,9 +826,7 @@ _imgui_taskbar(Imgui* self) } if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE_AS)) - { dialog_anm2_save(self->dialog); - } ImGui::EndPopup(); } @@ -1016,7 +855,7 @@ _imgui_tools(Imgui* self) if (i > 0 && i % buttonsPerRow != 0) ImGui::SameLine(); - ImVec4 buttonColor = self->tool == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; + ImVec4 buttonColor = self->tool->type == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); switch (i) @@ -1051,7 +890,7 @@ _imgui_tools(Imgui* self) } if (ImGui::ImageButton(string, self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType))) - self->tool = (ToolType)i; + self->tool->type = (ToolType)i; _imgui_tooltip(tooltip); @@ -1059,12 +898,15 @@ _imgui_tools(Imgui* self) } ImGui::End(); + } /* Animations */ static void _imgui_animations(Imgui* self) { + static s32 textEntryAnimationID = -1; + ImGui::Begin(STRING_IMGUI_ANIMATIONS); /* Iterate through all animations, can be selected and names can be edited */ @@ -1072,7 +914,8 @@ _imgui_animations(Imgui* self) { char name[ANM2_STRING_FORMATTED_MAX]; char oldName[ANM2_STRING_MAX]; - bool isSelected = self->animationID == id; + bool isSelected = *self->animationID == id; + bool isTextEntry = textEntryAnimationID == id; /* Distinguish default animation */ if (strcmp(animation.name, self->anm2->defaultAnimation) == 0) @@ -1085,37 +928,58 @@ _imgui_animations(Imgui* self) ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_ANIMATION)); ImGui::SameLine(); - if (isSelected) + if (isTextEntry) { if (ImGui::InputText(STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL, animation.name, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - { - strncpy(self->anm2->defaultAnimation, animation.name, ANM2_STRING_MAX); - self->animationID = -1; - } + textEntryAnimationID = -1; + + if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + textEntryAnimationID = -1; } else { if (ImGui::Selectable(name, isSelected)) { - self->animationID = id; - self->frameIndex = -1; - self->frameVector = NULL; - self->animationType = ANM2_NONE; + *self->animationID = id; + *self->reference = Anm2Reference{}; self->preview->isPlaying = false; self->preview->time = 0.0f; } + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + textEntryAnimationID = id; + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(STRING_IMGUI_ANIMATIONS_DRAG_DROP, &id, sizeof(s32)); + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_ANIMATION)); + ImGui::SameLine(); + ImGui::Text(name); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_ANIMATIONS_DRAG_DROP)) + { + s32 sourceID = *(s32*)payload->Data; + if (sourceID != id) + map_swap(self->anm2->animations, sourceID, id); + } + ImGui::EndDragDropTarget(); + } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SELECT); ImGui::PopID(); } - + if (ImGui::Button(STRING_IMGUI_ANIMATIONS_ADD)) { - bool isDefault = self->anm2->animations.size() == 0; /* first animation is default automatically */ + bool isDefault = (s32)self->anm2->animations.size() == 0; /* first animation is default automatically */ s32 id = anm2_animation_add(self->anm2); - self->animationID = id; + *self->animationID = id; if (isDefault) strncpy(self->anm2->defaultAnimation, self->anm2->animations[id].name, ANM2_STRING_MAX); @@ -1127,10 +991,10 @@ _imgui_animations(Imgui* self) /* Remove */ if (ImGui::Button(STRING_IMGUI_ANIMATIONS_REMOVE)) { - if (self->animationID != -1) + if (*self->animationID > -1) { - anm2_animation_remove(self->anm2, self->animationID); - self->animationID = -1; + anm2_animation_remove(self->anm2, *self->animationID); + *self->animationID = -1; } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE); @@ -1140,11 +1004,11 @@ _imgui_animations(Imgui* self) /* Duplicate */ if (ImGui::Button(STRING_IMGUI_ANIMATIONS_DUPLICATE)) { - if (self->animationID > -1) + if (*self->animationID > -1) { s32 id = map_next_id_get(self->anm2->animations); - self->anm2->animations.insert({id, self->anm2->animations[self->animationID]}); - self->animationID = id; + self->anm2->animations.insert({id, self->anm2->animations[*self->animationID]}); + *self->animationID = id; } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE); @@ -1154,13 +1018,13 @@ _imgui_animations(Imgui* self) /* Set as default */ if (ImGui::Button(STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT)) { - if (self->animationID > -1) - strncpy(self->anm2->defaultAnimation, self->anm2->animations[self->animationID].name, ANM2_STRING_MAX); + if (*self->animationID > -1) + strncpy(self->anm2->defaultAnimation, self->anm2->animations[*self->animationID].name, ANM2_STRING_MAX); } _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - self->animationID = -1; + *self->animationID = -1; ImGui::End(); } @@ -1169,13 +1033,17 @@ _imgui_animations(Imgui* self) static void _imgui_events(Imgui* self) { + static s32 selectedEventID = -1; + static s32 textEntryEventID = -1; + ImGui::Begin(STRING_IMGUI_EVENTS); /* Iterate through all events, can be selected and names can be edited */ for (auto & [id, event] : self->anm2->events) { char eventString[ANM2_STRING_FORMATTED_MAX]; - bool isSelected; + bool isSelected = selectedEventID == id; + bool isTextEntry = textEntryEventID == id; snprintf(eventString, ANM2_STRING_FORMATTED_MAX, STRING_IMGUI_EVENT_FORMAT, (s32)id, event.name); @@ -1184,29 +1052,55 @@ _imgui_events(Imgui* self) ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_EVENT]), IMVEC2_ATLAS_UV_GET(TEXTURE_EVENT)); ImGui::SameLine(); - isSelected = self->eventID == id; - - if (isSelected) + if (isTextEntry) { - if (ImGui::InputText(STRING_IMGUI_EVENTS_EVENT_LABEL, event.name, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - self->eventID = -1; + if (ImGui::InputText(STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL, event.name, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) + { + selectedEventID = -1; + textEntryEventID = -1; + } + + if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + textEntryEventID = -1; } else { if (ImGui::Selectable(eventString, isSelected)) - self->eventID = id; - } + selectedEventID = id; + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + textEntryEventID = id; + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(STRING_IMGUI_EVENTS_DRAG_DROP, &id, sizeof(s32)); + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_EVENT)); + ImGui::SameLine(); + ImGui::Text(eventString); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_EVENTS_DRAG_DROP)) + { + s32 sourceID = *(s32*)payload->Data; + if (sourceID != id) + map_swap(self->anm2->events, sourceID, id); + } + ImGui::EndDragDropTarget(); + } + } _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_SELECT); ImGui::PopID(); } - + if (ImGui::Button(STRING_IMGUI_EVENTS_ADD)) { s32 id = map_next_id_get(self->anm2->events); self->anm2->events[id] = Anm2Event{}; - self->eventID = id; + selectedEventID = id; } _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_ADD); @@ -1214,16 +1108,16 @@ _imgui_events(Imgui* self) if (ImGui::Button(STRING_IMGUI_EVENTS_REMOVE)) { - if (self->eventID != -1) + if (selectedEventID != -1) { - self->anm2->events.erase(self->eventID); - self->eventID = -1; + self->anm2->events.erase(selectedEventID); + selectedEventID = -1; } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_REMOVE); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - self->eventID = -1; + selectedEventID = -1; ImGui::End(); } @@ -1232,22 +1126,16 @@ _imgui_events(Imgui* self) static void _imgui_spritesheets(Imgui* self) { + static s32 selectedSpritesheetID = -1; + ImGui::Begin(STRING_IMGUI_SPRITESHEETS); for (auto [id, spritesheet] : self->anm2->spritesheets) { ImVec2 spritesheetPreviewSize = IMGUI_SPRITESHEET_PREVIEW_SIZE; char spritesheetString[ANM2_STRING_FORMATTED_MAX]; - bool isSelected = false; + bool isSelected = selectedSpritesheetID == id; Texture* texture = &self->resources->textures[id]; - - f32 spritesheetAspect = (f32)self->resources->textures[id].size.x / self->resources->textures[id].size.y; - - if ((IMGUI_SPRITESHEET_PREVIEW_SIZE.x / IMGUI_SPRITESHEET_PREVIEW_SIZE.y) > spritesheetAspect) - spritesheetPreviewSize.x = IMGUI_SPRITESHEET_PREVIEW_SIZE.y * spritesheetAspect; - else - spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW_SIZE.x / spritesheetAspect; - snprintf(spritesheetString, ANM2_PATH_FORMATTED_MAX, STRING_IMGUI_SPRITESHEET_FORMAT, (s32)id, spritesheet.path); ImGui::BeginChild(spritesheetString, IMGUI_SPRITESHEET_SIZE, true, ImGuiWindowFlags_None); @@ -1257,11 +1145,40 @@ _imgui_spritesheets(Imgui* self) ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); ImGui::SameLine(); - isSelected = self->spritesheetID == id; if (ImGui::Selectable(spritesheetString, isSelected)) - self->spritesheetID = id; + selectedSpritesheetID = id; _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_SELECT); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(STRING_IMGUI_SPRITESHEETS_DRAG_DROP, &id, sizeof(s32)); + ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); + ImGui::SameLine(); + ImGui::Text(spritesheetString); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_SPRITESHEETS_DRAG_DROP)) + { + s32 sourceID = *(s32*)payload->Data; + if (sourceID != id) + { + map_swap(self->anm2->spritesheets, sourceID, id); + map_swap(self->resources->textures, sourceID, id); + } + } + ImGui::EndDragDropTarget(); + } + + f32 spritesheetAspect = (f32)self->resources->textures[id].size.x / self->resources->textures[id].size.y; + + if ((IMGUI_SPRITESHEET_PREVIEW_SIZE.x / IMGUI_SPRITESHEET_PREVIEW_SIZE.y) > spritesheetAspect) + spritesheetPreviewSize.x = IMGUI_SPRITESHEET_PREVIEW_SIZE.y * spritesheetAspect; + else + spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW_SIZE.x / spritesheetAspect; + if (texture->isInvalid) ImGui::Image(self->resources->atlas.id, IMVEC2_VEC2(ATLAS_SIZES[TEXTURE_ERROR]), IMVEC2_ATLAS_UV_GET(TEXTURE_ERROR)); else @@ -1281,11 +1198,11 @@ _imgui_spritesheets(Imgui* self) /* Remove */ if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REMOVE)) { - if (self->spritesheetID > -1) + if (selectedSpritesheetID > -1) { - self->resources->textures.erase(self->spritesheetID); - self->anm2->spritesheets.erase(self->spritesheetID); - self->spritesheetID = -1; + self->resources->textures.erase(selectedSpritesheetID); + self->anm2->spritesheets.erase(selectedSpritesheetID); + selectedSpritesheetID = -1; } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE); @@ -1295,8 +1212,8 @@ _imgui_spritesheets(Imgui* self) /* Reload */ if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_RELOAD)) { - if (self->spritesheetID > -1) - anm2_spritesheet_texture_load(self->anm2, self->resources, self->anm2->spritesheets[self->spritesheetID].path, self->spritesheetID); + if (selectedSpritesheetID > -1) + anm2_spritesheet_texture_load(self->anm2, self->resources, self->anm2->spritesheets[selectedSpritesheetID].path, selectedSpritesheetID); } _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD); ImGui::SameLine(); @@ -1304,16 +1221,16 @@ _imgui_spritesheets(Imgui* self) /* Replace */ if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REPLACE)) { - if (self->spritesheetID > -1) + if (selectedSpritesheetID > -1) { - self->dialog->replaceID = self->spritesheetID; + self->dialog->replaceID = selectedSpritesheetID; dialog_png_replace(self->dialog); } } _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE); if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - self->spritesheetID = -1; + selectedSpritesheetID = -1; ImGui::End(); } @@ -1324,7 +1241,12 @@ _imgui_animation_preview(Imgui* self) { static bool isPreviewHover = false; static bool isPreviewCenter = false; - vec2 previewPos; + static vec2 mousePos = {0, 0}; + char mousePositionString[IMGUI_POSITION_STRING_MAX]; + + memset(mousePositionString, '\0', IMGUI_POSITION_STRING_MAX); + + snprintf(mousePositionString, IMGUI_POSITION_STRING_MAX, STRING_IMGUI_ANIMATION_PREVIEW_POSITION_FORMAT, mousePos.x, mousePos.y); ImGui::Begin(STRING_IMGUI_ANIMATION_PREVIEW, NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); @@ -1355,6 +1277,38 @@ _imgui_animation_preview(Imgui* self) ImGui::SameLine(); + ImGui::SameLine(); + + /* View settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); + + /* Zoom */ + ImGui::DragFloat(STRING_IMGUI_ANIMATION_PREVIEW_ZOOM, &self->settings->previewZoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM); + + /* Center view */ + if (ImGui::Button(STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) + isPreviewCenter = true; + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW); + + /* Mouse position */ + ImGui::Text(mousePositionString); + + ImGui::EndChild(); + + ImGui::SameLine(); + + /* Background settings */ + ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); + + /* Background color */ + ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->previewBackgroundColorR, ImGuiColorEditFlags_NoInputs); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR); + + ImGui::EndChild(); + + ImGui::SameLine(); + /* Helper settings */ ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); @@ -1376,51 +1330,36 @@ _imgui_animation_preview(Imgui* self) ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT, &self->settings->previewIsShowPivot); _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT); - ImGui::EndChild(); - - ImGui::SameLine(); - - /* View settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Zoom */ - ImGui::DragFloat(STRING_IMGUI_ANIMATION_PREVIEW_ZOOM, &self->settings->previewZoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM); - - /* Center view */ - if (ImGui::Button(STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) - isPreviewCenter = true; - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW); - - ImGui::EndChild(); - - ImGui::SameLine(); - - /* Background settings */ - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - /* Background color */ - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->previewBackgroundColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR); - ImGui::EndChild(); /* Animation preview texture */ - previewPos = VEC2_IMVEC2(ImGui::GetCursorPos()); + vec2 previewPos = VEC2_IMVEC2(ImGui::GetCursorPos()); + ImGui::Image(self->preview->texture, IMVEC2_VEC2(PREVIEW_SIZE)); - - /* Panning */ + + /* Using tools when hovered */ if (ImGui::IsItemHovered()) { - Anm2Frame* frame = NULL; + vec2 windowPos = VEC2_IMVEC2(ImGui::GetWindowPos()); - if (self->frameIndex > - 1 && self->animationType != ANM2_TRIGGER) - { - std::vector* frames = (std::vector*)self->frameVector; - frame = &(*frames)[self->frameIndex]; - } + mousePos = VEC2_IMVEC2(ImGui::GetMousePos()); - switch (self->tool) + mousePos -= (windowPos + previewPos); + mousePos -= (PREVIEW_SIZE / 2.0f); + mousePos.x += self->settings->previewPanX; + mousePos.y += self->settings->previewPanY; + mousePos.x /= (self->settings->previewZoom / 100.0f); + mousePos.y /= (self->settings->previewZoom / 100.0f); + + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + + if (self->reference->type == ANM2_TRIGGERS) + frame = NULL; + + /* allow use of keybinds for tools */ + self->tool->isEnabled = true; + + switch (self->tool->type) { case TOOL_PAN: SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)); @@ -1433,25 +1372,48 @@ _imgui_animation_preview(Imgui* self) break; case TOOL_MOVE: SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE)); - if (mouse_held(&self->input->mouse, MOUSE_LEFT) && frame) + + if (frame) { - vec2 mousePos = VEC2_IMVEC2(ImGui::GetMousePos()); - vec2 windowPos = VEC2_IMVEC2(ImGui::GetWindowPos()); + if (mouse_held(&self->input->mouse, MOUSE_LEFT)) + frame->position = VEC2_IMVEC2(mousePos); - mousePos -= (windowPos + previewPos); - mousePos -= (PREVIEW_SIZE / 2.0f); - mousePos.x += self->settings->previewPanX; - mousePos.y += self->settings->previewPanY; - mousePos.x /= (self->settings->previewZoom / 100.0f); - mousePos.y /= (self->settings->previewZoom / 100.0f); + if (key_held(&self->input->keyboard, INPUT_KEYS[INPUT_LEFT])) + frame->position.x -= PREVIEW_MOVE_STEP; - frame->position = VEC2_IMVEC2(mousePos); + if (key_held(&self->input->keyboard, INPUT_KEYS[INPUT_RIGHT])) + frame->position.x += PREVIEW_MOVE_STEP; + + if (key_held(&self->input->keyboard, INPUT_KEYS[INPUT_UP])) + frame->position.y -= PREVIEW_MOVE_STEP; + + if (key_held(&self->input->keyboard, INPUT_KEYS[INPUT_DOWN])) + frame->position.y += PREVIEW_MOVE_STEP; } break; case TOOL_ROTATE: SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); - if (mouse_held(&self->input->mouse, MOUSE_LEFT) && frame) - frame->rotation += (s32)self->input->mouse.delta.x; + if (frame) + { + if (mouse_held(&self->input->mouse, MOUSE_LEFT)) + frame->rotation += (s32)self->input->mouse.delta.x; + + if + ( + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_LEFT]) || + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_UP]) || + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_ROTATE_LEFT]) + ) + frame->rotation -= PREVIEW_ROTATE_STEP; + + if + ( + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_RIGHT]) || + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_DOWN]) || + key_held(&self->input->keyboard, INPUT_KEYS[INPUT_ROTATE_RIGHT]) + ) + frame->rotation += PREVIEW_ROTATE_STEP; + } break; case TOOL_SCALE: SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE)); @@ -1465,7 +1427,7 @@ _imgui_animation_preview(Imgui* self) SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); break; default: - break; + break; }; isPreviewHover = true; @@ -1473,10 +1435,12 @@ _imgui_animation_preview(Imgui* self) /* Used to not be annoying when at lowest zoom */ self->settings->previewZoom = self->settings->previewZoom == EDITOR_ZOOM_MIN ? 0 : self->settings->previewZoom; - if (self->input->mouse.wheelDeltaY > 0) + /* Zoom in */ + if (self->input->mouse.wheelDeltaY > 0 || key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ZOOM_IN])) self->settings->previewZoom += PREVIEW_ZOOM_STEP; - if (self->input->mouse.wheelDeltaY < 0) + /* Zoom out */ + if (self->input->mouse.wheelDeltaY < 0 || key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ZOOM_OUT])) self->settings->previewZoom -= PREVIEW_ZOOM_STEP; self->settings->previewZoom = CLAMP(self->settings->previewZoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); @@ -1496,7 +1460,7 @@ _imgui_animation_preview(Imgui* self) /* Based on the preview's crop in its window, adjust the pan */ self->settings->previewPanX = -(previewWindowRectSize.x - PREVIEW_SIZE.x) / 2.0f; - self->settings->previewPanY = -((previewWindowRectSize.y - PREVIEW_SIZE.y) / 2.0f);// + (IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE.y / 2.0f); + self->settings->previewPanY = -((previewWindowRectSize.y - PREVIEW_SIZE.y) / 2.0f) + (IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE.y / 2.0f); isPreviewCenter = false; } @@ -1510,6 +1474,13 @@ _imgui_spritesheet_editor(Imgui* self) { static bool isEditorHover = false; static bool isEditorCenter = false; + static bool isCropDrag = false; + static vec2 mousePos = {0, 0}; + char mousePositionString[IMGUI_POSITION_STRING_MAX]; + + memset(mousePositionString, '\0', IMGUI_POSITION_STRING_MAX); + + snprintf(mousePositionString, IMGUI_POSITION_STRING_MAX, STRING_IMGUI_SPRITESHEET_EDITOR_POSITION_FORMAT, mousePos.x, mousePos.y); ImGui::Begin(STRING_IMGUI_SPRITESHEET_EDITOR, NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); @@ -1522,6 +1493,12 @@ _imgui_spritesheet_editor(Imgui* self) ImGui::SameLine(); + /* Grid snap */ + ImGui::Checkbox(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, &self->settings->editorIsGridSnap); + _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SNAP); + + ImGui::SameLine(); + /* Grid Color */ ImGui::ColorEdit4(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_COLOR, (f32*)&self->settings->editorGridColorR, ImGuiColorEditFlags_NoInputs); _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_COLOR); @@ -1552,6 +1529,9 @@ _imgui_spritesheet_editor(Imgui* self) isEditorCenter = true; _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_CENTER_VIEW); + /* Info position */ + ImGui::Text(mousePositionString); + ImGui::EndChild(); ImGui::SameLine(); @@ -1569,30 +1549,83 @@ _imgui_spritesheet_editor(Imgui* self) ImGui::EndChild(); + vec2 editorPos = VEC2_IMVEC2(ImGui::GetCursorPos()); ImGui::Image(self->editor->texture, IMVEC2_VEC2(EDITOR_SIZE)); - self->editor->spritesheetID = self->spritesheetID; - /* Panning + zoom */ if (ImGui::IsItemHovered()) { - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE)); + vec2 windowPos = VEC2_IMVEC2(ImGui::GetWindowPos()); + mousePos = VEC2_IMVEC2(ImGui::GetMousePos()); + + mousePos -= (windowPos + editorPos); + mousePos -= (EDITOR_SIZE / 2.0f); + mousePos.x += self->settings->editorPanX; + mousePos.y += self->settings->editorPanY; + mousePos.x /= (self->settings->editorZoom / 100.0f); + mousePos.y /= (self->settings->editorZoom / 100.0f); + isEditorHover = true; - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) + + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + + /* allow use of keybinds for tools */ + self->tool->isEnabled = true; + + if (self->reference->type != ANM2_LAYER) + frame = NULL; + + if (self->tool->type == TOOL_CROP && frame) { - self->settings->editorPanX -= self->input->mouse.delta.x; - self->settings->editorPanY -= self->input->mouse.delta.y; + SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); + + if (mouse_press(&self->input->mouse, MOUSE_LEFT)) + { + vec2 cropPosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; + + if (self->settings->editorIsGridSnap) + { + cropPosition.x = (s32)(cropPosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; + cropPosition.y = (s32)(cropPosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; + } + + frame->crop = cropPosition; + frame->size = {0, 0}; + } + else if (mouse_held(&self->input->mouse, MOUSE_LEFT)) + { + vec2 sizePosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; + + if (self->settings->editorIsGridSnap) + { + sizePosition.x = (s32)(sizePosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; + sizePosition.y = (s32)(sizePosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; + } + + frame->size = sizePosition - frame->crop; + } } - + else + { + SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)); + + if (mouse_held(&self->input->mouse, MOUSE_LEFT)) + { + self->settings->editorPanX -= self->input->mouse.delta.x; + self->settings->editorPanY -= self->input->mouse.delta.y; + } + } + /* Used to not be annoying when at lowest zoom */ self->settings->editorZoom = self->settings->editorZoom == EDITOR_ZOOM_MIN ? 0 : self->settings->editorZoom; - if (self->input->mouse.wheelDeltaY > 0) - self->settings->editorZoom += EDITOR_ZOOM_STEP; + /* Zoom in */ + if (self->input->mouse.wheelDeltaY > 0 || key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ZOOM_IN])) + self->settings->editorZoom += PREVIEW_ZOOM_STEP; - if (self->input->mouse.wheelDeltaY < 0) - self->settings->editorZoom -= EDITOR_ZOOM_STEP; + /* Zoom out */ + if (self->input->mouse.wheelDeltaY < 0 || key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ZOOM_OUT])) + self->settings->editorZoom -= PREVIEW_ZOOM_STEP; self->settings->editorZoom = CLAMP(self->settings->editorZoom, EDITOR_ZOOM_MIN, EDITOR_ZOOM_MAX); } @@ -1612,7 +1645,7 @@ _imgui_spritesheet_editor(Imgui* self) isEditorCenter = false; } - + ImGui::End(); } @@ -1621,44 +1654,39 @@ static void _imgui_frame_properties(Imgui* self) { ImGui::Begin(STRING_IMGUI_FRAME_PROPERTIES); - - if (self->frameIndex > -1) - { - Anm2Animation* animation = &self->anm2->animations[self->animationID]; + + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference, *self->animationID); + + if (frame) + { + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); - std::vector* frameVector; - std::vector* triggerVector; - Anm2Frame* frame = NULL; - Anm2Trigger* trigger = NULL; std::vector eventNames; std::vector eventIDs; static s32 selectedEventIndex = -1; - switch (self->animationType) + switch (self->reference->type) { - case ANM2_ROOT_ANIMATION: + case ANM2_ROOT: ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_ROOT); break; - case ANM2_LAYER_ANIMATION: + case ANM2_LAYER: ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_LAYER); break; - case ANM2_NULL_ANIMATION: + case ANM2_NULL: ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_NULL); break; - case ANM2_TRIGGER: + case ANM2_TRIGGERS: ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_TRIGGER); break; default: break; } - switch (self->animationType) + switch (self->reference->type) { - case ANM2_ROOT_ANIMATION: - case ANM2_NULL_ANIMATION: - frameVector = (std::vector*)self->frameVector; - frame = (Anm2Frame*)&(*frameVector)[self->frameIndex]; - + case ANM2_ROOT: + case ANM2_NULL: /* Position */ ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); @@ -1674,7 +1702,7 @@ _imgui_frame_properties(Imgui* self) /* Duration */ ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); - frame->delay = CLAMP(frame->delay, 0, animation->frameNum + 1); + frame->delay = CLAMP(frame->delay, ANM2_FRAME_DELAY_MIN, animation->frameNum + 1); /* Tint */ ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); @@ -1699,17 +1727,14 @@ _imgui_frame_properties(Imgui* self) if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_Y)) frame->scale.y = -frame->scale.y; _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_Y); - + ImGui::SameLine(); /* Interpolation */ ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); break; - case ANM2_LAYER_ANIMATION: - frameVector = (std::vector*)self->frameVector; - frame = (Anm2Frame*)&(*frameVector)[self->frameIndex]; - + case ANM2_LAYER: /* Position */ ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); @@ -1738,7 +1763,7 @@ _imgui_frame_properties(Imgui* self) ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); /* clamp delay */ - frame->delay = CLAMP(frame->delay, 0, animation->frameNum + 1); + frame->delay = CLAMP(frame->delay, ANM2_FRAME_DELAY_MIN, animation->frameNum + 1); /* Tint */ ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); @@ -1759,7 +1784,7 @@ _imgui_frame_properties(Imgui* self) if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_Y)) frame->scale.y = -frame->scale.y; _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_Y); - + /* Visible */ ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); @@ -1770,32 +1795,27 @@ _imgui_frame_properties(Imgui* self) ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); break; - case ANM2_TRIGGER: - triggerVector = (std::vector*)self->frameVector; - trigger = (Anm2Trigger*)&(*triggerVector)[self->frameIndex]; - + case ANM2_TRIGGERS: /* Events drop down; pick one! */ for (auto & [id, event] : self->anm2->events) { eventIDs.push_back(id); eventNames.push_back(event.name); - if (id == trigger->eventID) + if (id == frame->eventID) selectedEventIndex = eventIDs.size() - 1; } if (ImGui::Combo(STRING_IMGUI_FRAME_PROPERTIES_EVENT, &selectedEventIndex, eventNames.data(), eventNames.size())) { - trigger->eventID = eventIDs[selectedEventIndex]; + frame->eventID = eventIDs[selectedEventIndex]; selectedEventIndex = -1; } _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_EVENT); /* At Frame */ - ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_AT_FRAME, &trigger->atFrame); + ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_AT_FRAME, &frame->atFrame); _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_AT_FRAME); - /* clamp at frame */ - trigger->atFrame = CLAMP(trigger->atFrame, 0, animation->frameNum- 1); - + frame->atFrame = CLAMP(frame->atFrame, 0, animation->frameNum- 1); break; default: break; @@ -1807,19 +1827,23 @@ _imgui_frame_properties(Imgui* self) } -void +void imgui_init ( - Imgui* self, - Dialog* dialog, - Resources* resources, - Input* input, - Anm2* anm2, - Editor* editor, - Preview* preview, - Settings* settings, - SDL_Window* window, - SDL_GLContext* glContext + Imgui* self, + Dialog* dialog, + Resources* resources, + Input* input, + Anm2* anm2, + Anm2Reference* reference, + s32* animationID, + s32* spritesheetID, + Editor* editor, + Preview* preview, + Settings* settings, + Tool* tool, + SDL_Window* window, + SDL_GLContext* glContext ) { IMGUI_CHECKVERSION(); @@ -1828,9 +1852,13 @@ imgui_init self->resources = resources; self->input = input; self->anm2 = anm2; + self->reference = reference; + self->animationID = animationID; + self->spritesheetID = spritesheetID; self->editor = editor; self->preview = preview; self->settings = settings; + self->tool = tool; self->window = window; self->glContext = glContext; @@ -1891,19 +1919,10 @@ imgui_tick(Imgui* self) ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); + self->tool->isEnabled = false; + _imgui_taskbar(self); _imgui_dock(self); - - self->preview->animationID = self->animationID; - - if (self->frameIndex > -1 && self->animationType == ANM2_LAYER_ANIMATION) - { - std::vector* framesVector = (std::vector*)self->frameVector; - self->editor->frame = (*framesVector)[self->frameIndex]; - self->editor->isFrame = true; - } - else - self->editor->isFrame = false; } void diff --git a/src/imgui.h b/src/imgui.h index f0645b8..20192f3 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -7,6 +7,7 @@ #include "window.h" #include "input.h" #include "settings.h" +#include "tool.h" #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_ENABLE_DOCKING @@ -15,8 +16,6 @@ #include #include - -#define IMGUI_TIMELINE_ELEMENT_WIDTH 300 #define IMGUI_TIMELINE_ELEMENT_WIDTH 300 #define IMGUI_DRAG_SPEED 1.0 @@ -32,15 +31,15 @@ #define IMGUI_PICKER_LINE_COLOR IM_COL32(255, 255, 255, 255) #define IMGUI_TOOLS_WIDTH_INCREMENT -2 +#define IMGUI_POSITION_STRING_MAX 0xFF + static const vec2 IMGUI_TASKBAR_MARGINS = {8, 4}; +static const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE = {1280, 105}; static const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE = {200, 85}; static const ImVec2 IMGUI_ANIMATION_PREVIEW_POSITION = {8, 135}; -static const ImVec2 IMGUI_TIMELINE_ELEMENT_NAME_SIZE = {95, 20}; -static const ImVec2 IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE = {45, 20}; - static const ImVec2 IMGUI_SPRITESHEET_EDITOR_SETTINGS_CHILD_SIZE = {200, 85}; static const ImVec2 IMGUI_SPRITESHEET_EDITOR_SETTINGS_SIZE = {1280, 105}; @@ -52,15 +51,13 @@ static const ImVec2 IMGUI_TIMELINE_VIEWER_SIZE = {0, 40}; static const ImVec2 IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE = {0, 40}; static const ImVec2 IMGUI_TIMELINE_FRAME_INDICES_SIZE = {0, 40}; static const ImVec2 IMGUI_TIMELINE_ELEMENT_SIZE = {300, 40}; -static const ImVec2 IMGUI_TIMELINE_SHIFT_ARROWS_SIZE = {64, 40}; +static const ImVec2 IMGUI_TIMELINE_ELEMENT_NAME_SIZE = {150, 20}; +static const ImVec2 IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE = {60, 20}; static const ImVec2 IMGUI_SPRITESHEET_SIZE = {0, 150}; static const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {100, 100}; -static const ImVec2 IMGUI_ICON_SIZE = {16, 16}; -static const ImVec2 IMGUI_ICON_SMALL_SIZE = {8, 8}; -static const ImVec2 IMGUI_ICON_DUMMY_SIZE = {20, 16}; -static const ImVec2 IMGUI_ICON_BUTTON_SIZE = {24, 24}; static const ImVec2 IMGUI_IMAGE_TARGET_SIZE = {125, 125}; +static const ImVec2 IMGUI_ICON_BUTTON_SIZE = {24, 24}; static const ImVec2 IMGUI_DUMMY_SIZE = {1, 1}; static const ImVec4 IMGUI_TIMELINE_HEADER_COLOR = {0.04, 0.04, 0.04, 1.0f}; @@ -89,15 +86,6 @@ static const ImVec4 IMGUI_TIMELINE_LAYER_ACTIVE_COLOR = {1.000, 0.618, 0.324, 0. static const ImVec4 IMGUI_TIMELINE_NULL_ACTIVE_COLOR = {0.646, 0.971, 0.441, 0.75}; static const ImVec4 IMGUI_TIMELINE_TRIGGERS_ACTIVE_COLOR = {1.000, 0.618, 0.735, 0.75}; -#define TOOL_COUNT (TOOL_CROP + 1) -enum ToolType -{ - TOOL_PAN, - TOOL_MOVE, - TOOL_ROTATE, - TOOL_SCALE, - TOOL_CROP -}; struct Imgui { @@ -105,36 +93,38 @@ struct Imgui Resources* resources = NULL; Input* input = NULL; Anm2* anm2 = NULL; + Anm2Reference* reference = NULL; + s32* animationID = NULL; + s32* spritesheetID = NULL; Editor* editor = NULL; Preview* preview = NULL; + Settings* settings = NULL; + Tool* tool = NULL; SDL_Window* window = NULL; SDL_GLContext* glContext = NULL; - Settings* settings = NULL; - s32 animationID = -1; - s32 timelineElementID = -1; - s32 eventID = -1; - s32 spritesheetID = -1; - s32 timelineElementIndex = -1; - Anm2AnimationType animationType = ANM2_NONE; - ToolType tool = TOOL_PAN; - void* frameVector = NULL; - s32 frameIndex = -1; + bool isSwap = false; + Anm2Reference swapReference; }; -void imgui_init +void +imgui_init ( Imgui* self, Dialog* dialog, Resources* resources, Input* input, Anm2* anm2, + Anm2Reference* reference, + s32* animationID, + s32* spritesheetID, Editor* editor, Preview* preview, Settings* settings, + Tool* tool, SDL_Window* window, SDL_GLContext* glContext ); void imgui_tick(Imgui* self); void imgui_draw(Imgui* self); -void imgui_free(Imgui* self); +void imgui_free(Imgui* self); \ No newline at end of file diff --git a/src/input.cpp b/src/input.cpp index 1e1d94d..bb304f5 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -29,6 +29,19 @@ _mouse_tick(Mouse* self) self->oldPosition = self->position; } +static void +_keyboard_tick(Keyboard* self) +{ + const bool* state; + + memcpy(&self->previous, &self->current, sizeof(self->previous)); + memset(&self->current, '\0', sizeof(self->current)); + + state = SDL_GetKeyboardState(NULL); + + memcpy(&self->current, state, KEY_COUNT); +} + bool mouse_press(Mouse* self, MouseType type) { @@ -47,8 +60,27 @@ mouse_release(Mouse* self, MouseType type) return (!self->current[type] && self->previous[type]); } +bool +key_press(Keyboard* self, KeyType type) +{ + return (self->current[type] && !self->previous[type]); +} + +bool +key_held(Keyboard* self, KeyType type) +{ + return (self->current[type] && self->previous[type]); +} + +bool +key_release(Keyboard* self, KeyType type) +{ + return (!self->current[type] && self->previous[type]); +} + void input_tick(Input* self) { _mouse_tick(&self->mouse); + _keyboard_tick(&self->keyboard); } diff --git a/src/input.h b/src/input.h index b39aa44..fa216a3 100644 --- a/src/input.h +++ b/src/input.h @@ -9,22 +9,267 @@ enum MouseType MOUSE_RIGHT }; -#define KEY_COUNT (KEY_DELETE + 1) +#define KEY_COUNT (255) enum KeyType { - KEY_DELETE + KEY_UNKNOWN = 0, + KEY_UNKNOWN_TWO = 1, + KEY_UNKNOWN_THREE = 2, + KEY_UNKNOWN_FOUR = 3, + KEY_A = 4, + KEY_B = 5, + KEY_C = 6, + KEY_D = 7, + KEY_E = 8, + KEY_F = 9, + KEY_G = 10, + KEY_H = 11, + KEY_I = 12, + KEY_J = 13, + KEY_K = 14, + KEY_L = 15, + KEY_M = 16, + KEY_N = 17, + KEY_O = 18, + KEY_P = 19, + KEY_Q = 20, + KEY_R = 21, + KEY_S = 22, + KEY_T = 23, + KEY_U = 24, + KEY_V = 25, + KEY_W = 26, + KEY_X = 27, + KEY_Y = 28, + KEY_Z = 29, + KEY_1 = 30, + KEY_2 = 31, + KEY_3 = 32, + KEY_4 = 33, + KEY_5 = 34, + KEY_6 = 35, + KEY_7 = 36, + KEY_8 = 37, + KEY_9 = 38, + KEY_0 = 39, + KEY_RETURN = 40, + KEY_ESCAPE = 41, + KEY_BACKSPACE = 42, + KEY_TAB = 43, + KEY_SPACE = 44, + KEY_MINUS = 45, + KEY_EQUALS = 46, + KEY_LEFTBRACKET = 47, + KEY_RIGHTBRACKET = 48, + KEY_BACKSLASH = 49, + KEY_NONUSHASH = 50, + KEY_SEMICOLON = 51, + KEY_APOSTROPHE = 52, + KEY_GRAVE = 53, + KEY_COMMA = 54, + KEY_PERIOD = 55, + KEY_SLASH = 56, + KEY_CAPSLOCK = 57, + KEY_F1 = 58, + KEY_F2 = 59, + KEY_F3 = 60, + KEY_F4 = 61, + KEY_F5 = 62, + KEY_F6 = 63, + KEY_F7 = 64, + KEY_F8 = 65, + KEY_F9 = 66, + KEY_F10 = 67, + KEY_F11 = 68, + KEY_F12 = 69, + KEY_PRINTSCREEN = 70, + KEY_SCROLLLOCK = 71, + KEY_PAUSE = 72, + KEY_INSERT = 73, + KEY_HOME = 74, + KEY_PAGEUP = 75, + KEY_DELETE = 76, + KEY_END = 77, + KEY_PAGEDOWN = 78, + KEY_RIGHT = 79, + KEY_LEFT = 80, + KEY_DOWN = 81, + KEY_UP = 82, + KEY_NUMLOCKCLEAR = 83, + KEY_KP_DIVIDE = 84, + KEY_KP_MULTIPLY = 85, + KEY_KP_MINUS = 86, + KEY_KP_PLUS = 87, + KEY_KP_ENTER = 88, + KEY_KP_1 = 89, + KEY_KP_2 = 90, + KEY_KP_3 = 91, + KEY_KP_4 = 92, + KEY_KP_5 = 93, + KEY_KP_6 = 94, + KEY_KP_7 = 95, + KEY_KP_8 = 96, + KEY_KP_9 = 97, + KEY_KP_0 = 98, + KEY_KP_PERIOD = 99, + KEY_NONUSBACKSLASH = 100, + KEY_APPLICATION = 101, + KEY_POWER = 102, + KEY_KP_EQUALS = 103, + KEY_F13 = 104, + KEY_F14 = 105, + KEY_F15 = 106, + KEY_F16 = 107, + KEY_F17 = 108, + KEY_F18 = 109, + KEY_F19 = 110, + KEY_F20 = 111, + KEY_F21 = 112, + KEY_F22 = 113, + KEY_F23 = 114, + KEY_F24 = 115, + KEY_EXECUTE = 116, + KEY_HELP = 117, + KEY_MENU = 118, + KEY_SELECT = 119, + KEY_STOP = 120, + KEY_AGAIN = 121, + KEY_UNDO = 122, + KEY_CUT = 123, + KEY_COPY = 124, + KEY_PASTE = 125, + KEY_FIND = 126, + KEY_MUTE = 127, + KEY_VOLUMEUP = 128, + KEY_VOLUMEDOWN = 129, + KEY_LOCKINGCAPSLOCK = 130, + KEY_LOCKINGNUMLOCK = 131, + KEY_LOCKINGSCROLLLOCK = 132, + KEY_KP_COMMA = 133, + KEY_KP_EQUALSAS400 = 134, + KEY_INTERNATIONAL1 = 135, + KEY_INTERNATIONAL2 = 136, + KEY_INTERNATIONAL3 = 137, + KEY_INTERNATIONAL4 = 138, + KEY_INTERNATIONAL5 = 139, + KEY_INTERNATIONAL6 = 140, + KEY_INTERNATIONAL7 = 141, + KEY_INTERNATIONAL8 = 142, + KEY_INTERNATIONAL9 = 143, + KEY_LANG1 = 144, + KEY_LANG2 = 145, + KEY_LANG3 = 146, + KEY_LANG4 = 147, + KEY_LANG5 = 148, + KEY_LANG6 = 149, + KEY_LANG7 = 150, + KEY_LANG8 = 151, + KEY_LANG9 = 152, + KEY_ALTERASE = 153, + KEY_SYSREQ = 154, + KEY_CANCEL = 155, + KEY_CLEAR = 156, + KEY_PRIOR = 157, + KEY_RETURN2 = 158, + KEY_SEPARATOR = 159, + KEY_OUT = 160, + KEY_OPER = 161, + KEY_CLEARAGAIN = 162, + KEY_CRSEL = 163, + KEY_EXSEL = 164, + KEY_KP_00 = 176, + KEY_KP_000 = 177, + KEY_THOUSANDSSEPARATOR = 178, + KEY_DECIMALSEPARATOR = 179, + KEY_CURRENCYUNIT = 180, + KEY_CURRENCYSUBUNIT = 181, + KEY_KP_LEFTPAREN = 182, + KEY_KP_RIGHTPAREN = 183, + KEY_KP_LEFTBRACE = 184, + KEY_KP_RIGHTBRACE = 185, + KEY_KP_TAB = 186, + KEY_KP_BACKSPACE = 187, + KEY_KP_A = 188, + KEY_KP_B = 189, + KEY_KP_C = 190, + KEY_KP_D = 191, + KEY_KP_E = 192, + KEY_KP_F = 193, + KEY_KP_XOR = 194, + KEY_KP_POWER = 195, + KEY_KP_PERCENT = 196, + KEY_KP_LESS = 197, + KEY_KP_GREATER = 198, + KEY_KP_AMPERSAND = 199, + KEY_KP_DBLAMPERSAND = 200, + KEY_KP_VERTICALBAR = 201, + KEY_KP_DBLVERTICALBAR = 202, + KEY_KP_COLON = 203, + KEY_KP_HASH = 204, + KEY_KP_SPACE = 205, + KEY_KP_AT = 206, + KEY_KP_EXCLAM = 207, + KEY_KP_MEMSTORE = 208, + KEY_KP_MEMRECALL = 209, + KEY_KP_MEMCLEAR = 210, + KEY_KP_MEMADD = 211, + KEY_KP_MEMSUBTRACT = 212, + KEY_KP_MEMMULTIPLY = 213, + KEY_KP_MEMDIVIDE = 214, + KEY_KP_PLUSMINUS = 215, + KEY_KP_CLEAR = 216, + KEY_KP_CLEARENTRY = 217, + KEY_KP_BINARY = 218, + KEY_KP_OCTAL = 219, + KEY_KP_DECIMAL = 220, + KEY_KP_HEXADECIMAL = 221, + KEY_LCTRL = 224, + KEY_LSHIFT = 225, + KEY_LALT = 226, + KEY_LGUI = 227, + KEY_RCTRL = 228, + KEY_RSHIFT = 229, + KEY_RALT = 230, + KEY_RGUI = 231 }; -#define INPUT_COUNT (INPUT_MOUSE_CLICK + 1) +#define INPUT_COUNT (INPUT_ZOOM_OUT + 1) enum InputType { - INPUT_MOUSE_CLICK + INPUT_PAN, + INPUT_MOVE, + INPUT_SCALE, + INPUT_CROP, + INPUT_LEFT, + INPUT_RIGHT, + INPUT_UP, + INPUT_DOWN, + INPUT_ROTATE_LEFT, + INPUT_ROTATE_RIGHT, + INPUT_ZOOM_IN, + INPUT_ZOOM_OUT +}; + +static const KeyType INPUT_KEYS[INPUT_COUNT] +{ + KEY_SPACE, + KEY_T, + KEY_S, + KEY_C, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_Q, + KEY_W, + KEY_1, + KEY_2 }; struct Keyboard { - bool current[MOUSE_COUNT]; - bool previous[MOUSE_COUNT]; + bool current[KEY_COUNT]; + bool previous[KEY_COUNT]; }; struct Mouse @@ -39,11 +284,14 @@ struct Mouse struct Input { - Keyboard keyboard; Mouse mouse; + Keyboard keyboard; }; bool mouse_press(Mouse* self, MouseType type); bool mouse_held(Mouse* self, MouseType type); bool mouse_release(Mouse* self, MouseType type); +bool key_press(Keyboard* self, KeyType type); +bool key_held(Keyboard* self, KeyType type); +bool key_release(Keyboard* self, KeyType type); void input_tick(Input* self); \ No newline at end of file diff --git a/src/preview.cpp b/src/preview.cpp index 6a61207..ce7f315 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -1,7 +1,7 @@ #include "preview.h" static void _preview_axis_set(Preview* self); -static void _preview_grid_set(Preview* self); +static s32 _preview_grid_set(Preview* self); static void _preview_axis_set(Preview* self) @@ -16,13 +16,14 @@ _preview_axis_set(Preview* self) glBindVertexArray(0); } -static void +/* Sets and returns the grid's vertices */ +static s32 _preview_grid_set(Preview* self) { std::vector vertices; - s32 verticalLineCount = PREVIEW_SIZE.x / MIN(self->settings->previewGridSizeX, PREVIEW_GRID_MIN); - s32 horizontalLineCount = PREVIEW_SIZE.y / MIN(self->settings->previewGridSizeY, PREVIEW_GRID_MIN); + s32 verticalLineCount = (s32)(PREVIEW_SIZE.x / MIN(self->settings->previewGridSizeX, PREVIEW_GRID_MIN)); + s32 horizontalLineCount = (s32)(PREVIEW_SIZE.y / MIN(self->settings->previewGridSizeY, PREVIEW_GRID_MIN)); /* Vertical */ for (s32 i = 0; i <= verticalLineCount; i++) @@ -48,22 +49,23 @@ _preview_grid_set(Preview* self) vertices.push_back(normY); } - self->gridVertexCount = (s32)vertices.size(); - glBindVertexArray(self->gridVAO); glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + + return (s32)vertices.size(); } void -preview_init(Preview* self, Anm2* anm2, Resources* resources, Input* input, Settings* settings) +preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, s32* animationID, Resources* resources, Settings* settings) { self->anm2 = anm2; + self->reference = reference; + self->animationID = animationID; self->resources = resources; - self->input = input; self->settings = settings; /* Framebuffer + texture */ @@ -137,14 +139,11 @@ void preview_tick(Preview* self) { self->settings->previewZoom = CLAMP(self->settings->previewZoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); - - self->oldGridSize = glm::vec2(self->settings->previewGridSizeX, self->settings->previewGridSizeY); - self->oldGridOffset = glm::vec2(self->settings->previewGridOffsetX, self->settings->previewGridOffsetY); + + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); - if (self->animationID > -1) + if (animation) { - Anm2Animation* animation = &self->anm2->animations[self->animationID]; - if (self->isPlaying) { self->time += (f32)self->anm2->fps / TICK_RATE; @@ -162,7 +161,7 @@ preview_draw(Preview* self) { GLuint shaderLine = self->resources->shaders[SHADER_LINE]; GLuint shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - + f32 zoomFactor = self->settings->previewZoom / 100.0f; glm::vec2 ndcPan = glm::vec2(-self->settings->previewPanX / (PREVIEW_SIZE.x / 2.0f), -self->settings->previewPanY / (PREVIEW_SIZE.y / 2.0f)); glm::mat4 previewTransform = glm::translate(glm::mat4(1.0f), glm::vec3(ndcPan, 0.0f)); @@ -183,12 +182,18 @@ preview_draw(Preview* self) /* Grid */ if (self->settings->previewIsGrid) { - if - ( - (ivec2(self->settings->previewGridSizeX, self->settings->previewGridSizeY) != self->oldGridSize) || - (ivec2(self->settings->previewGridOffsetX, self->settings->previewGridOffsetY) != self->oldGridOffset) - ) - _preview_grid_set(self); + static ivec2 previousGridSize = {-1, -1}; + static ivec2 previousGridOffset = {-1, -1}; + static s32 gridVertexCount = -1; + ivec2 gridSize = ivec2(self->settings->previewGridSizeX, self->settings->previewGridSizeY); + ivec2 gridOffset = ivec2(self->settings->previewGridOffsetX, self->settings->previewGridOffsetY); + + if (previousGridSize != gridSize || previousGridOffset != gridOffset) + { + gridVertexCount = _preview_grid_set(self); + previousGridSize = gridSize; + previousGridOffset = gridOffset; + } glUseProgram(shaderLine); glBindVertexArray(self->gridVAO); @@ -200,7 +205,7 @@ preview_draw(Preview* self) self->settings->previewGridColorR, self->settings->previewGridColorG, self->settings->previewGridColorB, self->settings->previewGridColorA ); - glDrawArrays(GL_LINES, 0, self->gridVertexCount); + glDrawArrays(GL_LINES, 0, gridVertexCount); glBindVertexArray(0); glUseProgram(0); @@ -230,33 +235,32 @@ preview_draw(Preview* self) glDrawArrays(GL_LINES, 2, 2); - glBindVertexArray(0); glUseProgram(0); } + Anm2Animation* animation = anm2_animation_from_id(self->anm2, *self->animationID); + /* Animation */ - if (self->animationID > -1) + if (animation) { - Anm2Frame rootFrame = Anm2Frame{}; - Anm2Animation* animation = &self->anm2->animations[self->animationID]; - bool isRootFrame = anm2_frame_from_time(self->anm2, animation, &rootFrame, ANM2_ROOT_ANIMATION, 0, self->time); + Anm2Frame rootFrame; + anm2_frame_from_time(self->anm2, &rootFrame, Anm2Reference{ANM2_ROOT, 0, 0}, *self->animationID, self->time); - /* Layers (Reversed) */ + /* Layers */ for (auto & [id, layerAnimation] : animation->layerAnimations) { if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) continue; - Anm2Layer* layer = &self->anm2->layers[id]; - Anm2Frame frame = layerAnimation.frames[0]; + Anm2Frame frame; - anm2_frame_from_time(self->anm2, animation, &frame, ANM2_LAYER_ANIMATION, id, self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{ANM2_LAYER, id, 0}, *self->animationID, self->time); if (!frame.isVisible) continue; - Texture* texture = &self->resources->textures[layer->spritesheetID]; + Texture* texture = &self->resources->textures[self->anm2->layers[id].spritesheetID]; if (texture->isInvalid) continue; @@ -264,7 +268,7 @@ preview_draw(Preview* self) glm::mat4 layerTransform = previewTransform; glm::vec2 position = self->settings->previewIsRootTransform ? (frame.position + rootFrame.position) : frame.position; - glm::vec2 scale = self->settings->previewIsRootTransform ? (frame.scale / 100.0f) * (rootFrame.scale / 100.0f) : (frame.scale / 100.0f); + glm::vec2 scale = frame.scale / 100.0f; glm::vec2 ndcPos = position / (PREVIEW_SIZE / 2.0f); glm::vec2 ndcPivotOffset = (frame.pivot * scale) / (PREVIEW_SIZE / 2.0f); glm::vec2 ndcScale = (frame.size * scale) / (PREVIEW_SIZE / 2.0f); @@ -276,7 +280,6 @@ preview_draw(Preview* self) layerTransform = glm::translate(layerTransform, glm::vec3(-ndcPivotOffset, 0.0f)); layerTransform = glm::scale(layerTransform, glm::vec3(ndcScale, 1.0f)); - glm::vec2 uvMin = frame.crop / glm::vec2(texture->size); glm::vec2 uvMax = (frame.crop + frame.size) / glm::vec2(texture->size); @@ -305,17 +308,13 @@ preview_draw(Preview* self) glUseProgram(0); } - /* Root */ - if - (isRootFrame && animation->rootAnimation.isVisible && rootFrame.isVisible) + if (animation->rootAnimation.isVisible && rootFrame.isVisible) { glm::mat4 rootTransform = previewTransform; - glm::vec2 ndcPos = (rootFrame.position - (ATLAS_SIZES[TEXTURE_TARGET] / 2.0f)) / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = ATLAS_SIZES[TEXTURE_TARGET] / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcPivot = (-ATLAS_SIZES[TEXTURE_TARGET] / 2.0f) / (PREVIEW_SIZE / 2.0f); + glm::vec2 ndcPos = (rootFrame.position - (PREVIEW_TARGET_SIZE / 2.0f)) / (PREVIEW_SIZE / 2.0f); + glm::vec2 ndcScale = PREVIEW_TARGET_SIZE / (PREVIEW_SIZE / 2.0f); rootTransform = glm::translate(rootTransform, glm::vec3(ndcPos, 0.0f)); - rootTransform = glm::rotate(rootTransform, glm::radians(rootFrame.rotation), glm::vec3(0, 0, 1)); rootTransform = glm::scale(rootTransform, glm::vec3(ndcScale, 1.0f)); f32 vertices[] = ATLAS_UV_VERTICES(TEXTURE_TARGET); @@ -346,17 +345,15 @@ preview_draw(Preview* self) /* Pivots */ if (self->settings->previewIsShowPivot) { - for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) + /* Layers (Reversed) */ + for (auto & [id, layerAnimation] : animation->layerAnimations) { - s32 id = it->first; - Anm2LayerAnimation layerAnimation = it->second; - if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) continue; - Anm2Frame frame = layerAnimation.frames[0]; + Anm2Frame frame; - anm2_frame_from_time(self->anm2, animation, &frame, ANM2_LAYER_ANIMATION, id, self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{ANM2_LAYER, id, 0}, *self->animationID, self->time); if (!frame.isVisible) continue; @@ -365,8 +362,8 @@ preview_draw(Preview* self) glm::vec2 position = self->settings->previewIsRootTransform ? (frame.position + rootFrame.position) : frame.position; - glm::vec2 ndcPos = (position - (ATLAS_SIZES[TEXTURE_PIVOT] / 2.0f)) / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = ATLAS_SIZES[TEXTURE_PIVOT] / (PREVIEW_SIZE / 2.0f); + glm::vec2 ndcPos = (position - (PREVIEW_PIVOT_SIZE / 2.0f)) / (PREVIEW_SIZE / 2.0f); + glm::vec2 ndcScale = PREVIEW_PIVOT_SIZE / (PREVIEW_SIZE / 2.0f); pivotTransform = glm::translate(pivotTransform, glm::vec3(ndcPos, 0.0f)); pivotTransform = glm::scale(pivotTransform, glm::vec3(ndcScale, 1.0f)); @@ -403,21 +400,19 @@ preview_draw(Preview* self) if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) continue; - Anm2Frame frame = nullAnimation.frames[0]; + Anm2Frame frame; - anm2_frame_from_time(self->anm2, animation, &frame, ANM2_NULL_ANIMATION, id, self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{ANM2_NULL, id, 0}, *self->animationID, self->time); if (!frame.isVisible) continue; - Anm2Null* null = NULL; - - null = &self->anm2->nulls[id]; + Anm2Null* null = &self->anm2->nulls[id]; glm::mat4 nullTransform = previewTransform; TextureType textureType = null->isShowRect ? TEXTURE_SQUARE : TEXTURE_TARGET; - glm::vec2 size = null->isShowRect ? PREVIEW_POINT_SIZE : ATLAS_SIZES[TEXTURE_TARGET]; + glm::vec2 size = null->isShowRect ? PREVIEW_POINT_SIZE : PREVIEW_TARGET_SIZE; glm::vec2 pos = self->settings->previewIsRootTransform ? frame.position + (rootFrame.position) - (size / 2.0f) : frame.position - (size / 2.0f); glm::vec2 ndcPos = pos / (PREVIEW_SIZE / 2.0f); @@ -477,8 +472,6 @@ preview_draw(Preview* self) } } - glUseProgram(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); } diff --git a/src/preview.h b/src/preview.h index 4f57dff..dde93c7 100644 --- a/src/preview.h +++ b/src/preview.h @@ -15,6 +15,9 @@ static const vec2 PREVIEW_CENTER = {0, 0}; #define PREVIEW_GRID_MAX 1000 #define PREVIEW_GRID_OFFSET_MIN 0 #define PREVIEW_GRID_OFFSET_MAX 100 +#define PREVIEW_MOVE_STEP 1 +#define PREVIEW_ROTATE_STEP 1 +#define PREVIEW_SCALE_STEP 1 static const f32 PREVIEW_AXIS_VERTICES[] = { @@ -26,16 +29,20 @@ static const f32 PREVIEW_AXIS_VERTICES[] = static const vec2 PREVIEW_NULL_RECT_SIZE = {100, 100}; static const vec2 PREVIEW_POINT_SIZE = {2, 2}; +static const vec2 PREVIEW_PIVOT_SIZE = {4, 4}; static const vec4 PREVIEW_ROOT_TINT = COLOR_GREEN; static const vec4 PREVIEW_NULL_TINT = COLOR_BLUE; static const vec4 PREVIEW_PIVOT_TINT = COLOR_RED; +static const vec2 PREVIEW_TARGET_SIZE = {16, 16}; struct Preview { Anm2* anm2 = NULL; + Anm2Reference* reference = NULL; Input* input = NULL; Resources* resources = NULL; Settings* settings = NULL; + s32* animationID = NULL; GLuint axisVAO; GLuint axisVBO; GLuint fbo; @@ -50,14 +57,9 @@ struct Preview GLuint textureVBO; bool isPlaying = false; f32 time = 0; - ivec2 oldGridOffset = {-1, -1}; - ivec2 oldGridSize = {-1, -1}; - ivec2 viewport = PREVIEW_SIZE; - s32 animationID = -1; - s32 gridVertexCount = -1; }; -void preview_init(Preview* self, Anm2* anm2, Resources* resources, Input* input, Settings* settings); +void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, s32* animationID, Resources* resources, Settings* settings); void preview_draw(Preview* self); void preview_tick(Preview* self); void preview_free(Preview* self); diff --git a/src/settings.h b/src/settings.h index 2fe8e23..4a6b4ae 100644 --- a/src/settings.h +++ b/src/settings.h @@ -52,6 +52,7 @@ enum SettingsItem SETTINGS_PREVIEW_BACKGROUND_COLOR_B, SETTINGS_PREVIEW_BACKGROUND_COLOR_A, SETTINGS_EDITOR_IS_GRID, + SETTINGS_EDITOR_IS_GRID_SNAP, SETTINGS_EDITOR_IS_BORDER, SETTINGS_EDITOR_PAN_X, SETTINGS_EDITOR_PAN_Y, @@ -98,6 +99,7 @@ struct Settings f32 previewBackgroundColorB = 0.286f; f32 previewBackgroundColorA = 1.0f; bool editorIsGrid = true; + bool editorIsGridSnap = true; bool editorIsBorder = true; f32 editorPanX = 0.0f; f32 editorPanY = 0.0f; @@ -144,6 +146,7 @@ static const SettingsEntry SETTINGS_ENTRIES[SETTINGS_COUNT] = {"previewBackgroundColorB=", "previewBackgroundColorB=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorB)}, {"previewBackgroundColorA=", "previewBackgroundColorA=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorA)}, {"editorIsGrid=", "editorIsGrid=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsGrid)}, + {"editorIsGridSnap=", "editorIsGridSnap=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, {"editorIsBorder=", "editorIsBorder=%i", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsBorder)}, {"editorPanX=", "editorPanX=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorPanX)}, {"editorPanY=", "editorPanY=%f", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorPanY)}, diff --git a/src/state.cpp b/src/state.cpp index f36c702..53e44d3 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -34,6 +34,7 @@ _tick(State* state) input_tick(&state->input); editor_tick(&state->editor); preview_tick(&state->preview); + tool_tick(&state->tool); dialog_tick(&state->dialog); imgui_tick(&state->imgui); } @@ -104,10 +105,29 @@ init(State* state) printf(STRING_INFO_GLEW_INIT); resources_init(&state->resources); - dialog_init(&state->dialog, &state->anm2, &state->resources, state->window); - - preview_init(&state->preview, &state->anm2, &state->resources, &state->input, &state->settings); - editor_init(&state->editor, &state->resources, &state->settings); + dialog_init(&state->dialog, &state->anm2, &state->reference, &state->resources, state->window); + tool_init(&state->tool, &state->input); + + preview_init + ( + &state->preview, + &state->anm2, + &state->reference, + &state->animationID, + &state->resources, + &state->settings + ); + + editor_init + ( + &state->editor, + &state->anm2, + &state->reference, + &state->animationID, + &state->spritesheetID, + &state->resources, + &state->settings + ); imgui_init ( @@ -116,10 +136,14 @@ init(State* state) &state->resources, &state->input, &state->anm2, + &state->reference, + &state->animationID, + &state->spritesheetID, &state->editor, &state->preview, &state->settings, - state->window, + &state->tool, + state->window, &state->glContext ); @@ -144,10 +168,11 @@ loop(State* state) SDL_Delay(TICK_DELAY - (state->tick - state->lastTick)); _tick(state); - _draw(state); state->lastTick = state->tick; } + + _draw(state); } void diff --git a/src/state.h b/src/state.h index f075365..440ab8a 100644 --- a/src/state.h +++ b/src/state.h @@ -18,14 +18,18 @@ struct State Editor editor; Preview preview; Anm2 anm2; + Anm2Reference reference; Resources resources; Settings settings; + Tool tool; + bool isArgument = false; + bool isRunning = true; char argument[PATH_MAX] = STRING_EMPTY; char startPath[PATH_MAX] = STRING_EMPTY; - bool isArgument = false; - u64 tick = 0; + s32 animationID = -1; + s32 spritesheetID = -1; u64 lastTick = 0; - bool isRunning = true; + u64 tick = 0; }; void init(State* state); diff --git a/src/tool.cpp b/src/tool.cpp new file mode 100644 index 0000000..b0cfa99 --- /dev/null +++ b/src/tool.cpp @@ -0,0 +1,33 @@ +#include "tool.h" + +void +tool_init(Tool* self, Input* input) +{ + self->input = input; +} + +void +tool_tick(Tool* self) +{ + if (!self->isEnabled) return; + + /* Input handling */ + if (key_press(&self->input->keyboard, INPUT_KEYS[INPUT_PAN])) + self->type = TOOL_PAN; + + if (key_press(&self->input->keyboard, INPUT_KEYS[INPUT_MOVE])) + self->type = TOOL_MOVE; + + if (key_press(&self->input->keyboard, INPUT_KEYS[INPUT_SCALE])) + self->type = TOOL_SCALE; + + if (key_press(&self->input->keyboard, INPUT_KEYS[INPUT_CROP])) + self->type = TOOL_CROP; + + if + ( + key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ROTATE_LEFT]) || + key_press(&self->input->keyboard, INPUT_KEYS[INPUT_ROTATE_RIGHT]) + ) + self->type = TOOL_ROTATE; +} \ No newline at end of file diff --git a/src/tool.h b/src/tool.h new file mode 100644 index 0000000..4cb673e --- /dev/null +++ b/src/tool.h @@ -0,0 +1,23 @@ +#pragma once + +#include "input.h" + +#define TOOL_COUNT (TOOL_CROP + 1) +enum ToolType +{ + TOOL_PAN, + TOOL_MOVE, + TOOL_ROTATE, + TOOL_SCALE, + TOOL_CROP +}; + +struct Tool +{ + Input* input = NULL; + ToolType type = TOOL_PAN; + bool isEnabled = false; +}; + +void tool_init(Tool* self, Input* input); +void tool_tick(Tool* self); \ No newline at end of file