fixed frame drag/drop
This commit is contained in:
@@ -8,3 +8,4 @@ workshop/resources
|
|||||||
cmake-build-debug/
|
cmake-build-debug/
|
||||||
.vs/
|
.vs/
|
||||||
.idea/
|
.idea/
|
||||||
|
.codex
|
||||||
@@ -1 +1 @@
|
|||||||
/home/anon/sda/Personal/Repos/anm2ed/out/build/linux-debug/compile_commands.json
|
/home/anon/sda/Personal/Repos/anm2ed/out/build/linux-release/compile_commands.json
|
||||||
+191
-98
@@ -81,7 +81,6 @@ namespace anm2ed::imgui
|
|||||||
{0.6353f, 0.2235f, 0.3647f, 1.0f}};
|
{0.6353f, 0.2235f, 0.3647f, 1.0f}};
|
||||||
|
|
||||||
constexpr auto FRAME_MULTIPLE = 5;
|
constexpr auto FRAME_MULTIPLE = 5;
|
||||||
constexpr auto FRAME_DRAG_PAYLOAD_ID = "Frame Drag Drop";
|
|
||||||
constexpr auto FRAME_TOOLTIP_HOVER_DELAY = 0.75f; // Extra delay for frame info tooltip.
|
constexpr auto FRAME_TOOLTIP_HOVER_DELAY = 0.75f; // Extra delay for frame info tooltip.
|
||||||
|
|
||||||
#define ITEM_CHILD_WIDTH ImGui::GetTextLineHeightWithSpacing() * 12.5
|
#define ITEM_CHILD_WIDTH ImGui::GetTextLineHeightWithSpacing() * 12.5
|
||||||
@@ -1100,6 +1099,96 @@ namespace anm2ed::imgui
|
|||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
anm2::Type frameMoveDropType = anm2::NONE;
|
||||||
|
int frameMoveDropItemID = -1;
|
||||||
|
int frameMoveDropIndex = -1;
|
||||||
|
bool isFrameMoveDropTarget = false;
|
||||||
|
|
||||||
|
auto frame_move_drag_clear = [&]()
|
||||||
|
{
|
||||||
|
frameMoveDrag = {};
|
||||||
|
frameSelectionLocked.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto time_from_index = [](anm2::Item* target, int index)
|
||||||
|
{
|
||||||
|
if (!target || target->frames.empty()) return 0.0f;
|
||||||
|
index = std::clamp(index, 0, (int)target->frames.size());
|
||||||
|
float timeAccum = 0.0f;
|
||||||
|
for (int n = 0; n < index && n < (int)target->frames.size(); ++n)
|
||||||
|
timeAccum += target->frames[n].duration;
|
||||||
|
return timeAccum;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto frames_move_to = [&](anm2::Type targetType, int targetID, int insertIndex)
|
||||||
|
{
|
||||||
|
if (!frameMoveDrag.isActive || !animation || frameMoveDrag.animationIndex != reference.animationIndex) return;
|
||||||
|
if (frameMoveDrag.type == anm2::TRIGGER || targetType == anm2::TRIGGER) return;
|
||||||
|
|
||||||
|
auto sourceItem = animation->item_get(frameMoveDrag.type, frameMoveDrag.itemID);
|
||||||
|
auto targetItem = animation->item_get(targetType, targetID);
|
||||||
|
if (!sourceItem || !targetItem) return;
|
||||||
|
|
||||||
|
std::vector<int> indices = frameMoveDrag.indices;
|
||||||
|
if (indices.empty() && frameMoveDrag.frameIndex >= 0) indices.push_back(frameMoveDrag.frameIndex);
|
||||||
|
std::sort(indices.begin(), indices.end());
|
||||||
|
indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
|
||||||
|
indices.erase(std::remove_if(indices.begin(), indices.end(),
|
||||||
|
[&](int i) { return i < 0 || i >= (int)sourceItem->frames.size(); }),
|
||||||
|
indices.end());
|
||||||
|
if (indices.empty()) return;
|
||||||
|
|
||||||
|
int insertPosResult = -1;
|
||||||
|
int insertedCount = 0;
|
||||||
|
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_FRAMES), Document::FRAMES, {
|
||||||
|
std::vector<anm2::Frame> movedFrames;
|
||||||
|
movedFrames.reserve(indices.size());
|
||||||
|
|
||||||
|
for (int i : indices)
|
||||||
|
movedFrames.push_back(std::move(sourceItem->frames[i]));
|
||||||
|
|
||||||
|
for (auto it = indices.rbegin(); it != indices.rend(); ++it)
|
||||||
|
sourceItem->frames.erase(sourceItem->frames.begin() + *it);
|
||||||
|
|
||||||
|
int desired = std::clamp(insertIndex, 0, (int)targetItem->frames.size());
|
||||||
|
if (sourceItem == targetItem)
|
||||||
|
{
|
||||||
|
int removedBefore = 0;
|
||||||
|
for (int i : indices)
|
||||||
|
if (i < desired) ++removedBefore;
|
||||||
|
desired -= removedBefore;
|
||||||
|
}
|
||||||
|
desired = std::clamp(desired, 0, (int)targetItem->frames.size());
|
||||||
|
|
||||||
|
insertPosResult = desired;
|
||||||
|
insertedCount = (int)movedFrames.size();
|
||||||
|
targetItem->frames.insert(targetItem->frames.begin() + insertPosResult,
|
||||||
|
std::make_move_iterator(movedFrames.begin()),
|
||||||
|
std::make_move_iterator(movedFrames.end()));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (insertedCount > 0)
|
||||||
|
{
|
||||||
|
frames.selection.clear();
|
||||||
|
for (int offset = 0; offset < insertedCount; ++offset)
|
||||||
|
frames.selection.insert(insertPosResult + offset);
|
||||||
|
|
||||||
|
reference = {reference.animationIndex, targetType, targetID, insertPosResult};
|
||||||
|
document.frameTime = time_from_index(targetItem, reference.frameIndex);
|
||||||
|
frameSelectionSnapshot.assign(frames.selection.begin(), frames.selection.end());
|
||||||
|
frameSelectionSnapshotReference = reference;
|
||||||
|
frameSelectionLocked.clear();
|
||||||
|
isFrameSelectionLocked = false;
|
||||||
|
frameFocusIndex = reference.frameIndex;
|
||||||
|
frameFocusRequested = true;
|
||||||
|
if (targetType == anm2::LAYER)
|
||||||
|
{
|
||||||
|
if (auto it = anm2.content.layers.find(targetID); it != anm2.content.layers.end())
|
||||||
|
document.spritesheet.reference = it->second.spritesheetID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
auto frame_child = [&](anm2::Type type, int id, int& index, float width)
|
auto frame_child = [&](anm2::Type type, int id, int& index, float width)
|
||||||
{
|
{
|
||||||
auto item = animation ? animation->item_get(type, id) : nullptr;
|
auto item = animation ? animation->item_get(type, id) : nullptr;
|
||||||
@@ -1237,7 +1326,7 @@ namespace anm2ed::imgui
|
|||||||
{
|
{
|
||||||
float frameTime{};
|
float frameTime{};
|
||||||
|
|
||||||
if (ImGui::IsWindowHovered() &&
|
if (!frameMoveDrag.isActive && ImGui::IsWindowHovered() &&
|
||||||
(ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Right)) &&
|
(ImGui::IsMouseReleased(ImGuiMouseButton_Left) || ImGui::IsMouseReleased(ImGuiMouseButton_Right)) &&
|
||||||
!ImGui::IsAnyItemHovered())
|
!ImGui::IsAnyItemHovered())
|
||||||
reference_set_item(type, id);
|
reference_set_item(type, id);
|
||||||
@@ -1253,7 +1342,65 @@ namespace anm2ed::imgui
|
|||||||
drawList->AddRectFilled(frameScreenPos, frameRectMax, ImGui::GetColorU32(frameMultipleOverlayColor));
|
drawList->AddRectFilled(frameScreenPos, frameRectMax, ImGui::GetColorU32(frameMultipleOverlayColor));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != anm2::TRIGGER) frames.selection.start(item->frames.size(), ImGuiMultiSelectFlags_ClearOnEscape);
|
bool isFrameSelectionStarted = type != anm2::TRIGGER && !frameMoveDrag.isActive;
|
||||||
|
if (isFrameSelectionStarted) frames.selection.start(item->frames.size(), ImGuiMultiSelectFlags_ClearOnEscape);
|
||||||
|
|
||||||
|
bool isFrameMovePreview = false;
|
||||||
|
ImVec2 frameMovePreviewMin{};
|
||||||
|
ImVec2 frameMovePreviewMax{};
|
||||||
|
bool isFrameMoveHoveredFrame = false;
|
||||||
|
ImVec2 frameMoveHoveredFrameMin{};
|
||||||
|
ImVec2 frameMoveHoveredFrameMax{};
|
||||||
|
|
||||||
|
if (frameMoveDrag.isActive && type != anm2::TRIGGER)
|
||||||
|
{
|
||||||
|
auto mousePos = ImGui::GetIO().MousePos;
|
||||||
|
auto rowMin = cursorScreenPos;
|
||||||
|
auto rowMax = ImVec2(cursorScreenPos.x + width, cursorScreenPos.y + frameSize.y);
|
||||||
|
if (mousePos.x >= rowMin.x && mousePos.x < rowMax.x && mousePos.y >= rowMin.y && mousePos.y < rowMax.y)
|
||||||
|
{
|
||||||
|
auto mouseX = mousePos.x - cursorScreenPos.x;
|
||||||
|
auto targetTime = glm::max(0.0f, mouseX / frameSize.x);
|
||||||
|
int dropIndex = (int)item->frames.size();
|
||||||
|
float dropFrameTime{};
|
||||||
|
float frameTime{};
|
||||||
|
|
||||||
|
for (auto [i, frame] : std::views::enumerate(item->frames))
|
||||||
|
{
|
||||||
|
auto frameStart = frameTime;
|
||||||
|
auto frameEnd = frameStart + frame.duration;
|
||||||
|
if (!isFrameMoveHoveredFrame && targetTime >= frameStart && targetTime < frameEnd)
|
||||||
|
{
|
||||||
|
isFrameMoveHoveredFrame = true;
|
||||||
|
frameMoveHoveredFrameMin = ImVec2(cursorScreenPos.x + frameStart * frameSize.x, cursorScreenPos.y);
|
||||||
|
frameMoveHoveredFrameMax = ImVec2(cursorScreenPos.x + frameEnd * frameSize.x,
|
||||||
|
cursorScreenPos.y + frameSize.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto midpoint = frameStart + ((float)frame.duration * 0.5f);
|
||||||
|
if (targetTime < midpoint)
|
||||||
|
{
|
||||||
|
dropIndex = (int)i;
|
||||||
|
dropFrameTime = frameStart;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameTime = frameEnd;
|
||||||
|
dropFrameTime = frameTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
frameMoveDropType = type;
|
||||||
|
frameMoveDropItemID = id;
|
||||||
|
frameMoveDropIndex = dropIndex;
|
||||||
|
isFrameMoveDropTarget = true;
|
||||||
|
|
||||||
|
auto dropX = cursorScreenPos.x + dropFrameTime * frameSize.x;
|
||||||
|
auto previewWidth = glm::max(frameSize.x, (float)frameMoveDrag.duration * frameSize.x);
|
||||||
|
frameMovePreviewMin = ImVec2(dropX, cursorScreenPos.y);
|
||||||
|
frameMovePreviewMax = ImVec2(dropX + previewWidth, cursorScreenPos.y + frameSize.y);
|
||||||
|
isFrameMovePreview = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (auto [i, frame] : std::views::enumerate(item->frames))
|
for (auto [i, frame] : std::views::enumerate(item->frames))
|
||||||
{
|
{
|
||||||
@@ -1344,12 +1491,9 @@ namespace anm2ed::imgui
|
|||||||
|
|
||||||
if (type != anm2::TRIGGER)
|
if (type != anm2::TRIGGER)
|
||||||
{
|
{
|
||||||
if (!draggedFrame && ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
if (!draggedFrame && !frameMoveDrag.isActive && ImGui::IsItemActive() &&
|
||||||
|
ImGui::IsMouseDragging(ImGuiMouseButton_Left))
|
||||||
{
|
{
|
||||||
frameDragDrop = {};
|
|
||||||
frameDragDrop.type = type;
|
|
||||||
frameDragDrop.itemID = id;
|
|
||||||
frameDragDrop.animationIndex = reference.animationIndex;
|
|
||||||
frameSelectionLocked.clear();
|
frameSelectionLocked.clear();
|
||||||
|
|
||||||
auto append_valid_indices = [&](const auto& container)
|
auto append_valid_indices = [&](const auto& container)
|
||||||
@@ -1358,7 +1502,13 @@ namespace anm2ed::imgui
|
|||||||
if (idx >= 0 && idx < (int)item->frames.size()) frameSelectionLocked.push_back(idx);
|
if (idx >= 0 && idx < (int)item->frames.size()) frameSelectionLocked.push_back(idx);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isReferenced) append_valid_indices(frames.selection);
|
if (frameSelectionSnapshotReference.animationIndex == reference.animationIndex &&
|
||||||
|
frameSelectionSnapshotReference.itemType == type && frameSelectionSnapshotReference.itemID == id &&
|
||||||
|
std::find(frameSelectionSnapshot.begin(), frameSelectionSnapshot.end(), (int)i) !=
|
||||||
|
frameSelectionSnapshot.end())
|
||||||
|
append_valid_indices(frameSelectionSnapshot);
|
||||||
|
else if (isReferenced)
|
||||||
|
append_valid_indices(frames.selection);
|
||||||
|
|
||||||
auto contains_index = [&](const std::vector<int>& container, int index)
|
auto contains_index = [&](const std::vector<int>& container, int index)
|
||||||
{ return std::find(container.begin(), container.end(), index) != container.end(); };
|
{ return std::find(container.begin(), container.end(), index) != container.end(); };
|
||||||
@@ -1381,95 +1531,20 @@ namespace anm2ed::imgui
|
|||||||
frameSelectionLocked.erase(std::unique(frameSelectionLocked.begin(), frameSelectionLocked.end()),
|
frameSelectionLocked.erase(std::unique(frameSelectionLocked.begin(), frameSelectionLocked.end()),
|
||||||
frameSelectionLocked.end());
|
frameSelectionLocked.end());
|
||||||
|
|
||||||
frameDragDropPayload = {type, id, reference.animationIndex};
|
int dragDuration = 0;
|
||||||
ImGui::SetDragDropPayload(FRAME_DRAG_PAYLOAD_ID, &frameDragDropPayload, sizeof(frameDragDropPayload));
|
for (int idx : frameSelectionLocked)
|
||||||
ImGui::EndDragDropSource();
|
if (idx >= 0 && idx < (int)item->frames.size()) dragDuration += item->frames[idx].duration;
|
||||||
}
|
dragDuration = glm::max(1, dragDuration);
|
||||||
|
|
||||||
if (!draggedFrame && ImGui::BeginDragDropTarget())
|
frameMoveDrag = {
|
||||||
{
|
.type = type,
|
||||||
if (auto payload = ImGui::AcceptDragDropPayload(FRAME_DRAG_PAYLOAD_ID))
|
.itemID = id,
|
||||||
{
|
.animationIndex = reference.animationIndex,
|
||||||
auto source = static_cast<const FrameDragDropPayload*>(payload->Data);
|
.frameIndex = (int)i,
|
||||||
auto sameAnimation = source && source->animationIndex == reference.animationIndex;
|
.duration = dragDuration,
|
||||||
auto sourceItem =
|
.indices = frameSelectionLocked,
|
||||||
sameAnimation && animation ? animation->item_get(source->type, source->itemID) : nullptr;
|
.isActive = true,
|
||||||
auto targetItem = animation ? animation->item_get(type, id) : nullptr;
|
};
|
||||||
|
|
||||||
auto time_from_index = [&](anm2::Item* target, int index)
|
|
||||||
{
|
|
||||||
if (!target || target->frames.empty()) return 0.0f;
|
|
||||||
index = std::clamp(index, 0, (int)target->frames.size());
|
|
||||||
float timeAccum = 0.0f;
|
|
||||||
for (int n = 0; n < index && n < (int)target->frames.size(); ++n)
|
|
||||||
timeAccum += target->frames[n].duration;
|
|
||||||
return timeAccum;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (source && sourceItem && targetItem && source->type != anm2::TRIGGER && type != anm2::TRIGGER)
|
|
||||||
{
|
|
||||||
std::vector<int> indices = frameSelectionLocked;
|
|
||||||
if (indices.empty()) indices.push_back((int)i);
|
|
||||||
std::sort(indices.begin(), indices.end());
|
|
||||||
indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
|
|
||||||
|
|
||||||
int insertPosResult = -1;
|
|
||||||
int insertedCount = 0;
|
|
||||||
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_FRAMES), Document::FRAMES, {
|
|
||||||
std::vector<anm2::Frame> movedFrames;
|
|
||||||
movedFrames.reserve(indices.size());
|
|
||||||
|
|
||||||
for (int i : indices)
|
|
||||||
if (i >= 0 && i < (int)sourceItem->frames.size())
|
|
||||||
movedFrames.push_back(std::move(sourceItem->frames[i]));
|
|
||||||
|
|
||||||
for (auto it = indices.rbegin(); it != indices.rend(); ++it)
|
|
||||||
if (*it >= 0 && *it < (int)sourceItem->frames.size())
|
|
||||||
sourceItem->frames.erase(sourceItem->frames.begin() + *it);
|
|
||||||
|
|
||||||
const int dropIndex = (int)i;
|
|
||||||
int desired = std::clamp(dropIndex + 1, 0, (int)targetItem->frames.size());
|
|
||||||
if (sourceItem == targetItem)
|
|
||||||
{
|
|
||||||
if (dropIndex < indices.front())
|
|
||||||
desired = dropIndex;
|
|
||||||
else if (dropIndex > indices.back())
|
|
||||||
desired = dropIndex + 1;
|
|
||||||
else
|
|
||||||
desired = indices.front();
|
|
||||||
|
|
||||||
int removedBefore = 0;
|
|
||||||
for (int i : indices)
|
|
||||||
if (i < desired) ++removedBefore;
|
|
||||||
desired -= removedBefore;
|
|
||||||
}
|
|
||||||
desired = std::clamp(desired, 0, (int)targetItem->frames.size());
|
|
||||||
|
|
||||||
insertPosResult = desired;
|
|
||||||
insertedCount = (int)movedFrames.size();
|
|
||||||
targetItem->frames.insert(targetItem->frames.begin() + insertPosResult,
|
|
||||||
std::make_move_iterator(movedFrames.begin()),
|
|
||||||
std::make_move_iterator(movedFrames.end()));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (insertedCount > 0)
|
|
||||||
{
|
|
||||||
frames.selection.clear();
|
|
||||||
for (int offset = 0; offset < insertedCount; ++offset)
|
|
||||||
frames.selection.insert(insertPosResult + offset);
|
|
||||||
|
|
||||||
reference = {reference.animationIndex, type, id, insertPosResult};
|
|
||||||
document.frameTime = time_from_index(targetItem, reference.frameIndex);
|
|
||||||
if (type == anm2::LAYER)
|
|
||||||
{
|
|
||||||
if (auto it = anm2.content.layers.find(id); it != anm2.content.layers.end())
|
|
||||||
document.spritesheet.reference = it->second.spritesheetID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndDragDropTarget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1517,7 +1592,20 @@ namespace anm2ed::imgui
|
|||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != anm2::TRIGGER) frames.selection.finish();
|
if (isFrameMovePreview)
|
||||||
|
{
|
||||||
|
drawList->AddRectFilled(frameMovePreviewMin, frameMovePreviewMax,
|
||||||
|
ImGui::GetColorU32(ImGuiCol_DragDropTargetBg), FRAME_ROUNDING);
|
||||||
|
drawList->AddRect(frameMovePreviewMin, frameMovePreviewMax, ImGui::GetColorU32(ImGuiCol_DragDropTarget),
|
||||||
|
FRAME_ROUNDING, 0, ImGui::GetStyle().DragDropTargetBorderSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFrameMoveHoveredFrame)
|
||||||
|
drawList->AddRect(frameMoveHoveredFrameMin, frameMoveHoveredFrameMax,
|
||||||
|
ImGui::GetColorU32(ImGuiCol_DragDropTarget), FRAME_ROUNDING, 0,
|
||||||
|
ImGui::GetStyle().DragDropTargetBorderSize * 1.5f);
|
||||||
|
|
||||||
|
if (isFrameSelectionStarted) frames.selection.finish();
|
||||||
|
|
||||||
if (isFrameSelectionLocked)
|
if (isFrameSelectionLocked)
|
||||||
{
|
{
|
||||||
@@ -1813,6 +1901,11 @@ namespace anm2ed::imgui
|
|||||||
isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows |
|
isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows |
|
||||||
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||||
frames_child();
|
frames_child();
|
||||||
|
if (frameMoveDrag.isActive && ImGui::IsMouseReleased(ImGuiMouseButton_Left))
|
||||||
|
{
|
||||||
|
if (isFrameMoveDropTarget) frames_move_to(frameMoveDropType, frameMoveDropItemID, frameMoveDropIndex);
|
||||||
|
frame_move_drag_clear();
|
||||||
|
}
|
||||||
items_child();
|
items_child();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
|||||||
@@ -10,18 +10,15 @@
|
|||||||
|
|
||||||
namespace anm2ed::imgui
|
namespace anm2ed::imgui
|
||||||
{
|
{
|
||||||
struct FrameDragDrop
|
struct FrameMoveDrag
|
||||||
{
|
|
||||||
anm2::Type type{anm2::NONE};
|
|
||||||
int itemID{-1};
|
|
||||||
int animationIndex{-1};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FrameDragDropPayload
|
|
||||||
{
|
{
|
||||||
anm2::Type type{anm2::NONE};
|
anm2::Type type{anm2::NONE};
|
||||||
int itemID{-1};
|
int itemID{-1};
|
||||||
int animationIndex{-1};
|
int animationIndex{-1};
|
||||||
|
int frameIndex{-1};
|
||||||
|
int duration{1};
|
||||||
|
std::vector<int> indices{};
|
||||||
|
bool isActive{};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Timeline
|
class Timeline
|
||||||
@@ -44,8 +41,7 @@ namespace anm2ed::imgui
|
|||||||
bool isDraggedFrameSnapshot{};
|
bool isDraggedFrameSnapshot{};
|
||||||
bool frameFocusRequested{};
|
bool frameFocusRequested{};
|
||||||
int frameFocusIndex{-1};
|
int frameFocusIndex{-1};
|
||||||
FrameDragDrop frameDragDrop{};
|
FrameMoveDrag frameMoveDrag{};
|
||||||
FrameDragDropPayload frameDragDropPayload{};
|
|
||||||
std::vector<int> frameSelectionSnapshot{};
|
std::vector<int> frameSelectionSnapshot{};
|
||||||
std::vector<int> frameSelectionLocked{};
|
std::vector<int> frameSelectionLocked{};
|
||||||
bool isFrameSelectionLocked{};
|
bool isFrameSelectionLocked{};
|
||||||
|
|||||||
@@ -53,6 +53,6 @@ Alternatively, if you have subscribed to the mod, you can find the latest releas
|
|||||||
[h3]Happy animating![/h3]
|
[h3]Happy animating![/h3]
|
||||||
[img]https://files.catbox.moe/4auc1c.gif[/img]
|
[img]https://files.catbox.moe/4auc1c.gif[/img]
|
||||||
</description>
|
</description>
|
||||||
<version>2.17</version>
|
<version>2.18</version>
|
||||||
<visibility>Public</visibility>
|
<visibility>Public</visibility>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|||||||
Reference in New Issue
Block a user