Frame movement on timeline changed

This commit is contained in:
2025-09-15 19:27:07 -04:00
parent f49eaa6a37
commit 9fb6366d7c
8 changed files with 401 additions and 139 deletions

View File

@@ -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[] = {

View File

@@ -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<s32>& 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;
}
}
/*
void anm2_merge(Anm2* self, const std::string& path, Anm2MergeType type)
{
Anm2 anm2;
if (anm2_deserialize(&anm2, path, false))
{
std::unordered_map<s32, s32> 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<s32, s32> 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<s32, s32> 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<s32, s32> 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];
}
}
}
}
*/

View File

@@ -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
};

View File

@@ -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);
}

View File

@@ -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",

View File

@@ -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);

View File

@@ -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) \

View File

@@ -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]
</description>
<version>1.2</version>
<version>1.3</version>
<visibility>Public</visibility>
</metadata>