From 9fb6366d7c4ef306234674a812a2c8ebc9b0dfbc Mon Sep 17 00:00:00 2001 From: shweet Date: Mon, 15 Sep 2025 19:27:07 -0400 Subject: [PATCH] Frame movement on timeline changed --- src/COMMON.h | 8 ++ src/anm2.cpp | 125 +++++++++++++++++- src/anm2.h | 6 +- src/imgui.cpp | 292 ++++++++++++++++++++++++++++++------------ src/imgui.h | 104 ++++++++------- src/preview.cpp | 1 + src/settings.h | 2 +- workshop/metadata.xml | 2 +- 8 files changed, 401 insertions(+), 139 deletions(-) diff --git a/src/COMMON.h b/src/COMMON.h index 4718041..9c8948c 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -114,6 +114,14 @@ static inline std::string string_to_lowercase(std::string string) { return string; } +static inline std::string string_backslash_replace(std::string string) +{ + for (char& character : string) + if (character == '\\') + character = '/'; + return string; +} + #define FLOAT_FORMAT_MAX_DECIMALS 5 #define FLOAT_FORMAT_EPSILON 1e-5f static constexpr f32 FLOAT_FORMAT_POW10[] = { diff --git a/src/anm2.cpp b/src/anm2.cpp index 70a74b9..c65d2ec 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -450,9 +450,12 @@ bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) // the paths are case-insensitive (as the game was developed on Windows) // However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyways) // If the check doesn't work, set the spritesheet path to lowercase + // If the check doesn't work, replace backslashes with slashes + // At the minimum this should make all textures be able to be loaded on Linux // If it doesn't work beyond that then that's on the user :^) addSpritesheet.path = attribute->Value(); if (!path_exists(addSpritesheet.path)) addSpritesheet.path = string_to_lowercase(addSpritesheet.path); + if (!path_exists(addSpritesheet.path)) addSpritesheet.path = string_backslash_replace(addSpritesheet.path); if (isTextures) texture_from_path_init(&addSpritesheet.texture, addSpritesheet.path); break; case ANM2_ATTRIBUTE_ID: id = std::atoi(attribute->Value()); break; @@ -846,9 +849,9 @@ Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference } else { - s32 index = reference->frameIndex + 1; + s32 index = reference->frameIndex; - if (index >= (s32)item->frames.size()) + if (index < 0 || index >= (s32)item->frames.size()) { item->frames.push_back(frameAdd); return &item->frames.back(); @@ -951,13 +954,13 @@ void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& m { switch (type) { - case ANM2_MERGE_APPEND_FRAMES: + case ANM2_MERGE_APPEND: destinationItem.frames.insert(destinationItem.frames.end(), sourceItem.frames.begin(), sourceItem.frames.end()); break; - case ANM2_MERGE_PREPEND_FRAMES: + case ANM2_MERGE_PREPEND: destinationItem.frames.insert(destinationItem.frames.begin(), sourceItem.frames.begin(), sourceItem.frames.end()); break; - case ANM2_MERGE_REPLACE_FRAMES: + case ANM2_MERGE_REPLACE: if (destinationItem.frames.size() < sourceItem.frames.size()) destinationItem.frames.resize(sourceItem.frames.size()); for (s32 i = 0; i < (s32)sourceItem.frames.size(); i++) @@ -1230,4 +1233,114 @@ bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml) _anm2_frame_deserialize(frame, element); return true; -} \ No newline at end of file +} + +/* +void anm2_merge(Anm2* self, const std::string& path, Anm2MergeType type) +{ + Anm2 anm2; + + if (anm2_deserialize(&anm2, path, false)) + { + std::unordered_map spritesheetMap; + for (auto& [id, spritesheet] : anm2.spritesheets) + { + bool isExists = false; + + for (auto& [selfID, selfSpritesheet] : self->spritesheets) + { + if (spritesheet.path == selfSpritesheet.path) isExists = true; + spritesheetMap[id] = selfID; + } + + if (isExists) continue; + + s32 nextID = map_next_id_get(self->spritesheets); + self->spritesheet[nextID] = spritesheet; + spritesheetMap[id] = nextID; + } + + std::unordered_map layerMap; + for (auto& [id, layer] : anm2.layers) + { + bool isExists = false; + + layer.spritesheetID = spritesheetMap[layer.spritesheetID]; + + for (auto& [selfID, selfLayer] : self->layers) + { + if (layer.name == selfLayer.name) isExists = true; + layerMap[id] = selfID; + } + + if (isExists) continue; + + s32 nextID = map_next_id_get(self->layers); + self->layer[nextID] = layer; + layerMap[id] = nextID; + } + + std::unordered_map nullMap; + for (auto& [id, null] : anm2.nulls) + { + bool isExists = false; + + for (auto& [selfID, selfNull] : self->nulls) + { + if (null.name == selfNull.name) isExists = true; + nullMap[id] = selfID; + } + + if (isExists) continue; + + s32 nextID = map_next_id_get(self->nulls); + self->null[nextID] = null; + nullMap[id] = nextID; + } + + std::unordered_map eventMap; + for (auto& [id, event] : anm2.events) + { + bool isExists = false; + + for (auto& [selfID, selfEvent] : self->events) + { + if (event.name == selfEvent.name) isExists = true; + eventMap[id] = selfID; + } + + if (isExists) continue; + + s32 nextID = map_next_id_get(self->events); + self->event[nextID] = event; + eventMap[id] = nextID; + } + + for (auto& [id, animation] : anm2.animations) + { + bool isExists = false; + + for (auto& [selfID, selfAnimation] : self->animations) + { + if (event.name == selfAnimation.name) isExists = true; + eventMap[id] = selfID; + } + + if (isExists) continue; + + for (auto& frame : animation.rootAnimation.frames) + { + + + } + + for (auto& [layerID, layerAnimation] : animation.layerAnimations) + { + s32 newLayerID = layerMap[layerID]; + + + } + } + } +} +*/ \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 5d82032..02c8614 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -249,9 +249,9 @@ struct Anm2FrameChange enum Anm2MergeType { - ANM2_MERGE_APPEND_FRAMES, - ANM2_MERGE_REPLACE_FRAMES, - ANM2_MERGE_PREPEND_FRAMES, + ANM2_MERGE_APPEND, + ANM2_MERGE_REPLACE, + ANM2_MERGE_PREPEND, ANM2_MERGE_IGNORE }; diff --git a/src/imgui.cpp b/src/imgui.cpp index 43321b8..05a57c1 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -706,7 +706,7 @@ static void _imgui_timeline(Imgui* self) ImVec2 scrollDelta{}; - if (_imgui_is_window_hovered()) + if (_imgui_is_window_hovered() && !imgui_is_any_popup_open()) { ImGuiIO& io = ImGui::GetIO(); f32 lineHeight = ImGui::GetTextLineHeight(); @@ -851,6 +851,7 @@ static void _imgui_timeline(Imgui* self) ) ) *self->reference = reference; + break; case ANM2_NULL: null = &self->anm2->nulls[reference.itemID]; @@ -869,6 +870,117 @@ static void _imgui_timeline(Imgui* self) break; } + if (imgui_begin_popup_modal(IMGUI_POPUP_ITEM_PROPERTIES, self, IMGUI_POPUP_ITEM_PROPERTIES_SIZE)) + { + static s32 selectedLayerID = ID_NONE; + static s32 selectedNullID = ID_NONE; + Anm2Type& type = reference.itemType; + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); + + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER.copy({true}), self, (s32&)type); + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL.copy({true}), self, (s32&)type); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); + + switch (type) + { + case ANM2_LAYER: + default: + { + for (auto & [id, layer] : self->anm2->layers) + { + if (id == reference.itemID) continue; + + 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) + { + if (id == reference.itemID) continue; + + 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_ITEM_PROPERTIES_ITEMS_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_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_ITEM_PROPERTIES_CONFIRM.copy({isDisabled}), self)) + { + switch (type) + { + case ANM2_LAYER: + if (!map_find(animation->layerAnimations, selectedLayerID)) + { + anm2_animation_layer_animation_add(animation, selectedLayerID); + anm2_animation_layer_animation_remove(animation, reference.itemID); + } + else + map_swap(animation->layerAnimations, selectedLayerID, reference.itemID); + + *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; + break; + case ANM2_NULL: + if (!map_find(animation->nullAnimations, selectedNullID)) + { + anm2_animation_null_animation_add(animation, selectedNullID); + anm2_animation_null_animation_remove(animation, reference.itemID); + + } + else + map_swap(animation->nullAnimations, selectedNullID, reference.itemID); + + *self->reference = {self->reference->animationID, ANM2_NULL, 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_ITEM_PROPERTIES_OPTIONS_CHILD + + imgui_end_popup(self); + } + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None) && !dragDrop.empty()) { @@ -940,6 +1052,8 @@ static void _imgui_timeline(Imgui* self) { Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + imgui_snapshot(self, IMGUI_ACTION_ITEM_SWAP); + switch (swapItemReference.itemType) { case ANM2_LAYER: @@ -990,6 +1104,8 @@ static void _imgui_timeline(Imgui* self) _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); } } + else + hoverReference.frameIndex = INDEX_NONE; s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; if (start < 0) start = 0; @@ -1018,8 +1134,8 @@ static void _imgui_timeline(Imgui* self) static s32 frameDelayStart{}; static f32 frameDelayTimeStart{}; const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); - static Anm2Frame* draggingFrame = nullptr; - static Anm2Type draggingFrameType = ANM2_NONE; + static bool isFrameSwap = false; + static bool isDrag = false; ImGui::PushID(i); reference.frameIndex = i; @@ -1037,90 +1153,91 @@ static void _imgui_timeline(Imgui* self) ImGui::SetCursorPos(framePos); - if (_imgui_atlas_button(frameButton, self)) *self->reference = reference; - - if (ImGui::IsItemActivated()) + if (_imgui_atlas_button(frameButton, self)) { - if (type == ANM2_TRIGGERS || isModCtrl) - { - draggingFrame = &frame; - draggingFrameType = type; - *self->reference = reference; - } - - if (type == ANM2_TRIGGERS) - imgui_snapshot(self, IMGUI_ACTION_TRIGGER_MOVE); - else if (isModCtrl) - { - imgui_snapshot(self, IMGUI_ACTION_FRAME_DELAY); - frameDelayStart = draggingFrame->delay; - frameDelayTimeStart = frameTime; - } + *self->reference = reference; + frameDelayStart = frame.delay; + frameDelayTimeStart = frameTime; } - if (draggingFrame) + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoDisableHover)) { - if (draggingFrameType == ANM2_TRIGGERS) + if (!isDrag) { - draggingFrame->atFrame = std::max(frameTime, 0); - for (auto& frameCheck : animation->triggers.frames) + *self->reference = reference; + frameDelayStart = frame.delay; + frameDelayTimeStart = frameTime; + isFrameSwap = false; + isDrag = true; + } + + ImGui::SetDragDropPayload(frameButton.drag_drop_get(), &reference, sizeof(Anm2Reference)); + if (!isModCtrl) timeline_item_frame(i, frame); + ImGui::EndDragDropSource(); + } + + if (isDrag) + { + if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, self->reference)) + { + switch (self->reference->itemType) { - if (draggingFrame == &frameCheck) continue; - if (draggingFrame->atFrame == frameCheck.atFrame) + case ANM2_TRIGGERS: { - draggingFrame->atFrame++; + referenceFrame->atFrame = std::max(frameTime, 0); + for (auto& trigger : animation->triggers.frames) + { + if (&trigger == referenceFrame) continue; + if (trigger.atFrame == referenceFrame->atFrame) + { + referenceFrame->atFrame++; break; + } + } break; } - } - } - else if (isModCtrl) - draggingFrame->delay = std::max(frameDelayStart + (s32)(frameTime - frameDelayTimeStart), ANM2_FRAME_NUM_MIN); - - if (ImGui::IsMouseReleased(0)) - { - draggingFrame = nullptr; - draggingFrameType = ANM2_NONE; - } - } - else - { - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload(frameButton.drag_drop_get(), &reference, sizeof(Anm2Reference)); - timeline_item_frame(i, frame); - ImGui::EndDragDropSource(); - } - - if (ImGui::BeginDragDropTarget()) - { - Anm2Reference swapReference; - if (const ImGuiPayload* payload = ImGui::GetDragDropPayload()) - swapReference = *(Anm2Reference*)payload->Data; - - if (swapReference != reference && reference.itemType == swapReference.itemType) - { - if (ImGui::AcceptDragDropPayload(frameButton.drag_drop_get())) + case ANM2_ROOT: + case ANM2_LAYER: + case ANM2_NULL: { - imgui_snapshot(self, IMGUI_ACTION_FRAME_SWAP); - - Anm2Frame* swapFrame = anm2_frame_from_reference(self->anm2, &reference); - Anm2Frame* dragFrame = anm2_frame_from_reference(self->anm2, &swapReference); - - if (swapFrame && dragFrame) - { - Anm2Frame oldFrame = *swapFrame; - - *swapFrame = *dragFrame; - *dragFrame = oldFrame; - - *self->reference = swapReference; - } + if (isModCtrl) + referenceFrame->delay = std::max(frameDelayStart + (s32)(frameTime - frameDelayTimeStart), ANM2_FRAME_NUM_MIN); + break; } + default: break; } - ImGui::EndDragDropTarget(); } } - + + if (ImGui::BeginDragDropTarget()) + { + ImGui::AcceptDragDropPayload(frameButton.drag_drop_get()); + ImGui::EndDragDropTarget(); + } + + if (isDrag && ImGui::IsMouseReleased(0)) + { + if + ( !isFrameSwap && + *self->reference != hoverReference && + self->reference->itemType == hoverReference.itemType + ) + { + imgui_snapshot(self, IMGUI_ACTION_FRAME_MOVE); + if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, self->reference)) + { + Anm2Reference addReference = hoverReference; + addReference.frameIndex = std::min(0, addReference.frameIndex - 1); + Anm2Frame addFrame = *referenceFrame; + anm2_frame_remove(self->anm2, self->reference); + anm2_frame_add(self->anm2, &addFrame, &hoverReference); + *self->reference = hoverReference; + hoverReference = Anm2Reference(); + } + } + + isDrag = false; + } + if (i < (s32)item->frames.size() - 1) ImGui::SameLine(); ImGui::PopID(); @@ -1201,14 +1318,14 @@ static void _imgui_timeline(Imgui* self) static s32 selectedNullID = ID_NONE; s32& type = self->settings->timelineAddItemType; - _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD, self); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); - _imgui_radio_button(IMGUI_TIMELINE_ADD_ITEM_LAYER, self, type); - _imgui_radio_button(IMGUI_TIMELINE_ADD_ITEM_NULL, self, type); + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self, type); + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self, type); - _imgui_end_child(); // IMGUI_TIMELINE_ADD_ITEM_TYPE_CHILD + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD - _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD, self); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); switch (type) { @@ -1251,9 +1368,9 @@ static void _imgui_timeline(Imgui* self) } } - _imgui_end_child(); // IMGUI_TIMELINE_ADD_ITEM_ITEMS_CHILD + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD - _imgui_begin_child(IMGUI_TIMELINE_ADD_ITEM_OPTIONS_CHILD, self); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self); if (self->anm2->layers.size() == 0) selectedLayerID = ID_NONE; if (self->anm2->nulls.size() == 0) selectedNullID = ID_NONE; @@ -1262,20 +1379,27 @@ static void _imgui_timeline(Imgui* self) (type == ANM2_LAYER && selectedLayerID == ID_NONE) || (type == ANM2_NULL && selectedNullID == ID_NONE); - if (_imgui_button(IMGUI_TIMELINE_ADD_ITEM_ADD.copy({isDisabled}), self)) + if (_imgui_button(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM.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; + case ANM2_LAYER: + anm2_animation_layer_animation_add(animation, selectedLayerID); + *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; + break; + case ANM2_NULL: + anm2_animation_null_animation_add(animation, selectedNullID); + *self->reference = {self->reference->animationID, ANM2_NULL, 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_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD imgui_end_popup(self); } diff --git a/src/imgui.h b/src/imgui.h index 85d2cd8..1ccc1f5 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -63,10 +63,11 @@ #define IMGUI_CHORD_REPEAT_TIME 0.25f #define IMGUI_ACTION_FRAME_CROP "Frame Crop" -#define IMGUI_ACTION_FRAME_SWAP "Frame Swap" +#define IMGUI_ACTION_FRAME_MOVE "Frame Move" #define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap" #define IMGUI_ACTION_TRIGGER_MOVE "Trigger At Frame" +#define IMGUI_ACTION_ITEM_SWAP "Item Swap" #define IMGUI_ACTION_FRAME_DELAY "Frame Delay" #define IMGUI_ACTION_DRAW "Draw" #define IMGUI_ACTION_ERASE "Erase" @@ -78,6 +79,7 @@ #define IMGUI_ACTION_REPLACE_SPRITESHEET "Replace Spritesheet" #define IMGUI_ACTION_OPEN_FILE "Open File" +#define IMGUI_SET_ITEM_PROPERTIES_POPUP "Item Properties" #define IMGUI_POPUP_FLAGS ImGuiWindowFlags_NoMove #define IMGUI_POPUP_MODAL_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize @@ -1429,20 +1431,20 @@ IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT, self.label = "On Conflict"); IMGUI_ITEM(IMGUI_MERGE_APPEND_FRAMES, self.label = "Append Frames ", self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames appended.", - self.value = ANM2_MERGE_APPEND_FRAMES, + self.value = ANM2_MERGE_APPEND, self.isSameLine = true ); IMGUI_ITEM(IMGUI_MERGE_REPLACE_FRAMES, self.label = "Replace Frames", self.tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", - self.value = ANM2_MERGE_REPLACE_FRAMES + self.value = ANM2_MERGE_REPLACE ); IMGUI_ITEM(IMGUI_MERGE_PREPEND_FRAMES, self.label = "Prepend Frames", self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames prepended.", - self.value = ANM2_MERGE_PREPEND_FRAMES, + self.value = ANM2_MERGE_PREPEND, self.isSameLine = true ); @@ -2085,6 +2087,51 @@ const inline ImguiItem* IMGUI_TIMELINE_ITEM_CHILDS[ANM2_COUNT] &IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD }; +#define IMGUI_POPUP_ITEM_PROPERTIES "Item Properties" +#define IMGUI_POPUP_ITEM_PROPERTIES_TYPE IMGUI_POPUP_CENTER_WINDOW +const ImVec2 IMGUI_POPUP_ITEM_PROPERTIES_SIZE = {300, 350}; + +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, + self.label = "## Item Properties Type Child", + self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, + self.label = "Layer", + self.tooltip = "The item will be a layer item.\nA layer item is a primary graphical item, using a spritesheet.", + self.isSizeToText = true, + self.value = ANM2_LAYER, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, + self.label = "Null", + self.tooltip = "The item will be 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_ITEM_PROPERTIES_ITEMS_CHILD, + self.label = "## Item Properties Items", + self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 250}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, + self.label = "## Item Properties Options Child", + self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM, + self.label = "Confirm", + self.tooltip = "Set the timeline item's properties.", + self.snapshotAction = "Timeline Item Change", + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, + self.isSameLine = true +); + IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SELECTABLE, self.label = "## Selectable", self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE @@ -2101,6 +2148,8 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, self.label = "## Layer Selectable", self.tooltip = "A layer item.\nA graphical item within the animation.", self.dragDrop = "## Layer Drag Drop", + self.popup = IMGUI_POPUP_ITEM_PROPERTIES, + self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, self.atlas = ATLAS_LAYER, self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE ); @@ -2109,6 +2158,8 @@ IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, self.label = "## Null Selectable", self.tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.", self.dragDrop = "## Null Drag Drop", + self.popup = IMGUI_POPUP_ITEM_PROPERTIES, + self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, self.atlas = ATLAS_NULL, self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE ); @@ -2188,6 +2239,7 @@ IMGUI_ITEM(IMGUI_TIMELINE_FRAME, self.label = "## Frame"); static const vec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.25f}; IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME, self.label = "## Root Frame", + self.snapshotAction = "Root Frame", self.color = {{0.14f, 0.27f, 0.39f, 1.0f}, {0.28f, 0.54f, 0.78f, 1.0f}, {0.36f, 0.70f, 0.95f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, @@ -2197,6 +2249,7 @@ IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME, IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME, self.label = "## Layer Frame", self.dragDrop = "## Layer Frame Drag Drop", + self.snapshotAction = "Layer Frame", self.color = {{0.45f, 0.18f, 0.07f, 1.0f}, {0.78f, 0.32f, 0.12f, 1.0f}, {0.95f, 0.40f, 0.15f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, @@ -2206,6 +2259,7 @@ IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME, IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME, self.label = "## Null Frame", self.dragDrop = "## Null Frame Drag Drop", + self.snapshotAction = "Null Frame", self.color = {{0.17f, 0.33f, 0.17f, 1.0f}, {0.34f, 0.68f, 0.34f, 1.0f}, {0.44f, 0.88f, 0.44f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, @@ -2214,6 +2268,7 @@ IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME, IMGUI_ITEM(IMGUI_TIMELINE_TRIGGERS_FRAME, self.label = "## Triggers Frame", + self.snapshotAction = "Trigger", self.color = {{0.36f, 0.14f, 0.24f, 1.0f}, {0.72f, 0.28f, 0.48f, 1.0f}, {0.92f, 0.36f, 0.60f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, @@ -2249,52 +2304,13 @@ IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM, self.label = "Add", 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.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, 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.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 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, self.label = "Remove", diff --git a/src/preview.cpp b/src/preview.cpp index 48a8c00..6ef404e 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -37,6 +37,7 @@ void preview_tick(Preview* self) glBindFramebuffer(GL_READ_FRAMEBUFFER, self->canvas.fbo); glReadBuffer(GL_COLOR_ATTACHMENT0); glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data()); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); diff --git a/src/settings.h b/src/settings.h index d72be3a..d56edd9 100644 --- a/src/settings.h +++ b/src/settings.h @@ -123,7 +123,7 @@ X(editorGridColor, EDITOR_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ X(editorBackgroundColor, EDITOR_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \ \ - X(mergeType, MERGE_TYPE, TYPE_INT, ANM2_MERGE_APPEND_FRAMES) \ + X(mergeType, MERGE_TYPE, TYPE_INT, ANM2_MERGE_APPEND) \ X(mergeIsDeleteAnimationsAfter,MERGE_IS_DELETE_ANIMATIONS_AFTER,TYPE_BOOL, false) \ \ X(bakeInterval, BAKE_INTERVAL, TYPE_INT, 1) \ diff --git a/workshop/metadata.xml b/workshop/metadata.xml index 44801ef..489c749 100644 --- a/workshop/metadata.xml +++ b/workshop/metadata.xml @@ -43,6 +43,6 @@ Alternatively, if you have subscribed to the mod, you can find the latest releas [h3]Happy animating![/h3] [img]https://files.catbox.moe/4auc1c.gif[/img] - 1.2 + 1.3 Public