Holy vibecode Batman!

This commit is contained in:
2026-05-21 21:00:57 -04:00
parent 7db835e6d4
commit 1bc5199e3b
140 changed files with 11888 additions and 9481 deletions
+232 -182
View File
@@ -7,15 +7,15 @@
#include <format>
#include <map>
#include <optional>
#include <ranges>
#include <system_error>
#include <glm/gtc/type_ptr.hpp>
#include "imgui_.hpp"
#include "actions.hpp"
#include "util/imgui/imgui.hpp"
#include "log.hpp"
#include "math_.hpp"
#include "path_.hpp"
#include "math.hpp"
#include "path.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "tool.hpp"
@@ -103,7 +103,7 @@ namespace anm2ed::imgui
pixels[index + 2] = (uint8_t)glm::clamp((float)std::round((float)pixels[index + 2] / alphaUnit), 0.0f, 255.0f);
}
}
bool render_audio_stream_generate(AudioStream& audioStream, std::map<int, anm2::Sound>& sounds,
bool render_audio_stream_generate(AudioStream& audioStream, std::map<int, Audio>& sounds,
const std::vector<int>& frameSoundIDs, int fps)
{
audioStream.stream.clear();
@@ -122,7 +122,7 @@ namespace anm2ed::imgui
for (auto soundID : frameSoundIDs)
{
if (soundID != -1 && sounds.contains(soundID)) sounds.at(soundID).audio.play(false, mixer);
if (soundID != -1 && sounds.contains(soundID)) sounds.at(soundID).play(false, mixer);
sampleFrameAccumulator += framesPerStep;
auto sampleFramesToGenerate = (int)std::floor(sampleFrameAccumulator);
@@ -133,7 +133,7 @@ namespace anm2ed::imgui
if (!MIX_Generate(mixer, frameBuffer.data(), (int)(frameBuffer.size() * sizeof(float))))
{
for (auto& [_, sound] : sounds)
sound.audio.track_detach(mixer);
sound.track_detach(mixer);
MIX_DestroyMixer(mixer);
audioStream.stream.clear();
return false;
@@ -143,7 +143,7 @@ namespace anm2ed::imgui
}
for (auto& [_, sound] : sounds)
sound.audio.track_detach(mixer);
sound.track_detach(mixer);
MIX_DestroyMixer(mixer);
return true;
}
@@ -151,6 +151,8 @@ namespace anm2ed::imgui
AnimationPreview::AnimationPreview() : Canvas(vec2()) {}
bool AnimationPreview::is_focused_get() const { return isFocused; }
void AnimationPreview::tick(Manager& manager, Settings& settings, float deltaSeconds)
{
auto& document = *manager.get();
@@ -164,8 +166,24 @@ namespace anm2ed::imgui
auto stop_all_sounds = [&]()
{
for (auto& sound : anm2.content.sounds | std::views::values)
sound.audio.stop(mixer);
for (auto& [_, sound] : document.sounds)
sound.stop(mixer);
};
auto trigger_sound_id_get = [&](Element* animation, float time)
{
if (!animation) return -1;
auto triggers = animation_item_get(*animation, ItemType::TRIGGER);
if (!triggers || !triggers->isVisible) return -1;
auto trigger = frame_generate(*triggers, time);
if (!trigger.isVisible || trigger.soundIds.empty()) return -1;
auto soundIndex =
trigger.soundIds.size() > 1 ? (size_t)math::random_in_range(0.0f, (float)trigger.soundIds.size()) : (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIds.size() - 1);
auto soundID = trigger.soundIds[soundIndex];
return document.sound_get(soundID) ? soundID : -1;
};
if (manager.isRecording)
@@ -261,7 +279,7 @@ namespace anm2ed::imgui
{
if (settings.timelineIsSound && type != render::GIF)
{
if (!render_audio_stream_generate(audioStream, anm2.content.sounds, renderFrameSoundIDs, renderFrameRate))
if (!render_audio_stream_generate(audioStream, document.sounds, renderFrameSoundIDs, renderFrameRate))
{
toasts.push(localize.get(TOAST_EXPORT_RENDERED_ANIMATION_FAILED));
logger.error("Failed to generate deterministic render audio stream; exporting without audio.");
@@ -322,26 +340,13 @@ namespace anm2ed::imgui
{
if (settings.timelineIsSound && renderTempFrames.empty()) audioStream.capture_begin(mixer);
auto frameSoundID = -1;
if (settings.timelineIsSound && !anm2.content.sounds.empty())
if (settings.timelineIsSound && !document.sounds.empty())
{
auto soundTime = (int)std::floor(playback.time);
if (auto animation = document.animation_get(); soundTime != renderFrameSoundTimePrev && animation &&
animation->triggers.isVisible &&
(!settings.timelineIsOnlyShowLayers || manager.isRecording))
{
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
{
if (!trigger.soundIDs.empty())
{
auto soundIndex = trigger.soundIDs.size() > 1
? (size_t)math::random_in_range(0.0f, (float)trigger.soundIDs.size())
: (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIDs.size() - 1);
auto soundID = trigger.soundIDs[soundIndex];
if (anm2.content.sounds.contains(soundID)) frameSoundID = soundID;
}
}
}
auto animation = anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex);
if (soundTime != renderFrameSoundTimePrev && animation &&
(!settings.timelineIsOnlyShowLayers || manager.isRecording))
frameSoundID = trigger_sound_id_get(animation, playback.time);
renderFrameSoundTimePrev = soundTime;
}
renderFrameSoundIDs.push_back(frameSoundID);
@@ -376,7 +381,7 @@ namespace anm2ed::imgui
if (playback.isPlaying)
{
auto animation = document.animation_get();
auto animation = anm2.element_get(ElementType::ANIMATION, document.reference.animationIndex);
auto& isSound = settings.timelineIsSound;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
@@ -388,27 +393,15 @@ namespace anm2ed::imgui
}
else
{
if (!manager.isRecording && !anm2.content.sounds.empty() && isSound)
if (!manager.isRecording && !document.sounds.empty() && isSound)
{
if (animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording))
{
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
{
if (!trigger.soundIDs.empty())
{
auto soundIndex = trigger.soundIDs.size() > 1
? (size_t)math::random_in_range(0.0f, (float)trigger.soundIDs.size())
: (size_t)0;
soundIndex = std::min(soundIndex, trigger.soundIDs.size() - 1);
auto soundID = trigger.soundIDs[soundIndex];
if (anm2.content.sounds.contains(soundID)) anm2.content.sounds[soundID].audio.play(false, mixer);
}
}
}
if (!isOnlyShowLayers || manager.isRecording)
if (auto soundID = trigger_sound_id_get(animation, playback.time); soundID != -1)
if (auto sound = document.sound_get(soundID)) sound->play(false, mixer);
}
auto fps = std::max(anm2.info.fps, 1);
auto info = element_first_get(anm2.root, ElementType::INFO);
auto fps = std::max(info ? info->fps : 30, 1);
playback.tick(fps, animation->frameNum, (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording,
deltaSeconds);
@@ -422,11 +415,13 @@ namespace anm2ed::imgui
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
{
isFocused = false;
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference;
auto animation = document.animation_get();
auto animation = anm2.element_get(ElementType::ANIMATION, reference.animationIndex);
auto& pan = document.previewPan;
auto& zoom = document.previewZoom;
auto& backgroundColor = settings.previewBackgroundColor;
@@ -502,6 +497,7 @@ namespace anm2ed::imgui
if (ImGui::Begin(localize.get(LABEL_ANIMATION_PREVIEW_WINDOW), &settings.windowIsAnimationPreview))
{
isFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
manager.isAbleToRecord = true;
auto childSize = ImVec2(row_widget_width_get(4),
@@ -636,11 +632,12 @@ namespace anm2ed::imgui
savedZoom = zoom;
savedPan = pan;
if (auto rect = anm2.animation_rect(*document.animation_get(), isRootTransform); rect != vec4(-1.0f))
{
size_set(vec2(rect.z, rect.w) * settings.renderScale);
set_to_rect(zoom, pan, rect);
}
if (animation)
if (auto rect = anm2.animation_rect(*animation, isRootTransform); rect != vec4(-1.0f))
{
size_set(vec2(rect.z, rect.w) * settings.renderScale);
set_to_rect(zoom, pan, rect);
}
isSizeTrySet = false;
}
@@ -738,43 +735,56 @@ namespace anm2ed::imgui
add_samples(settings.onionskinAfterCount, 1, settings.onionskinAfterColor);
}
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
auto referenceItemType = static_cast<ItemType>(reference.itemType);
auto render = [&](Element* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
const std::vector<OnionskinSample>* layeredOnions = nullptr, bool isIndexMode = false)
{
auto sample_time_for_item = [&](anm2::Item& item, const OnionskinSample& sample) -> std::optional<float>
auto sample_time_for_item = [&](Element& item, const OnionskinSample& sample) -> std::optional<float>
{
if (!isIndexMode)
{
if (sample.time < 0.0f || sample.time > animation->frameNum) return std::nullopt;
return sample.time;
}
if (item.frames.empty()) return std::nullopt;
int baseIndex = item.frame_index_from_time_get(frameTime);
if (item.children.empty()) return std::nullopt;
int baseIndex = frame_index_from_time_get(item, frameTime);
if (baseIndex < 0) return std::nullopt;
int sampleIndex = baseIndex + sample.indexOffset;
if (sampleIndex < 0 || sampleIndex >= (int)item.frames.size()) return std::nullopt;
return item.frame_time_from_index_get(sampleIndex);
if (!track_frame_get(item, sampleIndex)) return std::nullopt;
return frame_time_from_index_get(item, sampleIndex);
};
auto transform_for_time = [&](anm2::Animation* anim, float t)
auto root = animation_item_get(*animation, ItemType::ROOT);
auto transform_for_time = [&](float t)
{
auto sampleTransform = baseTransform;
if (isRootTransform)
if (isRootTransform && root)
{
auto rootFrame = anim->rootAnimation.frame_generate(t, anm2::ROOT);
auto rootFrame = frame_generate(*root, t);
sampleTransform *= math::quad_model_parent_get(rootFrame.position, {},
math::percent_to_unit(rootFrame.scale), rootFrame.rotation);
}
return sampleTransform;
};
auto transform = transform_for_time(animation, time);
auto transform = transform_for_time(time);
auto is_track_group_visible = [](const Element& container, const Element& track)
{
if (track.groupId == -1) return true;
for (const auto& child : container.children)
if (child.type == ElementType::GROUP && child.id == track.groupId) return child.isVisible;
return true;
};
auto draw_root =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
{
auto rootFrame = animation->rootAnimation.frame_generate(sampleTime, anm2::ROOT);
if (isOnlyShowLayers || !rootFrame.isVisible || !animation->rootAnimation.isVisible) return;
if (!root) return;
auto rootFrame = frame_generate(*root, sampleTime);
if (isOnlyShowLayers || !rootFrame.isVisible || !root->isVisible) return;
auto rootModel = isRootTransform
? math::quad_model_get(TARGET_SIZE, {}, TARGET_SIZE * 0.5f)
@@ -788,34 +798,45 @@ namespace anm2ed::imgui
texture_render(shaderTexture, resources.icons[icon].id, rootTransform, color);
};
if (layeredOnions)
if (layeredOnions && root)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(animation->rootAnimation, sample))
if (auto sampleTime = sample_time_for_item(*root, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
auto sampleTransform = transform_for_time(*sampleTime);
draw_root(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_root(time, transform, {}, 0.0f, false);
for (auto& id : animation->layerOrder)
if (auto layerAnimations = element_child_first_get(*animation, ElementType::LAYER_ANIMATIONS))
{
if (!animation->layerAnimations.contains(id)) continue;
auto& layerAnimation = animation->layerAnimations[id];
if (!layerAnimation.isVisible) continue;
if (!anm2.content.layers.contains(id)) continue;
auto& layer = anm2.content.layers.at(id);
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
if (!spritesheet || !spritesheet->is_valid()) continue;
auto draw_layer =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
auto layer_animation_draw = [&](auto&& self, Element& layerAnimation, bool isParentVisible = true) -> void
{
if (auto frame = layerAnimation.frame_generate(sampleTime, anm2::LAYER); frame.isVisible)
if (layerAnimation.type == ElementType::GROUP)
{
auto& texture = spritesheet->texture;
for (auto& child : layerAnimation.children)
self(self, child, isParentVisible && layerAnimation.isVisible);
return;
}
if (layerAnimation.type != ElementType::LAYER_ANIMATION || !isParentVisible ||
!layerAnimation.isVisible || !is_track_group_visible(*layerAnimations, layerAnimation))
return;
auto id = layerAnimation.layerId;
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, id);
if (!layer) return;
auto spritesheet = anm2.element_get(ElementType::SPRITESHEET, layer->spritesheetId);
auto textureInfo = document.texture_get(layer->spritesheetId);
if (!spritesheet || !textureInfo || !textureInfo->is_valid()) return;
auto draw_layer = [&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor,
float sampleAlpha, bool isOnion)
{
auto frame = frame_generate(layerAnimation, sampleTime);
if (!frame.isVisible) return;
auto& texture = *textureInfo;
auto texSize = vec2(texture.size);
if (texSize.x <= 0.0f || texSize.y <= 0.0f) return;
@@ -835,9 +856,9 @@ namespace anm2ed::imgui
vec3 frameColorOffset = frame.colorOffset + colorOffset + sampleColor;
vec4 frameTint = frame.tint;
if (isRootTransform)
if (isRootTransform && root)
{
auto rootFrame = animation->rootAnimation.frame_generate(sampleTime, anm2::ROOT);
auto rootFrame = frame_generate(*root, sampleTime);
frameColorOffset += rootFrame.colorOffset;
frameTint *= rootFrame.tint;
}
@@ -860,39 +881,54 @@ namespace anm2ed::imgui
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
}
}
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(layerAnimation, sample))
{
auto sampleTransform = transform_for_time(*sampleTime);
draw_layer(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_layer(time, transform, {}, 0.0f, false);
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(layerAnimation, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
draw_layer(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_layer(time, transform, {}, 0.0f, false);
for (auto& layerAnimation : layerAnimations->children)
layer_animation_draw(layer_animation_draw, layerAnimation);
}
for (auto& [id, nullAnimation] : animation->nullAnimations)
if (auto nullAnimations = element_child_first_get(*animation, ElementType::NULL_ANIMATIONS))
{
if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
auto nullInfo = anm2.content.nulls.find(id);
if (nullInfo == anm2.content.nulls.end()) continue;
auto& isShowRect = nullInfo->second.isShowRect;
auto draw_null =
[&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion)
auto null_animation_draw = [&](auto&& self, Element& nullAnimation, bool isParentVisible = true) -> void
{
if (auto frame = nullAnimation.frame_generate(sampleTime, anm2::NULL_); frame.isVisible)
if (nullAnimation.type == ElementType::GROUP)
{
for (auto& child : nullAnimation.children)
self(self, child, isParentVisible && nullAnimation.isVisible);
return;
}
if (nullAnimation.type != ElementType::NULL_ANIMATION || !isParentVisible || !nullAnimation.isVisible ||
!is_track_group_visible(*nullAnimations, nullAnimation) || isOnlyShowLayers)
return;
auto id = nullAnimation.nullId;
auto nulls = anm2.element_get(ElementType::NULLS);
auto nullInfo = nulls ? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, id) : nullptr;
if (!nullInfo) return;
auto isShowRect = nullInfo->isShowRect;
auto draw_null = [&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor,
float sampleAlpha, bool isOnion)
{
auto frame = frame_generate(nullAnimation, sampleTime);
if (!frame.isVisible) return;
auto icon = isShowRect ? icon::POINT : isAltIcons ? icon::TARGET_ALT : icon::TARGET;
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
auto color = isOnion ? vec4(sampleColor, 1.0f - sampleAlpha)
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
: NULL_COLOR;
: id == reference.itemID && referenceItemType == ItemType::NULL_ ? color::RED
: NULL_COLOR;
auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
@@ -908,18 +944,20 @@ namespace anm2ed::imgui
rect_render(shaderLine, rectTransform, rectModel, color);
}
}
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(nullAnimation, sample))
{
auto sampleTransform = transform_for_time(*sampleTime);
draw_null(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_null(time, transform, {}, 0.0f, false);
};
if (layeredOnions)
for (auto& sample : *layeredOnions)
if (auto sampleTime = sample_time_for_item(nullAnimation, sample))
{
auto sampleTransform = transform_for_time(animation, *sampleTime);
draw_null(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true);
}
draw_null(time, transform, {}, 0.0f, false);
for (auto& nullAnimation : nullAnimations->children)
null_animation_draw(null_animation_draw, nullAnimation);
}
};
@@ -929,7 +967,7 @@ namespace anm2ed::imgui
render(animation, frameTime, {}, 0.0f, layeredOnions, settings.onionskinMode == (int)OnionskinMode::INDEX);
if (auto overlayAnimation = anm2.animation_get(overlayIndex))
if (auto overlayAnimation = anm2.element_get(ElementType::ANIMATION, overlayIndex))
render(overlayAnimation, frameTime, {}, 1.0f - math::uint8_to_float(overlayTransparency), layeredOnions,
settings.onionskinMode == (int)OnionskinMode::INDEX);
}
@@ -945,10 +983,10 @@ namespace anm2ed::imgui
isPreviewHovered = ImGui::IsItemHovered();
if (animation && animation->triggers.isVisible && !isOnlyShowLayers && !manager.isRecording)
auto triggers = animation ? animation_item_get(*animation, ItemType::TRIGGER) : nullptr;
if (animation && triggers && triggers->isVisible && !isOnlyShowLayers && !manager.isRecording)
{
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER);
trigger.isVisible && trigger.eventID > -1)
if (auto trigger = frame_generate(*triggers, frameTime); trigger.isVisible && trigger.eventId > -1)
{
auto clipMin = ImGui::GetItemRectMin();
auto clipMax = ImGui::GetItemRectMax();
@@ -958,9 +996,9 @@ namespace anm2ed::imgui
drawList->PushClipRect(clipMin, clipMax);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
auto triggerTextColor = isLightTheme ? TRIGGER_TEXT_COLOR_LIGHT : TRIGGER_TEXT_COLOR_DARK;
if (anm2.content.events.contains(trigger.eventID))
drawList->AddText(textPos, ImGui::GetColorU32(triggerTextColor),
anm2.content.events.at(trigger.eventID).name.c_str());
auto events = anm2.element_get(ElementType::EVENTS);
auto event = events ? element_child_id_get(*events, ElementType::EVENT_ELEMENT, trigger.eventId) : nullptr;
if (event) drawList->AddText(textPos, ImGui::GetColorU32(triggerTextColor), event->name.c_str());
ImGui::PopFont();
drawList->PopClipRect();
}
@@ -999,20 +1037,23 @@ namespace anm2ed::imgui
auto isKeyDown = isLeftDown || isRightDown || isUpDown || isDownDown;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isZoomIn = shortcut(manager.chords[SHORTCUT_ZOOM_IN], shortcut::GLOBAL);
auto isZoomOut = shortcut(manager.chords[SHORTCUT_ZOOM_OUT], shortcut::GLOBAL);
auto isZoomIn = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_IN], shortcut::GLOBAL);
auto isZoomOut = isFocused && shortcut(manager.chords[SHORTCUT_ZOOM_OUT], shortcut::GLOBAL);
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto item = document.item_get();
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
auto item = anm2.element_get(reference.animationIndex, referenceItemType, reference.itemID);
auto useTool = tool;
auto step = isMod ? STEP_FAST : STEP;
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto selectedNullIt = reference.itemType == anm2::NULL_ ? anm2.content.nulls.find(reference.itemID)
: anm2.content.nulls.end();
bool isSelectedNullRect = selectedNullIt != anm2.content.nulls.end() && selectedNullIt->second.isShowRect;
auto null_rect_top_left = [](const anm2::Frame& frame) { return frame.position - (frame.scale * 0.5f); };
auto nulls = anm2.element_get(ElementType::NULLS);
auto selectedNull = referenceItemType == ItemType::NULL_ && nulls
? element_child_id_get(*nulls, ElementType::NULL_ELEMENT, reference.itemID)
: nullptr;
bool isSelectedNullRect = selectedNull && selectedNull->isShowRect;
auto null_rect_top_left = [](const Element& frame) { return frame.position - (frame.scale * 0.5f); };
if (isMouseMiddleDown) useTool = tool::PAN;
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
@@ -1033,8 +1074,31 @@ namespace anm2ed::imgui
auto isToolDuring = isToolMouseDown || isKeyDown;
auto isToolEnd = isToolMouseReleased || isKeyReleased;
auto frame_change_apply = [&](anm2::FrameChange frameChange, anm2::ChangeType changeType = anm2::ADJUST)
{ item->frames_change(frameChange, reference.itemType, changeType, frames); };
auto frame_snapshot = [&](auto message)
{
manager.command_push({manager.selected,
[message](Manager&, Document& document) { document.snapshot(localize.get(message)); }});
};
auto frame_change_apply = [&](FrameChange frameChange, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
auto queuedFrames = frames;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto item = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.itemID);
if (!item) return;
frames_change(*item, frameChange, queuedReferenceItemType, changeType, queuedFrames);
}});
};
auto frames_changed = [&]()
{
manager.command_push(
{manager.selected, [](Manager&, Document& document) { document.anm2_change(Document::FRAMES); }});
};
auto null_rect_change = [&](vec2 topLeft, vec2 rectSize)
{
topLeft = vec2(ivec2(topLeft));
@@ -1064,7 +1128,7 @@ namespace anm2ed::imgui
if (!item || !frame || frames.empty()) break;
if (isToolBegin)
{
document.snapshot(localize.get(EDIT_FRAME_POSITION));
frame_snapshot(EDIT_FRAME_POSITION);
if (isToolMouseClicked)
{
auto origin = isSelectedNullRect ? null_rect_top_left(*frame) : frame->position;
@@ -1082,13 +1146,13 @@ namespace anm2ed::imgui
frame_change_apply({.positionX = (int)position.x, .positionY = (int)position.y});
}
if (isLeftPressed) frame_change_apply({.positionX = step}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step}, anm2::ADD);
if (isUpPressed) frame_change_apply({.positionY = step}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.positionX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.positionY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step}, ChangeType::ADD);
if (isToolMouseReleased) isMoveDragging = false;
if (isToolEnd) document.change(Document::FRAMES);
if (isToolEnd) frames_changed();
if (isToolDuring)
{
if (ImGui::BeginTooltip())
@@ -1104,7 +1168,7 @@ namespace anm2ed::imgui
if (!item || !frame || frames.empty()) break;
if (isToolBegin)
{
document.snapshot(localize.get(EDIT_FRAME_SCALE));
frame_snapshot(EDIT_FRAME_SCALE);
if (isToolMouseClicked && isSelectedNullRect) nullRectScaleAnchor = null_rect_top_left(*frame);
}
if (isToolMouseDown)
@@ -1134,21 +1198,17 @@ namespace anm2ed::imgui
if (isSelectedNullRect)
{
if (isLeftPressed)
frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, anm2::SUBTRACT);
if (isRightPressed)
frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, anm2::ADD);
if (isUpPressed)
frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, anm2::SUBTRACT);
if (isDownPressed)
frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.positionX = step * 0.5f, .scaleX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.positionY = step * 0.5f, .scaleY = step}, ChangeType::ADD);
}
else
{
if (isLeftPressed) frame_change_apply({.scaleX = step}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.scaleX = step}, anm2::ADD);
if (isUpPressed) frame_change_apply({.scaleY = step}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.scaleY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.scaleX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.scaleX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.scaleY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.scaleY = step}, ChangeType::ADD);
}
if (isToolDuring)
@@ -1173,15 +1233,15 @@ namespace anm2ed::imgui
maxPoint = vec2(ivec2(maxPoint));
null_rect_change(minPoint, maxPoint - minPoint);
}
document.change(Document::FRAMES);
frames_changed();
}
break;
case tool::ROTATE:
if (!item || !frame || frames.empty()) break;
if (isToolBegin) document.snapshot(localize.get(EDIT_FRAME_ROTATION));
if (isToolMouseDown) frame_change_apply({.rotation = (int)mouseDelta.x}, anm2::ADD);
if (isLeftPressed || isDownPressed) frame_change_apply({.rotation = step}, anm2::SUBTRACT);
if (isUpPressed || isRightPressed) frame_change_apply({.rotation = step}, anm2::ADD);
if (isToolBegin) frame_snapshot(EDIT_FRAME_ROTATION);
if (isToolMouseDown) frame_change_apply({.rotation = (int)mouseDelta.x}, ChangeType::ADD);
if (isLeftPressed || isDownPressed) frame_change_apply({.rotation = step}, ChangeType::SUBTRACT);
if (isUpPressed || isRightPressed) frame_change_apply({.rotation = step}, ChangeType::ADD);
if (isToolDuring)
{
@@ -1193,7 +1253,7 @@ namespace anm2ed::imgui
}
}
if (isToolEnd) document.change(Document::FRAMES);
if (isToolEnd) frames_changed();
break;
default:
break;
@@ -1228,27 +1288,17 @@ namespace anm2ed::imgui
}
}
if (tool == tool::PAN && ImGui::BeginPopupContextWindow("##Animation Preview Context Menu", ImGuiMouseButton_Right))
if (tool == tool::PAN)
{
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_UNDO), settings.shortcutUndo.c_str(), false,
document.is_able_to_undo()))
document.undo();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REDO), settings.shortcutRedo.c_str(), false,
document.is_able_to_redo()))
document.redo();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(LABEL_CENTER_VIEW), settings.shortcutCenterView.c_str())) center_view();
if (ImGui::MenuItem(localize.get(LABEL_FIT), settings.shortcutFit.c_str(), false, animation)) fit_view();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_IN), settings.shortcutZoomIn.c_str())) zoom_in();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ZOOM_OUT), settings.shortcutZoomOut.c_str())) zoom_out();
ImGui::EndPopup();
Actions actions{};
actions_undo_redo_add(actions, manager, document);
actions.separator();
actions.add(ACTION_CENTER_VIEW, []() { return true; }, center_view);
actions.add(ACTION_FIT_VIEW, [&]() { return animation; }, fit_view);
actions.separator();
actions.add(ACTION_ZOOM_IN, []() { return true; }, zoom_in);
actions.add(ACTION_ZOOM_OUT, []() { return true; }, zoom_out);
actions_context_window_draw("##Animation Preview Context Menu", actions, settings);
}
manager.progressPopup.trigger();