From f8fb3df8d407baa36a7c8c6becc9490278bdd4ee Mon Sep 17 00:00:00 2001 From: shweet Date: Fri, 12 Sep 2025 18:06:30 -0400 Subject: [PATCH] Redid layer system, added layer/null windows, more quality of life, polish --- src/COMMON.h | 22 ++- src/anm2.cpp | 152 +++++++------------ src/anm2.h | 67 +++++---- src/canvas.cpp | 1 + src/clipboard.cpp | 14 +- src/clipboard.h | 2 +- src/imgui.cpp | 363 +++++++++++++++++++++++++++++++++++----------- src/imgui.h | 235 ++++++++++++++++++++++++------ src/preview.cpp | 4 +- src/resources.cpp | 2 +- src/settings.h | 81 +++++++---- src/snapshots.cpp | 4 +- src/state.cpp | 2 + src/state.h | 2 +- src/texture.cpp | 14 +- src/texture.h | 5 +- 16 files changed, 658 insertions(+), 312 deletions(-) diff --git a/src/COMMON.h b/src/COMMON.h index 550f296..c4a0ceb 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -233,8 +233,10 @@ static inline s32 map_next_id_get(const std::map& map) return id; } -template -static inline T* map_find(std::map& map, s32 id) + +template +static inline auto map_find(Map& map, typename Map::key_type id) + -> typename Map::mapped_type* { if (auto it = map.find(id); it != map.end()) return &it->second; @@ -289,6 +291,22 @@ static inline void map_insert_shift(std::map& map, s32 index, const T& v map[insertIndex] = value; } +template +void vector_value_erase(std::vector& v, const T& value) +{ + v.erase(std::remove(v.begin(), v.end(), value), v.end()); +} + +template +void vector_value_swap(std::vector& v, const T& a, const T& b) +{ + for (auto& element : v) + { + if (element == a) element = b; + else if (element == b) element = a; + } +} + static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {}) { vec2 scaleAbsolute = glm::abs(scale); diff --git a/src/anm2.cpp b/src/anm2.cpp index 1c7f686..fa280f2 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -64,7 +64,7 @@ void anm2_frame_serialize(Anm2Frame* frame, Anm2Type type, XMLDocument* document } } -void anm2_animation_serialize(Anm2* self, Anm2Animation* animation, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, std::string* string = nullptr) +void anm2_animation_serialize(Anm2Animation* animation, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, std::string* string = nullptr) { XMLDocument localDocument; XMLDocument* useDocument = document ? document : &localDocument; @@ -86,7 +86,7 @@ void anm2_animation_serialize(Anm2* self, Anm2Animation* animation, XMLDocument* // LayerAnimations XMLElement* layersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS]); - for (auto& [i, id] : self->layerMap) + for (auto& id : animation->layerOrder) { // LayerAnimation Anm2Item& layerAnimation = animation->layerAnimations[id]; @@ -231,7 +231,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->animations[self->defaultAnimationID].name.c_str()); // DefaultAnimation for (auto& [id, animation] : self->animations) - anm2_animation_serialize(self, &animation, &document, animationsElement); + anm2_animation_serialize(&animation, &document, animationsElement); animatedActorElement->InsertEndChild(animationsElement); @@ -248,7 +248,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) return true; } -static void _anm2_frame_deserialize(Anm2* self, Anm2Frame* frame, const XMLElement* element) +static void _anm2_frame_deserialize(Anm2Frame* frame, const XMLElement* element) { for (const XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) { @@ -276,18 +276,13 @@ static void _anm2_frame_deserialize(Anm2* self, Anm2Frame* frame, const XMLEleme case ANM2_ATTRIBUTE_INTERPOLATED: frame->isInterpolated = string_to_bool(attribute->Value()); break; // Interpolated case ANM2_ATTRIBUTE_AT_FRAME: frame->atFrame = std::atoi(attribute->Value()); break; // AtFrame case ANM2_ATTRIBUTE_DELAY: frame->delay = std::atoi(attribute->Value()); break; // Delay - case ANM2_ATTRIBUTE_EVENT_ID: // EventID - { - s32 eventID = std::atoi(attribute->Value()); - frame->eventID = map_find(self->events, eventID) ? eventID : ID_NONE; - break; - } + case ANM2_ATTRIBUTE_EVENT_ID: frame->eventID = std::atoi(attribute->Value()); break; // EventID default: break; } } } -static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, const XMLElement* element) +static void _anm2_animation_deserialize(Anm2Animation* animation, const XMLElement* element) { auto frames_deserialize = [&](const XMLElement* itemElement, Anm2Item* item) { @@ -297,7 +292,7 @@ static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, co const XMLElement* frame = itemElement->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); frame; frame = frame->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) ) - _anm2_frame_deserialize(self, &item->frames.emplace_back(Anm2Frame()), frame); + _anm2_frame_deserialize(&item->frames.emplace_back(Anm2Frame()), frame); }; s32 id{}; @@ -320,8 +315,6 @@ static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, co // LayerAnimations if (const XMLElement* layerAnimations = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS])) { - s32 layerMapIndex = 0; - // LayerAnimation for ( @@ -330,7 +323,6 @@ static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, co ) { Anm2Item layerAnimationItem; - for (const XMLAttribute* attribute = layerAnimation->FirstAttribute(); attribute; attribute = attribute->Next()) { @@ -344,9 +336,7 @@ static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, co frames_deserialize(layerAnimation, &layerAnimationItem); animation->layerAnimations[id] = layerAnimationItem; - - self->layerMap[id] = layerMapIndex; - layerMapIndex++; + animation->layerOrder.push_back(id); } } @@ -386,7 +376,7 @@ static void _anm2_animation_deserialize(Anm2* self, Anm2Animation* animation, co const XMLElement* trigger = triggers->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]); trigger; trigger = trigger->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]) ) - _anm2_frame_deserialize(self, &animation->triggers.frames.emplace_back(Anm2Frame()), trigger); + _anm2_frame_deserialize(&animation->triggers.frames.emplace_back(Anm2Frame()), trigger); } } @@ -568,7 +558,7 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) const XMLElement* animation = animations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); animation; animation = animation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]) ) - _anm2_animation_deserialize(self, &self->animations[map_next_id_get(self->animations)], animation); + _anm2_animation_deserialize(&self->animations[map_next_id_get(self->animations)], animation); } for (auto& [id, animation] : self->animations) @@ -584,99 +574,68 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) return true; } -void anm2_layer_add(Anm2* self) +void anm2_animation_layer_animation_add(Anm2Animation* animation, s32 id) +{ + animation->layerAnimations[id] = Anm2Item{}; + animation->layerOrder.push_back(id); +} + +void anm2_animation_layer_animation_remove(Anm2Animation* animation, s32 id) +{ + animation->layerAnimations.erase(id); + vector_value_erase(animation->layerOrder, id); +} + +void anm2_animation_null_animation_add(Anm2Animation* animation, s32 id) +{ + animation->nullAnimations[id] = Anm2Item{}; +} + +void anm2_animation_null_animation_remove(Anm2Animation* animation, s32 id) +{ + animation->nullAnimations.erase(id); +} + +s32 anm2_layer_add(Anm2* self) { s32 id = map_next_id_get(self->layers); - self->layers[id] = Anm2Layer{}; - self->layerMap[self->layers.size() - 1] = id; - - for (auto& [_, animation] : self->animations) - animation.layerAnimations[id] = Anm2Item{}; + return id; } void anm2_layer_remove(Anm2* self, s32 id) { - if (!self->layers.contains(id)) return; - self->layers.erase(id); - for (auto it = self->layerMap.begin(); it != self->layerMap.end(); ++it) - { - if (it->second == id) - { - self->layerMap.erase(it); - break; - } - } - - std::map newLayerMap; - s32 newIndex = 0; - - for (const auto& [_, layerID] : self->layerMap) - newLayerMap[newIndex++] = layerID; - - self->layerMap = std::move(newLayerMap); - for (auto& [_, animation] : self->animations) - animation.layerAnimations.erase(id); + anm2_animation_layer_animation_remove(&animation, id); } -void anm2_null_add(Anm2* self) +s32 anm2_null_add(Anm2* self) { s32 id = map_next_id_get(self->nulls); - self->nulls[id] = Anm2Null{}; - - for (auto& [_, animation] : self->animations) - animation.nullAnimations[id] = Anm2Item{}; + return id; } void anm2_null_remove(Anm2* self, s32 id) { - if (!self->nulls.contains(id)) - return; + if (!self->nulls.contains(id)) return; - self->nulls.erase(id); + self->nulls.erase(id); - std::map newNulls; - s32 newID = 0; - - for (const auto& [_, null] : self->nulls) - newNulls[newID++] = null; - - self->nulls = std::move(newNulls); - - for (auto& [_, animation] : self->animations) - { - if (animation.nullAnimations.contains(id)) - animation.nullAnimations.erase(id); - - std::map newNullAnims; - s32 newAnimID = 0; - for (const auto& [_, nullAnim] : animation.nullAnimations) - newNullAnims[newAnimID++] = nullAnim; - - animation.nullAnimations = std::move(newNullAnims); - } + for (auto& [_, animation] : self->animations) + anm2_animation_null_animation_remove(&animation, id); } -s32 anm2_animation_add(Anm2* self, bool isAddRootFrame, Anm2Animation* animation, s32 id) +s32 anm2_animation_add(Anm2* self, Anm2Animation* animation, s32 id) { s32 addID = map_next_id_get(self->animations); Anm2Animation localAnimation; Anm2Animation* addAnimation = animation ? animation : &localAnimation; - for (auto& [layerID, layer] : self->layers) - if (!map_find(addAnimation->layerAnimations, layerID)) - addAnimation->layerAnimations[layerID] = Anm2Item{}; - for (auto& [nullID, null] : self->nulls) - if (!map_find(addAnimation->nullAnimations, nullID)) - addAnimation->nullAnimations[nullID] = Anm2Item{}; - - if (isAddRootFrame) - addAnimation->rootAnimation.frames.push_back(Anm2Frame{}); + if (!animation) addAnimation->rootAnimation.frames.push_back(Anm2Frame{}); if (id != ID_NONE) { @@ -685,6 +644,7 @@ s32 anm2_animation_add(Anm2* self, bool isAddRootFrame, Anm2Animation* animation } else self->animations[addID] = *addAnimation; + return addID; } @@ -1153,7 +1113,7 @@ void anm2_spritesheet_texture_pixels_download(Anm2* self) if (texture.id != GL_ID_NONE && !texture.isInvalid) { - size_t bufferSize = (size_t)texture.size.x * (size_t)texture.size.y * (size_t)texture.channels; + size_t bufferSize = (size_t)texture.size.x * (size_t)texture.size.y * TEXTURE_CHANNELS; spritesheet.pixels.resize(bufferSize); glBindTexture(GL_TEXTURE_2D, texture.id); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, spritesheet.pixels.data()); @@ -1212,7 +1172,7 @@ vec4 anm2_animation_rect_get(Anm2* self, Anm2Reference* reference, bool isRootTr return {minX, minY, maxX - minX, maxY - minY}; } -bool anm2_animation_deserialize_from_xml(Anm2* self, Anm2Animation* animation, const std::string& xml) +bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml) { XMLDocument document; @@ -1225,14 +1185,15 @@ bool anm2_animation_deserialize_from_xml(Anm2* self, Anm2Animation* animation, c if (document.Parse(xml.c_str()) != XML_SUCCESS) return animation_deserialize_error(); const XMLElement* element = document.RootElement(); - if (element && std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) + if (!element) return animation_deserialize_error(); + if (std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) return animation_deserialize_error(); - _anm2_animation_deserialize(self, animation, element); + _anm2_animation_deserialize(animation, element); return true; } -bool anm2_frame_deserialize_from_xml(Anm2* self, Anm2Frame* frame, const std::string& xml) +bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml) { XMLDocument document; @@ -1243,18 +1204,17 @@ bool anm2_frame_deserialize_from_xml(Anm2* self, Anm2Frame* frame, const std::st }; if (document.Parse(xml.c_str()) != XML_SUCCESS) return frame_deserialize_error(); - + const XMLElement* element = document.RootElement(); + if (!element) return frame_deserialize_error(); + if ( - element && - ( - std::string(element->Name()) == std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) || - std::string(element->Name()) == std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]) - ) + std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) && + std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]) ) return frame_deserialize_error(); - _anm2_frame_deserialize(self, frame, element); + _anm2_frame_deserialize(frame, element); return true; } \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 73ac1f0..9efa496 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -142,7 +142,7 @@ struct Anm2Spritesheet struct Anm2Layer { std::string name = "New Layer"; - s32 spritesheetID = ID_NONE; + s32 spritesheetID{}; }; struct Anm2Null @@ -199,9 +199,9 @@ struct Anm2Animation s32 frameNum = ANM2_FRAME_NUM_MIN; std::string name = "New Animation"; bool isLoop = true; - bool isShowUnused = true; Anm2Item rootAnimation; - std::map layerAnimations; + std::unordered_map layerAnimations; + std::vector layerOrder; std::map nullAnimations; Anm2Item triggers; }; @@ -216,7 +216,6 @@ struct Anm2 std::map nulls; std::map events; std::map animations; - std::map layerMap; // index, id s32 defaultAnimationID = ID_NONE; s32 fps = ANM2_FPS_DEFAULT; s32 version{}; @@ -253,38 +252,42 @@ enum OnionskinDrawOrder ONIONSKIN_ABOVE }; -void anm2_layer_add(Anm2* self); -void anm2_layer_remove(Anm2* self, s32 id); -void anm2_null_add(Anm2* self); -void anm2_null_remove(Anm2* self, s32 id); -bool anm2_serialize(Anm2* self, const std::string& path); -bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true); -void anm2_new(Anm2* self); -void anm2_free(Anm2* self); -void anm2_created_on_set(Anm2* self); -s32 anm2_animation_add(Anm2* self, bool isAddRootFrame = true, Anm2Animation* animation = nullptr, s32 id = ID_NONE); -void anm2_animation_remove(Anm2* self, s32 id); Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference); -Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference); -Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference); -s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference); -void anm2_frame_remove(Anm2* self, Anm2Reference* reference); -void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); -void anm2_reference_clear(Anm2Reference* self); -void anm2_reference_item_clear(Anm2Reference* self); -void anm2_reference_frame_clear(Anm2Reference* self); +Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference); +Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference); +bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml); +bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true); +bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml); +bool anm2_serialize(Anm2* self, const std::string& path); +s32 anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, s32 id = ID_NONE); s32 anm2_animation_length_get(Anm2Animation* self); +s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); +s32 anm2_layer_add(Anm2* self); +s32 anm2_null_add(Anm2* self); +vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference* reference, bool isRootTransform); +void anm2_animation_layer_animation_add(Anm2Animation* animation, s32 id); +void anm2_animation_layer_animation_remove(Anm2Animation* animation, s32 id); void anm2_animation_length_set(Anm2Animation* self); void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type); +void anm2_animation_null_animation_add(Anm2Animation* animation, s32 id); +void anm2_animation_null_animation_remove(Anm2Animation* animation, s32 id); +void anm2_animation_remove(Anm2* self, s32 id); +void anm2_animation_serialize(Anm2Animation* animation, XMLDocument* document, XMLElement* addElement, std::string* string); +void anm2_created_on_set(Anm2* self); void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation); -void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count); -void anm2_scale(Anm2* self, f32 scale); -void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay); -void anm2_spritesheet_texture_pixels_upload(Anm2* self); -void anm2_spritesheet_texture_pixels_download(Anm2* self); -vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference* reference, bool isRootTransform); +void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); +void anm2_frame_remove(Anm2* self, Anm2Reference* reference); void anm2_frame_serialize(Anm2Frame* frame, Anm2Type type, XMLDocument* document, XMLElement* addElement, std::string* string); -void anm2_animation_serialize(Anm2* self, Anm2Animation* animation, XMLDocument* document, XMLElement* addElement, std::string* string); -bool anm2_frame_deserialize_from_xml(Anm2* self, Anm2Frame* frame, const std::string& xml); -bool anm2_animation_deserialize_from_xml(Anm2* self, Anm2Animation* frame, const std::string& xml); \ No newline at end of file +void anm2_free(Anm2* self); +void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay); +void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count); +void anm2_layer_remove(Anm2* self, s32 id); +void anm2_new(Anm2* self); +void anm2_null_remove(Anm2* self, s32 id); +void anm2_reference_clear(Anm2Reference* self); +void anm2_reference_frame_clear(Anm2Reference* self); +void anm2_reference_item_clear(Anm2Reference* self); +void anm2_scale(Anm2* self, f32 scale); +void anm2_spritesheet_texture_pixels_download(Anm2* self); +void anm2_spritesheet_texture_pixels_upload(Anm2* self); \ No newline at end of file diff --git a/src/canvas.cpp b/src/canvas.cpp index 39c657e..806ec9a 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -197,6 +197,7 @@ void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, glUseProgram(0); } + void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color) { vec4 originNDC = transform * vec4(0.0f, 0.0f, 0.0f, 1.0f); diff --git a/src/clipboard.cpp b/src/clipboard.cpp index b43607c..4bb8506 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -28,7 +28,7 @@ void clipboard_copy(Clipboard* self) if (!id) break; Anm2Animation* animation = map_find(self->anm2->animations, *id); if (!animation) break; - anm2_animation_serialize(self->anm2, animation, nullptr, nullptr, &clipboardText); + anm2_animation_serialize(animation, nullptr, nullptr, &clipboardText); clipboard_text_set(); break; } @@ -63,7 +63,7 @@ void clipboard_cut(Clipboard* self) } } -void clipboard_paste(Clipboard* self) +bool clipboard_paste(Clipboard* self) { auto clipboard_string = [&]() { @@ -80,8 +80,9 @@ void clipboard_paste(Clipboard* self) Anm2Reference* reference = std::get_if(&self->location); if (!reference) break; Anm2Frame frame; - if (anm2_frame_deserialize_from_xml(self->anm2, &frame, clipboard_string())) + if (anm2_frame_deserialize_from_xml(&frame, clipboard_string())) anm2_frame_add(self->anm2, &frame, reference); + else return false; break; } case CLIPBOARD_ANIMATION: @@ -89,13 +90,16 @@ void clipboard_paste(Clipboard* self) s32* id = std::get_if(&self->location); if (!id) break; Anm2Animation animation; - if (anm2_animation_deserialize_from_xml(self->anm2, &animation, clipboard_string())) - anm2_animation_add(self->anm2, false, &animation, *id); + if (anm2_animation_deserialize_from_xml(&animation, clipboard_string())) + anm2_animation_add(self->anm2, &animation, *id); + else return false; break; } default: break; } + + return true; } void clipboard_init(Clipboard* self, Anm2* anm2) diff --git a/src/clipboard.h b/src/clipboard.h index c448586..d269ce7 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -23,5 +23,5 @@ struct Clipboard bool clipboard_is_value(void); void clipboard_copy(Clipboard* self); void clipboard_cut(Clipboard* self); -void clipboard_paste(Clipboard* self); +bool clipboard_paste(Clipboard* self); void clipboard_init(Clipboard* self, Anm2* anm2); \ No newline at end of file diff --git a/src/imgui.cpp b/src/imgui.cpp index fe8ae47..a4e5ce7 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -62,6 +62,8 @@ static void _imgui_spritesheet_add(Imgui* self, const std::string& path) return; } + imgui_snapshot(self, IMGUI_ACTION_ADD_SPRITESHEET); + std::filesystem::path workingPath = std::filesystem::current_path(); std::string spritesheetPath = path; std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path); @@ -79,6 +81,16 @@ static bool _imgui_is_window_hovered(void) return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } +static bool _imgui_is_window_hovered_and_click(void) +{ + return _imgui_is_window_hovered() && ImGui::IsMouseClicked(0); +} + +static bool _imgui_is_window_hovered_and_click_no_anm2_path(Imgui* self) +{ + return _imgui_is_window_hovered_and_click() && self->anm2->path.empty(); +} + static bool _imgui_is_no_click_on_item(void) { return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered(); @@ -141,7 +153,7 @@ static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) case IMGUI_WINDOW: case IMGUI_DOCKSPACE: case IMGUI_CHILD: - case IMGUI_OPTION_POPUP: + case IMGUI_CONFIRM_POPUP: break; default: ImGui::BeginDisabled(self.isDisabled); @@ -264,7 +276,7 @@ static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType case IMGUI_WINDOW: case IMGUI_DOCKSPACE: case IMGUI_CHILD: - case IMGUI_OPTION_POPUP: + case IMGUI_CONFIRM_POPUP: break; default: ImGui::EndDisabled(); @@ -569,7 +581,7 @@ IMGUI_ITEM_ATLAS_FUNCTION(_imgui_atlas_selectable, _imgui_selectable(self, imgui IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_int, s32, _imgui_selectable_input_int(self, imgui, value)); IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_text, std::string, _imgui_selectable_input_text(self, imgui, value)); -static bool _imgui_option_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* state = nullptr) +static bool _imgui_confirm_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* state = nullptr, bool isOnlyConfirm = false) { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); @@ -582,20 +594,23 @@ static bool _imgui_option_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* s ImGui::Text(self.text_get()); ImGui::Separator(); - if (_imgui_button(IMGUI_POPUP_OK, imgui)) + if (_imgui_button(IMGUI_POPUP_OK.copy({.rowCount = isOnlyConfirm ? 1 : IMGUI_CONFIRM_POPUP_ROW_COUNT}), imgui)) { imgui_close_current_popup(imgui); imgui_end_popup(imgui); - if (state) *state = IMGUI_POPUP_STATE_CONFIRM; + if (state) *state = isOnlyConfirm ? IMGUI_POPUP_STATE_CANCEL : IMGUI_POPUP_STATE_CONFIRM; return true; } ImGui::SameLine(); - if (_imgui_button(IMGUI_POPUP_CANCEL, imgui)) + if (!isOnlyConfirm) { - imgui_close_current_popup(imgui); - if (state) *state = IMGUI_POPUP_STATE_CANCEL; + if (_imgui_button(IMGUI_POPUP_CANCEL, imgui)) + { + imgui_close_current_popup(imgui); + if (state) *state = IMGUI_POPUP_STATE_CANCEL; + } } imgui_end_popup(imgui); @@ -604,6 +619,13 @@ static bool _imgui_option_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* s return false; } +static void _imgui_no_anm2_path_check(Imgui* self) +{ + if (_imgui_is_window_hovered_and_click_no_anm2_path(self) && !imgui_is_any_popup_open()) + imgui_open_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION.label); + _imgui_confirm_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION, self, nullptr, true); +} + static void _imgui_context_menu(Imgui* self) { if (!self->isContextualActionsEnabled) return; @@ -650,6 +672,7 @@ static void _imgui_timeline(Imgui* self) static s32& itemID = self->reference->itemID; IMGUI_BEGIN_OR_RETURN(IMGUI_TIMELINE, self); + _imgui_no_anm2_path_check(self); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); @@ -704,12 +727,7 @@ static void _imgui_timeline(Imgui* self) localMousePos = ImVec2(mousePos.x - itemMin.x + scroll.x, mousePos.y - itemMin.y); frameTime = (s32)(localMousePos.x / frameSize.x); - if (ImGui::IsMouseDown(0) && _imgui_is_window_hovered()) - { - if (!isPlayheadDrag) - imgui_snapshot(self, IMGUI_ACTION_MOVE_PLAYHEAD); - isPlayheadDrag = true; - } + if (ImGui::IsMouseDown(0) && _imgui_is_window_hovered()) isPlayheadDrag = true; if (isPlayheadDrag) { @@ -790,12 +808,12 @@ static void _imgui_timeline(Imgui* self) std::function timeline_item_child = [&](Anm2Reference reference, s32& index) { Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); - + Anm2Type& type = reference.itemType; if (!item) return; + if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) return; ImVec2 buttonSize = ImVec2(ATLAS_SIZE_NORMAL) + (defaultFramePadding * ImVec2(2, 2)); - Anm2Type& type = reference.itemType; Anm2Layer* layer = nullptr; Anm2Null* null = nullptr; s32 buttonCount = type == ANM2_NULL ? 2 : 1; @@ -824,13 +842,13 @@ static void _imgui_timeline(Imgui* self) layer = &self->anm2->layers[reference.itemID]; if ( - _imgui_atlas_selectable_input_text(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy + _imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy ({ .isSelected = isSelected, - .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, layer->name), - .id = index + .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, layer->name) }), - self, layer->name) + self + ) ) *self->reference = reference; break; @@ -838,13 +856,12 @@ static void _imgui_timeline(Imgui* self) null = &self->anm2->nulls[reference.itemID]; if ( - _imgui_atlas_selectable_input_text(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy + _imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy ({ .isSelected = isSelected, - .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, null->name), - .id = index + .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, null->name) }), - self, null->name) + self) ) *self->reference = reference; break; @@ -878,13 +895,6 @@ static void _imgui_timeline(Imgui* self) ImGui::EndDragDropTarget(); } - if (type == ANM2_LAYER) - { - ImGui::SameLine(); - _imgui_atlas_selectable_input_int(IMGUI_TIMELINE_SPRITESHEET_ID.copy - ({.label = std::format(IMGUI_SPRITESHEET_ID_FORMAT, layer->spritesheetID), .id = index}), self, layer->spritesheetID); - } - ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); if (type == ANM2_NULL) @@ -916,7 +926,7 @@ static void _imgui_timeline(Imgui* self) timeline_item_child({animationID, ANM2_ROOT}, index); - for (auto& [i, id] : std::ranges::reverse_view(self->anm2->layerMap)) + for (auto& id : std::ranges::reverse_view(animation->layerOrder)) timeline_item_child({animationID, ANM2_LAYER, id}, index); for (auto & [id, null] : animation->nullAnimations) @@ -933,28 +943,12 @@ static void _imgui_timeline(Imgui* self) switch (swapItemReference.itemType) { case ANM2_LAYER: - { - s32 indexA = INDEX_NONE; - s32 indexB = INDEX_NONE; - - for (const auto& [index, id] : self->anm2->layerMap) - { - if (id == self->reference->itemID) - indexA = index; - else if (id == swapItemReference.itemID) - indexB = index; - } - - if ((indexA != INDEX_NONE) && (indexB != INDEX_NONE)) - std::swap(self->anm2->layerMap[indexA], self->anm2->layerMap[indexB]); + vector_value_swap(animation->layerOrder, self->reference->itemID, swapItemReference.itemID); break; - } case ANM2_NULL: - map_swap(self->anm2->nulls, self->reference->itemID, swapItemReference.itemID); map_swap(animation->nullAnimations, self->reference->itemID, swapItemReference.itemID); break; - default: - break; + default: break; } self->reference->itemID = swapItemReference.itemID; @@ -966,7 +960,9 @@ static void _imgui_timeline(Imgui* self) std::function timeline_item_frames = [&](Anm2Reference reference, s32& index) { Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); + if (!item) return; Anm2Type& type = reference.itemType; + if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) return; ImGui::PushID(index); @@ -1150,7 +1146,7 @@ static void _imgui_timeline(Imgui* self) timeline_item_frames(Anm2Reference(animationID, ANM2_ROOT), index); - for (auto& [i, id] : std::ranges::reverse_view(self->anm2->layerMap)) + for (auto& id : std::ranges::reverse_view(animation->layerOrder)) timeline_item_frames(Anm2Reference(animationID, ANM2_LAYER, id), index); for (auto & [id, null] : animation->nullAnimations) @@ -1171,7 +1167,15 @@ static void _imgui_timeline(Imgui* self) timeline_frames_child(); ImGui::SetCursorPos(ImVec2()); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_CHILD, self); + + const ImguiItem& unusedItem = self->settings->timelineIsShowUnused ? IMGUI_TIMELINE_SHOW_UNUSED : IMGUI_TIMELINE_HIDE_UNUSED; + if (_imgui_atlas_button(unusedItem, self)) self->settings->timelineIsShowUnused = !self->settings->timelineIsShowUnused; + ImGui::PopStyleVar(2); + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_CHILD ImGui::SameLine(); timeline_header(); @@ -1185,12 +1189,91 @@ static void _imgui_timeline(Imgui* self) Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); _imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self); + _imgui_button(IMGUI_TIMELINE_ADD_ITEM, self); - if (imgui_begin_popup(IMGUI_TIMELINE_ADD_ITEM.popup, self)) + if (imgui_begin_popup_modal(IMGUI_TIMELINE_ADD_ITEM.popup, self, IMGUI_TIMELINE_ADD_ITEM.popupSize)) { - if (_imgui_selectable(IMGUI_TIMELINE_ADD_ITEM_LAYER, self)) anm2_layer_add(self->anm2); - if (_imgui_selectable(IMGUI_TIMELINE_ADD_ITEM_NULL, self)) anm2_null_add(self->anm2); + static s32 selectedLayerID = ID_NONE; + static s32 selectedNullID = ID_NONE; + s32& type = self->settings->timelineAddItemType; + + _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD, self); + + _imgui_radio_button(IMGUI_TIMELINE_ADD_ITEM_LAYER, self, type); + _imgui_radio_button(IMGUI_TIMELINE_ADD_ITEM_NULL, self, type); + + _imgui_end_child(); // IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD, self); + + switch (type) + { + case ANM2_LAYER: + default: + { + for (auto & [id, layer] : self->anm2->layers) + { + ImGui::PushID(id); + + ImguiItem layerItem = IMGUI_LAYER.copy + ({ + .isSelected = selectedLayerID == id, + .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), + .id = id + }); + if (_imgui_atlas_selectable(layerItem, self)) selectedLayerID = id; + + ImGui::PopID(); + }; + break; + } + case ANM2_NULL: + { + for (auto & [id, null] : self->anm2->nulls) + { + ImGui::PushID(id); + + ImguiItem nullItem = IMGUI_NULL.copy + ({ + .isSelected = selectedNullID == id, + .label = std::format(IMGUI_NULL_FORMAT, id, null.name), + .id = id + }); + if (_imgui_atlas_selectable(nullItem, self)) selectedNullID = id; + + ImGui::PopID(); + }; + break; + } + } + + _imgui_end_child(); // IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_OPTIONS_CHILD, self); + + if (self->anm2->layers.size() == 0) selectedLayerID = ID_NONE; + if (self->anm2->nulls.size() == 0) selectedNullID = ID_NONE; + + bool isDisabled = type == ANM2_NONE || + (type == ANM2_LAYER && selectedLayerID == ID_NONE) || + (type == ANM2_NULL && selectedNullID == ID_NONE); + + if (_imgui_button(IMGUI_TIMELINE_ADD_ITEM_ADD.copy({isDisabled}), self)) + { + switch (type) + { + case ANM2_LAYER: anm2_animation_layer_animation_add(animation, selectedLayerID); break; + case ANM2_NULL: anm2_animation_null_animation_add(animation, selectedNullID); break; + default: break; + } + + imgui_close_current_popup(self); + } + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); + + _imgui_end_child(); // IMGUI_TIMELINE_ADD_ITEM_OPTIONS_CHILD + imgui_end_popup(self); } @@ -1198,14 +1281,9 @@ static void _imgui_timeline(Imgui* self) { switch (itemType) { - case ANM2_LAYER: - anm2_layer_remove(self->anm2, itemID); - break; - case ANM2_NULL: - anm2_null_remove(self->anm2, itemID); - break; - default: - break; + case ANM2_LAYER: anm2_animation_layer_animation_remove(animation, itemID); break; + case ANM2_NULL: anm2_animation_null_animation_remove(animation, itemID); break; + default: break; } anm2_reference_item_clear(self->reference); @@ -1260,8 +1338,7 @@ static void _imgui_timeline(Imgui* self) imgui_close_current_popup(self); } - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) - imgui_close_current_popup(self); + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); _imgui_end_child(); //IMGUI_BAKE_CHILD) @@ -1356,14 +1433,13 @@ static void _imgui_taskbar(Imgui* self) if (self->isTryQuit) imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label); - _imgui_option_popup(IMGUI_EXIT_CONFIRMATION, self, &exitConfirmState); + _imgui_confirm_popup(IMGUI_EXIT_CONFIRMATION, self, &exitConfirmState); switch (exitConfirmState) { - case IMGUI_POPUP_STATE_CLOSED: self->isTryQuit = false; break; - case IMGUI_POPUP_STATE_OPEN: self->isTryQuit = true; break; case IMGUI_POPUP_STATE_CONFIRM: self->isQuit = true; break; case IMGUI_POPUP_STATE_CANCEL: self->isTryQuit = false; break; + default: break; } _imgui_selectable(IMGUI_WIZARD.copy({}), self); @@ -1564,6 +1640,10 @@ static void _imgui_taskbar(Imgui* self) _imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT, self, format); _imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &type); + _imgui_end_child(); // IMGUI_RENDER_ANIMATION_CHILD + + _imgui_begin_child(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self); + if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self)) { bool isRenderStart = true; @@ -1609,7 +1689,7 @@ static void _imgui_taskbar(Imgui* self) if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - _imgui_end_child(); //IMGUI_RENDER_ANIMATION_CHILD + _imgui_end_child(); // IMGUI_RENDER_ANIMATION_FOOTER_CHILD imgui_end_popup(self); } @@ -1826,9 +1906,110 @@ static void _imgui_tools(Imgui* self) _imgui_end(); // IMGUI_TOOLS } +static void _imgui_layers(Imgui* self) +{ + static s32 selectedLayerID = ID_NONE; + + IMGUI_BEGIN_OR_RETURN(IMGUI_LAYERS, self); + _imgui_no_anm2_path_check(self); + + ImVec2 size = ImGui::GetContentRegionAvail(); + + _imgui_begin_child(IMGUI_LAYERS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); + ImGui::SetScrollX(0.0f); + + for (auto & [id, layer] : self->anm2->layers) + { + ImGui::PushID(id); + + ImguiItem layerItem = IMGUI_LAYER.copy + ({ + .isSelected = selectedLayerID == id, + .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), + .size = {ImGui::GetContentRegionAvail().x - (IMGUI_LAYER_SPRITESHEET_ID.size.x * 2.0f), 0}, + .id = id + }); + if (_imgui_atlas_selectable_input_text(layerItem, self, layer.name)) selectedLayerID = id; + + ImGui::SameLine(); + + ImguiItem spritesheetItem = IMGUI_LAYER_SPRITESHEET_ID.copy + ({ + .isSelected = selectedLayerID == id, + .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), + .id = id + }); + _imgui_atlas_selectable_input_int(spritesheetItem, self, layer.spritesheetID); + + ImGui::PopID(); + }; + + _imgui_end_child(); // layersChild + + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + + if (_imgui_button(IMGUI_LAYER_ADD.copy({self->anm2->path.empty()}), self)) + selectedLayerID = anm2_layer_add(self->anm2); + + if (_imgui_button(IMGUI_LAYER_REMOVE.copy({selectedLayerID == ID_NONE}), self)) + { + anm2_layer_remove(self->anm2, selectedLayerID); + selectedLayerID = ID_NONE; + } + + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_LAYERS +} + +static void _imgui_nulls(Imgui* self) +{ + static s32 selectedNullID = ID_NONE; + + IMGUI_BEGIN_OR_RETURN(IMGUI_NULLS, self); + _imgui_no_anm2_path_check(self); + + ImVec2 size = ImGui::GetContentRegionAvail(); + + _imgui_begin_child(IMGUI_NULLS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); + + for (auto & [id, null] : self->anm2->nulls) + { + ImGui::PushID(id); + + ImguiItem nullItem = IMGUI_NULL.copy + ({ + .isSelected = selectedNullID == id, + .label = std::format(IMGUI_NULL_FORMAT, id, null.name), + .id = id + }); + + if (_imgui_atlas_selectable_input_text(nullItem, self, null.name)) selectedNullID = id; + + ImGui::PopID(); + }; + + _imgui_end_child(); // nullsChild + + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + + if (_imgui_button(IMGUI_NULL_ADD.copy({self->anm2->path.empty()}), self)) + selectedNullID = anm2_null_add(self->anm2); + + if (_imgui_button(IMGUI_NULL_REMOVE.copy({selectedNullID == ID_NONE}), self)) + { + anm2_null_remove(self->anm2, selectedNullID); + selectedNullID = ID_NONE; + } + + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_NULLS +} + static void _imgui_animations(Imgui* self) { IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATIONS, self); + _imgui_no_anm2_path_check(self); + ImVec2 size = ImGui::GetContentRegionAvail(); _imgui_begin_child(IMGUI_ANIMATIONS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); @@ -1900,7 +2081,7 @@ static void _imgui_animations(Imgui* self) } if (_imgui_button(IMGUI_ANIMATION_DUPLICATE.copy({!animation}), self)) - self->reference->animationID = anm2_animation_add(self->anm2, false, animation, self->reference->animationID); + self->reference->animationID = anm2_animation_add(self->anm2, animation, self->reference->animationID); _imgui_button(IMGUI_ANIMATION_MERGE.copy({!animation}), self); @@ -2035,6 +2216,8 @@ static void _imgui_events(Imgui* self) static s32 selectedID = ID_NONE; IMGUI_BEGIN_OR_RETURN(IMGUI_EVENTS, self); + _imgui_no_anm2_path_check(self); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); _imgui_begin_child(IMGUI_EVENTS_CHILD.copy({.size = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size.y}}), self); @@ -2091,6 +2274,7 @@ static void _imgui_spritesheets(Imgui* self) static s32 highlightedID = ID_NONE; IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEETS, self); + _imgui_no_anm2_path_check(self); ImVec2 windowSize = ImGui::GetContentRegionAvail(); @@ -2170,9 +2354,6 @@ static void _imgui_spritesheets(Imgui* self) if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self)) { - if (selectedIDs.size() > 0) - imgui_snapshot(self, IMGUI_ACTION_RELOAD_SPRITESHEET); - for (auto& id : selectedIDs) { std::filesystem::path workingPath = std::filesystem::current_path(); @@ -2182,6 +2363,8 @@ static void _imgui_spritesheets(Imgui* self) self->anm2->spritesheets[id].texture = texture; std::filesystem::current_path(workingPath); } + + imgui_log_push(self, IMGUI_LOG_RELOAD_SPRITESHEET); } if (_imgui_button(IMGUI_SPRITESHEETS_REPLACE.copy({highlightedID == ID_NONE}), self)) @@ -2488,6 +2671,21 @@ static void _imgui_spritesheet_editor(Imgui* self) _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); + if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_FIT.copy({self->editor->spritesheetID == ID_NONE}), self)) + { + vec4 rect = {0, 0, self->anm2->spritesheets[self->editor->spritesheetID].texture.size.x, + self->anm2->spritesheets[self->editor->spritesheetID].texture.size.y}; + + if ((rect.z > 0 && rect.w > 0)) + { + f32 scaleX = self->editor->canvas.size.x / rect.z; + f32 scaleY = self->editor->canvas.size.y / rect.w; + f32 fitScale = std::min(scaleX, scaleY); + + zoom = UNIT_TO_PERCENT(fitScale); + pan = {}; + } + } ImGui::Text(mousePositionString.c_str()); _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD @@ -2558,8 +2756,8 @@ static void _imgui_spritesheet_editor(Imgui* self) { if (self->settings->editorIsGridSnap) { - position.x = roundf(position.x / gridSize.x) * gridSize.x + gridOffset.x - (gridSize.x * 0.5f); - position.y = roundf(position.y / gridSize.y) * gridSize.y + gridOffset.y - (gridSize.y * 0.5f); + position.x = roundf((position.x - gridSize.x) / gridSize.x) * gridSize.x + gridOffset.x - (gridSize.x * 0.5f); + position.y = roundf((position.y - gridSize.y) / gridSize.y) * gridSize.y + gridOffset.y - (gridSize.y * 0.5f); } frame->pivot = position - frame->crop; @@ -2770,6 +2968,8 @@ static void _imgui_dock(Imgui* self) _imgui_spritesheets(self); _imgui_animation_preview(self); _imgui_spritesheet_editor(self); + _imgui_layers(self); + _imgui_nulls(self); _imgui_timeline(self); _imgui_onionskin(self); _imgui_frame_properties(self); @@ -2875,20 +3075,15 @@ void imgui_update(Imgui* self) { const char* droppedFile = event.drop.data; - if (path_is_extension(droppedFile, ANM2_EXTENSION)) - _imgui_anm2_open(self, droppedFile); - else if (path_is_extension(droppedFile, ANM2_SPRITESHEET_EXTENSION)) - _imgui_spritesheet_add(self, droppedFile); - else - imgui_log_push(self, IMGUI_LOG_DRAG_DROP_ERROR); + if (path_is_extension(droppedFile, ANM2_EXTENSION)) _imgui_anm2_open(self, droppedFile); + else if (path_is_extension(droppedFile, ANM2_SPRITESHEET_EXTENSION)) _imgui_spritesheet_add(self, droppedFile); + else imgui_log_push(self, IMGUI_LOG_DRAG_DROP_ERROR); break; } case SDL_EVENT_QUIT: - if (self->isTryQuit) - self->isQuit = true; - else - imgui_quit(self); + if (self->isTryQuit) self->isQuit = true; + else imgui_quit(self); break; default: break; diff --git a/src/imgui.h b/src/imgui.h index 1032b69..d169a7d 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -59,7 +59,7 @@ #define IMGUI_TIMELINE_FRAME_MULTIPLE 5 #define IMGUI_TIMELINE_MERGE #define IMGUI_TOOL_COLOR_PICKER_DURATION 0.25f -#define IMGUI_OPTION_POPUP_ROW_COUNT 2 +#define IMGUI_CONFIRM_POPUP_ROW_COUNT 2 #define IMGUI_CHORD_REPEAT_TIME 0.25f #define IMGUI_ACTION_FRAME_CROP "Frame Crop" @@ -68,14 +68,13 @@ #define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap" #define IMGUI_ACTION_TRIGGER_MOVE "Trigger At Frame" #define IMGUI_ACTION_FRAME_DELAY "Frame Delay" -#define IMGUI_ACTION_MOVE_PLAYHEAD "Move Playhead" #define IMGUI_ACTION_DRAW "Draw" #define IMGUI_ACTION_ERASE "Erase" #define IMGUI_ACTION_MOVE "Move" #define IMGUI_ACTION_SCALE "Scale" #define IMGUI_ACTION_ROTATE "Rotate" #define IMGUI_ACTION_CROP "Crop" -#define IMGUI_ACTION_RELOAD_SPRITESHEET "Reload Spritesheet(s)" +#define IMGUI_ACTION_ADD_SPRITESHEET "Add Spritesheet" #define IMGUI_ACTION_REPLACE_SPRITESHEET "Replace Spritesheet" #define IMGUI_ACTION_OPEN_FILE "Open File" @@ -96,6 +95,9 @@ #define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation." #define IMGUI_LOG_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" #define IMGUI_LOG_DRAG_DROP_ERROR "Invalid file for dragging/dropping!" +#define IMGUI_LOG_ANIMATION_PASTE_ERROR "Failed to parse clipboard text as an animation." +#define IMGUI_LOG_FRAME_PASTE_ERROR "Failed to parse clipboard text as a frame." +#define IMGUI_LOG_RELOAD_SPRITESHEET "Reloaded spritesheet(s)." #define IMGUI_NONE "None" #define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" @@ -110,6 +112,8 @@ #define IMGUI_SPRITESHEET_FORMAT "#{} {}" #define IMGUI_SPRITESHEET_ID_FORMAT "#{}" #define IMGUI_TIMELINE_ITEM_CHILD_FORMAT "#{} {}" +#define IMGUI_LAYER_FORMAT "#{} {}" +#define IMGUI_NULL_FORMAT "#{} {}" #define IMGUI_TIMELINE_FRAME_LABEL_FORMAT "## {}" #define IMGUI_SELECTABLE_INPUT_INT_FORMAT "#{}" #define IMGUI_TIMELINE_ANIMATION_NONE "Select an animation to show timeline..." @@ -347,7 +351,15 @@ static inline void imgui_copy(Imgui* self) static inline void imgui_paste(Imgui* self) { - clipboard_paste(self->clipboard); + if (!clipboard_paste(self->clipboard)) + { + switch (self->clipboard->type) + { + case CLIPBOARD_FRAME: imgui_log_push(self, IMGUI_LOG_FRAME_PASTE_ERROR); break; + case CLIPBOARD_ANIMATION: imgui_log_push(self, IMGUI_LOG_ANIMATION_PASTE_ERROR); break; + default: break; + } + } } static inline void imgui_onionskin_toggle(Imgui* self) @@ -604,7 +616,7 @@ enum ImguiItemType IMGUI_DOCKSPACE, IMGUI_CHILD, IMGUI_TABLE, - IMGUI_OPTION_POPUP, + IMGUI_CONFIRM_POPUP, IMGUI_SELECTABLE, IMGUI_BUTTON, IMGUI_RADIO_BUTTON, @@ -631,6 +643,7 @@ struct ImguiItemOverride AtlasType atlas = ATLAS_NONE; bool isMnemonicDisabled{}; s32 value{}; + s32 rowCount{}; }; struct ImguiItem; @@ -754,6 +767,7 @@ struct ImguiItem if (override.size != ImVec2{}) out.size = override.size; if (override.max != 0) out.max = override.max; if (override.value != 0) out.value = override.value; + if (override.rowCount != 0) out.rowCount = override.rowCount; if (override.atlas != ATLAS_NONE) out.atlas = override.atlas; if (override.isMnemonicDisabled) out.isMnemonicDisabled = override.isMnemonicDisabled; return out; @@ -888,6 +902,11 @@ IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION, self.text = "Unsaved changes will be lost!\nAre you sure you open a new file?" ); +IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION, + self.label = "No Anm2 Path", + self.text = "You will need to load or make a new .anm2 file first!\n" +); + IMGUI_ITEM(IMGUI_WIZARD, self.label = "&Wizard", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", @@ -996,7 +1015,7 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self.label = "Generate", self.tooltip = "Generate an animation with the used settings.", self.snapshotAction = "Generate Animation from Grid", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true ); @@ -1103,7 +1122,7 @@ IMGUI_ITEM(IMGUI_SCALE_ANM2_SCALE, self.label = "Scale", self.tooltip = "Scale the anm2 with the value specified.", self.snapshotAction = "Scale Anm2", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true ); @@ -1111,12 +1130,20 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION, self.label = "&Render Animation", self.tooltip = "Renders the current animation preview; output options can be customized.", self.popup = "Render Animation", - self.popupSize = {600, 125} + self.popupSize = {600, 150}, + self.popupType = IMGUI_POPUP_CENTER_WINDOW ); IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, self.label = "## Render Animation Child", - self.size = {600, 125} + self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_RENDER_ANIMATION.popupSize.y - IMGUI_FOOTER_CHILD.size.y}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, + self.label = "## Render Animation Footer Child", + self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_FOOTER_CHILD.size.y}, + self.flags = true ); IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, @@ -1149,8 +1176,7 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, self.label = "Output", self.tooltip = "Select the rendered animation output.\nIt can either be one animated image or a sequence of frames.", self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, - self.value = RENDER_PNG, - self.isSeparator = true + self.value = RENDER_PNG ); IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, @@ -1166,7 +1192,7 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM, self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {300, 60}, self.isSameLine = true, - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT ); IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CHILD, @@ -1259,6 +1285,73 @@ IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, self.isSizeToText = true ); +IMGUI_ITEM(IMGUI_LAYERS, + self.label = "Layers", + self.flags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse +); +IMGUI_ITEM(IMGUI_LAYERS_CHILD, self.label = "## Layers Child", self.flags = true); + +IMGUI_ITEM(IMGUI_LAYER, + self.label = "## Layer Item", + self.dragDrop = "## Layer Drag Drop", + self.atlas = ATLAS_LAYER, + self.idOffset = 3000 +); + +IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID, + self.label = "## Spritesheet ID", + self.tooltip = "Change the spritesheet ID this layer uses.", + self.atlas = ATLAS_SPRITESHEET, + self.size = {50, 0} +); + +#define IMGUI_LAYERS_OPTIONS_ROW_COUNT 2 +IMGUI_ITEM(IMGUI_LAYER_ADD, + self.label = "Add", + self.tooltip = "Adds a new layer.", + self.snapshotAction = "Add Layer", + self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_LAYER_REMOVE, + self.label = "Remove", + self.tooltip = "Removes the selected layer.\nThis will remove all layer animations that use this layer from all animations.", + self.snapshotAction = "Remove Layer", + self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_NULLS, + self.label = "Nulls", + self.flags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse +); +IMGUI_ITEM(IMGUI_NULLS_CHILD, self.label = "## Nulls Child", self.flags = true); + +IMGUI_ITEM(IMGUI_NULL, + self.label = "## Null Item", + self.dragDrop = "## Null Drag Drop", + self.atlas = ATLAS_NULL, + self.idOffset = 4000 +); + +#define IMGUI_NULLS_OPTIONS_ROW_COUNT 2 +IMGUI_ITEM(IMGUI_NULL_ADD, + self.label = "Add", + self.tooltip = "Adds a null layer.", + self.snapshotAction = "Add Null", + self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_NULL_REMOVE, + self.label = "Remove", + self.tooltip = "Removes the selected null.\nThis will remove all null animations that use this null from all animations.", + self.snapshotAction = "Remove Null", + self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT +); + IMGUI_ITEM(IMGUI_ANIMATIONS, self.label = "Animations", self.flags = ImGuiWindowFlags_NoScrollbar | @@ -1354,7 +1447,7 @@ IMGUI_ITEM(IMGUI_MERGE_CONFIRM, self.label = "Merge", self.tooltip = "Merge the selected animations with the options set.", self.snapshotAction = "Merge Animations", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true ); @@ -1440,15 +1533,15 @@ IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, #define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3 IMGUI_ITEM(IMGUI_SPRITESHEET_ADD, self.label = "Add", - self.tooltip = "Select an image to add as a spritesheet.", + self.tooltip = "Select a .png image to add as a spritesheet.", self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, self.isSameLine = true ); IMGUI_ITEM(IMGUI_SPRITESHEETS_RELOAD, self.label = "Reload", - self.tooltip = "Reload the selected spritesheet.", - self.snapshotAction = "Reload Spritesheet", + self.tooltip = "Reload the selected spritesheet(s).", + self.snapshotAction = "Reload Spritesheet(s)", self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, self.isSameLine = true ); @@ -1490,7 +1583,9 @@ const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {230, 85}; IMGUI_ITEM(IMGUI_CANVAS_GRID_CHILD, self.label = "## Canvas Grid Child", self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); IMGUI_ITEM(IMGUI_CANVAS_GRID, @@ -1525,7 +1620,9 @@ IMGUI_ITEM(IMGUI_CANVAS_GRID_OFFSET, IMGUI_ITEM(IMGUI_CANVAS_VIEW_CHILD, self.label = "## View Child", self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); IMGUI_ITEM(IMGUI_CANVAS_ZOOM, @@ -1537,12 +1634,12 @@ IMGUI_ITEM(IMGUI_CANVAS_ZOOM, self.value = CANVAS_ZOOM_DEFAULT ); - - IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD, self.label = "## Animation Preview Visual Child", self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); IMGUI_ITEM(IMGUI_CANVAS_BACKGROUND_COLOR, @@ -1566,7 +1663,9 @@ IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD, self.label = "## Animation Preview Helper Child", self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); IMGUI_ITEM(IMGUI_CANVAS_AXES, @@ -1644,12 +1743,22 @@ IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR, self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); +#define IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT 2 IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW, self.label = "Center View", self.tooltip = "Centers the current view on the spritesheet editor.", self.hotkey = HOTKEY_CENTER_VIEW, self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, - self.size = {-FLT_MIN, 0} + self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_FIT, + self.label = "Fit", + self.tooltip = "Adjust the view/pan based on the size of the spritesheet, to fit the canvas' size.", + self.hotkey = HOTKEY_FIT, + self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, + self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT ); IMGUI_ITEM(IMGUI_FRAME_PROPERTIES, self.label = "Frame Properties"); @@ -2000,6 +2109,20 @@ const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT] &IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE }; +IMGUI_ITEM(IMGUI_TIMELINE_SHOW_UNUSED, + self.label = "## Show Unused", + self.tooltip = "Layers/nulls without any frames will be hidden.", + self.snapshotAction = "Hide Unused", + self.atlas = ATLAS_SHOW_UNUSED +); + +IMGUI_ITEM(IMGUI_TIMELINE_HIDE_UNUSED, + self.label = "## Hide Unused", + self.tooltip = "Layers/nulls without any frames will be shown.", + self.snapshotAction = "Show Unused", + self.atlas = ATLAS_HIDE_UNUSED +); + IMGUI_ITEM(IMGUI_TIMELINE_ITEM_VISIBLE, self.label = "## Visible", self.tooltip = "The item is visible.\nPress to set to invisible.", @@ -2028,12 +2151,6 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_HIDE_RECT, self.atlas = ATLAS_HIDE_RECT ); -IMGUI_ITEM(IMGUI_TIMELINE_SPRITESHEET_ID, - self.label = "## Spritesheet ID", - self.tooltip = "Change the spritesheet ID this item uses.", - self.atlas = ATLAS_SPRITESHEET, - self.size = {32, 0} -); IMGUI_ITEM(IMGUI_TIMELINE_FRAMES_CHILD, self.label = "## Timeline Frames Child", @@ -2095,36 +2212,68 @@ const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT] IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self.label = "## Item Footer Child", self.size = {IMGUI_TIMELINE_ITEM_CHILD.size.x, IMGUI_FOOTER_CHILD.size.y}, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); IMGUI_ITEM(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, self.label = "## Options Footer Child", self.size = {0, IMGUI_FOOTER_CHILD.size.y}, - self.flags = true + self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse ); #define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2 IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM, self.label = "Add", - self.tooltip = "Adds an item (layer or null) to the animation.", - self.popup = "## Add Item Popup", - self.popupType = IMGUI_POPUP_BY_ITEM, + self.tooltip = "Adds an item (layer or null) to the animation.\nMake sure to add a Layer/Null first in the Layers or Nulls windows.", + self.popup = "Add Item", + self.popupType = IMGUI_POPUP_CENTER_WINDOW, + self.popupSize = {300, 350}, self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT, self.isSameLine = true ); +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD, + self.label = "## Add Item Type Child", + self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 35}, + self.flags = true +); + IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_LAYER, self.label = "Layer", self.tooltip = "Adds a layer item.\nA layer item is a primary graphical item, using a spritesheet.", - self.snapshotAction = "Add Layer", - self.isSizeToText = true + self.isSizeToText = true, + self.value = ANM2_LAYER, + self.isSameLine = true ); IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_NULL, self.label = "Null", - self.tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by the game engine.", - self.snapshotAction = "Add Null", - self.isSizeToText = true + self.tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by a game engine.", + self.isSizeToText = true, + self.value = ANM2_NULL +); + +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD, + self.label = "## Add Item Items", + self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 250}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_OPTIONS_CHILD, + self.label = "## Add Item Options Child", + self.size = {IMGUI_TIMELINE_ADD_ITEM.popupSize.x, 35}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM_ADD, + self.label = "Add", + self.tooltip = "Add the selected item.", + self.snapshotAction = "Add Animation", + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, + self.isSameLine = true ); IMGUI_ITEM(IMGUI_TIMELINE_REMOVE_ITEM, @@ -2211,7 +2360,7 @@ IMGUI_ITEM(IMGUI_BAKE_CONFIRM, self.label = "Bake", self.tooltip = "Bake the selected frame with the options selected.", self.snapshotAction = "Bake Frames", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT, + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true ); @@ -2353,17 +2502,17 @@ IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, self.step = 0 ); -#define IMGUI_OPTION_POPUP_ROW_COUNT 2 +#define IMGUI_CONFIRM_POPUP_ROW_COUNT 2 IMGUI_ITEM(IMGUI_POPUP_OK, self.label = "OK", self.tooltip = "Confirm the action.", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT ); IMGUI_ITEM(IMGUI_POPUP_CANCEL, self.label = "Cancel", self.tooltip = "Cancel the action.", - self.rowCount = IMGUI_OPTION_POPUP_ROW_COUNT + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT ); IMGUI_ITEM(IMGUI_LOG_WINDOW, diff --git a/src/preview.cpp b/src/preview.cpp index 2dbccbf..b5610f2 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -40,7 +40,7 @@ void preview_tick(Preview* self) glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data()); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - texture_from_rgba_init(&frameTexture, size, TEXTURE_CHANNELS, framebufferPixels.data()); + texture_from_rgba_init(&frameTexture, size, framebufferPixels.data()); self->renderFrames.push_back(frameTexture); } @@ -199,7 +199,7 @@ void preview_draw(Preview* self) if (self->settings->previewIsIcons && animation->rootAnimation.isVisible && root.isVisible) root_draw(root, colorOffset, alphaOffset, isOnionskin); - for (auto [i, id] : self->anm2->layerMap) + for (auto id : animation->layerOrder) layer_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); if (self->settings->previewIsIcons) diff --git a/src/resources.cpp b/src/resources.cpp index 5e57912..09f0ea5 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -2,7 +2,7 @@ void resources_init(Resources* self) { - texture_from_encoded_data_init(&self->atlas, TEXTURE_ATLAS_SIZE, TEXTURE_CHANNELS, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH); + texture_from_encoded_data_init(&self->atlas, TEXTURE_ATLAS_SIZE, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH); for (s32 i = 0; i < SHADER_COUNT; i++) shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); diff --git a/src/settings.h b/src/settings.h index 5c11992..b180c30 100644 --- a/src/settings.h +++ b/src/settings.h @@ -28,7 +28,7 @@ #define SETTINGS_LIST \ /* name, symbol, type, defaultValue */ \ - X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1280, 720}) \ + X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1600, 900}) \ X(isVsync, IS_VSYNC, TYPE_BOOL, true) \ \ X(hotkeyCenterView, HOTKEY_CENTER_VIEW, TYPE_STRING, "Home") \ @@ -130,6 +130,9 @@ X(bakeIsRoundScale, BAKE_IS_ROUND_SCALE, TYPE_BOOL, true) \ X(bakeIsRoundRotation, BAKE_IS_ROUND_ROTATION, TYPE_BOOL, true) \ \ + X(timelineAddItemType, TIMELINE_ADD_ITEM_TYPE, TYPE_INT, ANM2_LAYER) \ + X(timelineIsShowUnused, TIMELINE_IS_SHOW_UNUSED, TYPE_BOOL, true) \ + \ X(onionskinIsEnabled, ONIONSKIN_IS_ENABLED, TYPE_BOOL, false) \ X(onionskinDrawOrder, ONIONSKIN_DRAW_ORDER, TYPE_INT, ONIONSKIN_BELOW) \ X(onionskinBeforeCount, ONIONSKIN_BEFORE_COUNT, TYPE_INT, 1) \ @@ -262,72 +265,86 @@ Collapsed=0 [Window][Tools] Pos=8,40 -Size=37,460 +Size=38,516 Collapsed=0 DockId=0x0000000B,0 [Window][Animations] -Pos=1288,284 -Size=304,216 +Pos=1289,307 +Size=303,249 Collapsed=0 DockId=0x0000000A,0 [Window][Events] -Pos=1007,332 -Size=279,168 +Pos=957,264 +Size=330,292 Collapsed=0 -DockId=0x00000008,0 +DockId=0x00000008,2 [Window][Spritesheets] -Pos=1288,40 -Size=304,242 +Pos=1289,40 +Size=303,265 Collapsed=0 DockId=0x00000009,0 [Window][Animation Preview] -Pos=47,40 -Size=958,460 +Pos=48,40 +Size=907,516 Collapsed=0 DockId=0x0000000C,0 [Window][Spritesheet Editor] -Pos=47,40 -Size=958,460 +Pos=48,40 +Size=907,516 Collapsed=0 DockId=0x0000000C,1 [Window][Timeline] -Pos=8,502 -Size=1584,390 +Pos=8,558 +Size=1584,334 Collapsed=0 DockId=0x00000004,0 [Window][Frame Properties] -Pos=1007,40 -Size=279,290 +Pos=957,40 +Size=330,222 Collapsed=0 DockId=0x00000007,0 [Window][Onionskin] -Pos=8,502 -Size=1584,390 +Pos=957,264 +Size=330,292 Collapsed=0 -DockId=0x00000004,1 +DockId=0x00000008,3 + +[Window][Layers] +Pos=957,264 +Size=330,292 +Collapsed=0 +DockId=0x00000008,0 + +[Window][Nulls] +Pos=957,264 +Size=330,292 +Collapsed=0 +DockId=0x00000008,1 + [Docking][Data] DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1584,852 Split=Y - DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,568 Split=X - DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1595,1016 Split=X Selected=0x024430EF - DockNode ID=0x00000005 Parent=0x00000001 SizeRef=997,654 Split=X Selected=0x024430EF - DockNode ID=0x0000000B Parent=0x00000005 SizeRef=37,654 Selected=0x18A5FDB9 - DockNode ID=0x0000000C Parent=0x00000005 SizeRef=958,654 CentralNode=1 Selected=0x024430EF - DockNode ID=0x00000006 Parent=0x00000001 SizeRef=279,654 Split=Y Selected=0x754E368F - DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,359 Selected=0x754E368F - DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,207 Selected=0x8A65D963 - DockNode ID=0x00000002 Parent=0x00000003 SizeRef=304,1016 Split=Y Selected=0x4EFD0020 - DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,299 Selected=0x4EFD0020 - DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,267 Selected=0xC1986EE2 - DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,390 Selected=0x4F89F0DC + DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,680 Split=X + DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1017,1016 Split=X Selected=0x024430EF + DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1264,654 Split=X Selected=0x024430EF + DockNode ID=0x0000000B Parent=0x00000005 SizeRef=38,654 Selected=0x18A5FDB9 + DockNode ID=0x0000000C Parent=0x00000005 SizeRef=1224,654 CentralNode=1 Selected=0x024430EF + DockNode ID=0x00000006 Parent=0x00000001 SizeRef=330,654 Split=Y Selected=0x754E368F + DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,293 Selected=0x754E368F + DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,385 Selected=0xCD8384B1 + DockNode ID=0x00000002 Parent=0x00000003 SizeRef=303,1016 Split=Y Selected=0x4EFD0020 + DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,349 Selected=0x4EFD0020 + DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,329 Selected=0xC1986EE2 + DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,334 Selected=0x4F89F0DC + )"; void settings_save(Settings* self); diff --git a/src/snapshots.cpp b/src/snapshots.cpp index 0f486b7..2c8cf46 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -26,13 +26,13 @@ static void _snapshot_set(Snapshots* self, Snapshot* snapshot) self->preview->time = snapshot->time; self->action = snapshot->action; - //anm2_spritesheet_texture_pixels_upload(self->anm2); + anm2_spritesheet_texture_pixels_upload(self->anm2); } Snapshot snapshot_get(Snapshots* self) { Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, self->action}; - //anm2_spritesheet_texture_pixels_download(&snapshot.anm2); + anm2_spritesheet_texture_pixels_download(&snapshot.anm2); return snapshot; } diff --git a/src/state.cpp b/src/state.cpp index 20ba040..5c3855a 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -116,6 +116,8 @@ bool sdl_init(State* self, bool isTestMode = false) void init(State* self) { + log_info(STATE_INIT_INFO); + settings_init(&self->settings); if (!sdl_init(self)) return; diff --git a/src/state.h b/src/state.h index bfc3424..79fbe9c 100644 --- a/src/state.h +++ b/src/state.h @@ -2,7 +2,7 @@ #include "imgui.h" -#define STATE_INIT_INFO "Initializing..." +#define STATE_INIT_INFO "Initializing anm2ed (Version 1.1)" #define STATE_SDL_INIT_ERROR "Failed to initialize SDL! {}" #define STATE_SDL_INIT_INFO "Initialized SDL" #define STATE_MIX_INIT_WARNING "Unable to initialize SDL_mixer! {}" diff --git a/src/texture.cpp b/src/texture.cpp index 6bdbba2..c09424f 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -17,8 +17,7 @@ static void _texture_gl_set(Texture* self, const u8* data) { - if (self->id == GL_ID_NONE) - glGenTextures(1, &self->id); + if (self->id == GL_ID_NONE) glGenTextures(1, &self->id); glBindTexture(GL_TEXTURE_2D, self->id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); @@ -43,7 +42,7 @@ std::vector texture_download(const Texture* self) bool texture_from_path_init(Texture* self, const std::string& path) { - u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS); + u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS); if (!data) { @@ -60,13 +59,12 @@ bool texture_from_path_init(Texture* self, const std::string& path) return true; } -bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, const u8* data, u32 length) +bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length) { *self = Texture{}; self->size = size; - self->channels = channels; - u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS); + u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS); if (!textureData) { @@ -79,11 +77,11 @@ bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, con return true; } -bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* data) +bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data) { *self = Texture{}; self->size = size; - self->channels = channels; + self->isInvalid = false; _texture_gl_set(self, data); diff --git a/src/texture.h b/src/texture.h index 6339d4b..c0cb7e1 100644 --- a/src/texture.h +++ b/src/texture.h @@ -12,14 +12,13 @@ struct Texture { GLuint id = GL_ID_NONE; ivec2 size{}; - s32 channels = TEXTURE_CHANNELS; bool isInvalid = true; }; -bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, const u8* data, u32 length); +bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length); bool texture_from_gl_write(Texture* self, const std::string& path); bool texture_from_path_init(Texture* self, const std::string& path); -bool texture_from_rgba_init(Texture* self, ivec2 size, s32 channels, const u8* data); +bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data); bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size); bool texture_pixel_set(Texture* self, ivec2 position, vec4 color); void texture_free(Texture* self);