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();
+2
View File
@@ -15,6 +15,7 @@ namespace anm2ed::imgui
MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
AudioStream audioStream = AudioStream(mixer);
bool wasPlaybackPlaying{};
bool isFocused{};
bool isPreviewHovered{};
bool isSizeTrySet{true};
Settings savedSettings{};
@@ -38,6 +39,7 @@ namespace anm2ed::imgui
public:
AnimationPreview();
bool is_focused_get() const;
void tick(Manager&, Settings&, float);
void update(Manager&, Settings&, Resources&);
};
-501
View File
@@ -1,501 +0,0 @@
#include "animations.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "vector_.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Animations::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& selection = document.animation.selection;
auto& mergeSelection = document.merge.selection;
auto& mergeReference = document.merge.reference;
auto& overlayIndex = document.overlayIndex;
auto rename_format_get = [&](int index)
{ return std::format("###Document #{} Animation #{}", manager.selected, index); };
auto rename = [&]()
{
if (!selection.empty()) renameQueued = *selection.begin();
};
auto add = [&]()
{
auto behavior = [&]()
{
anm2::Animation animation;
animation.name = localize.get(TEXT_NEW_ANIMATION);
if (anm2::Animation* referenceAnimation = document.animation_get())
{
for (auto [id, layerAnimation] : referenceAnimation->layerAnimations)
animation.layerAnimations[id] = anm2::Item();
animation.layerOrder = referenceAnimation->layerOrder;
for (auto [id, nullAnimation] : referenceAnimation->nullAnimations)
animation.nullAnimations[id] = anm2::Item();
}
animation.rootAnimation.frames.emplace_back(anm2::Frame());
auto index = (int)anm2.animations.items.size();
if (!selection.empty())
{
index = *selection.rbegin() + 1;
index = std::min(index, (int)anm2.animations.items.size());
}
if (anm2.animations.items.empty()) anm2.animations.defaultAnimation = animation.name;
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
selection = {index};
reference = {index};
newAnimationSelectedIndex = index;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_ANIMATION), Document::ANIMATIONS, behavior());
};
auto remove = [&]()
{
auto behavior = [&]()
{
if (!selection.empty())
{
for (auto it = selection.rbegin(); it != selection.rend(); ++it)
{
auto i = *it;
if (overlayIndex == i) overlayIndex = -1;
if (reference.animationIndex == i) reference.animationIndex = -1;
anm2.animations.items.erase(anm2.animations.items.begin() + i);
}
selection.clear();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto duplicate = [&]()
{
auto behavior = [&]()
{
auto duplicated = selection;
auto end = std::ranges::max(duplicated);
for (auto& id : duplicated)
{
anm2.animations.items.insert(anm2.animations.items.begin() + end, anm2.animations.items[id]);
selection.insert(++end);
selection.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto merge = [&]()
{
auto behavior = [&]()
{
if (mergeSelection.contains(overlayIndex)) overlayIndex = -1;
auto merged = anm2.animations_merge(mergeReference, mergeSelection, (merge::Type)settings.mergeType,
settings.mergeIsDeleteAnimationsAfter);
selection = {merged};
reference = {merged};
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto merge_popup_open = [&]()
{
mergePopup.open();
mergeSelection.clear();
mergeReference = *selection.begin();
};
auto merge_quick = [&]()
{
auto behavior = [&]()
{
int merged{};
if (selection.contains(overlayIndex)) overlayIndex = -1;
if (selection.size() > 1)
merged = anm2.animations_merge(*selection.begin(), selection);
else if (selection.size() == 1 && *selection.begin() != (int)anm2.animations.items.size() - 1)
{
auto start = *selection.begin();
auto next = *selection.begin() + 1;
std::set<int> animationSet{};
animationSet.insert(start);
animationSet.insert(next);
merged = anm2.animations_merge(start, animationSet);
}
else
return;
selection = {merged};
reference = {merged};
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
auto default_set = [&]()
{
DOCUMENT_EDIT(document, localize.get(EDIT_DEFAULT_ANIMATION), Document::ANIMATIONS,
anm2.animations.defaultAnimation = anm2.animations.items[*selection.begin()].name);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& i : selection)
clipboardText += anm2.animations.items[i].to_string();
clipboard.set(clipboardText);
};
auto cut = [&]()
{
copy();
DOCUMENT_EDIT(document, localize.get(EDIT_CUT_ANIMATIONS), Document::ANIMATIONS, remove());
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto clipboardText = clipboard.get();
auto start = selection.empty() ? anm2.animations.items.size() : *selection.rbegin() + 1;
std::set<int> indices{};
std::string errorString{};
if (anm2.animations_deserialize(clipboardText, start, indices, &errorString))
{
if (!indices.empty())
{
auto index = *indices.rbegin();
selection = {index};
reference = {index};
newAnimationSelectedIndex = index;
}
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_DESERIALIZE_ANIMATIONS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_ANIMATIONS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_ANIMATIONS), Document::ANIMATIONS, behavior());
};
if (ImGui::Begin(localize.get(LABEL_ANIMATIONS_WINDOW), &settings.windowIsAnimations))
{
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::IsKeyPressed(ImGuiKey_Escape))
reference = {};
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
{
selection.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{
ImGui::PushID((int)i);
auto isDefault = anm2.animations.defaultAnimation == animation.name;
auto isReferenced = reference.animationIndex == (int)i;
auto isNewAnimation = newAnimationSelectedIndex == (int)i;
auto font = isDefault && isReferenced ? font::BOLD_ITALICS
: isDefault ? font::BOLD
: isReferenced ? font::ITALICS
: font::REGULAR;
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
ImGui::SetNextItemSelectionUserData((int)i);
if (isNewAnimation || renameQueued == i)
{
renameState = RENAME_FORCE_EDIT;
renameQueued = -1;
}
if (selectable_input_text(animation.name, rename_format_get(i), animation.name, selection.contains((int)i),
ImGuiSelectableFlags_None, renameState))
{
reference = {(int)i};
document.frames.clear();
if (renameState == RENAME_BEGIN)
document.snapshot(localize.get(SNAPSHOT_RENAME_ANIMATION));
else if (renameState == RENAME_FINISHED)
{
if (anm2.animations.items.size() == 1) anm2.animations.defaultAnimation = animation.name;
document.change(Document::ANIMATIONS);
}
}
if (isNewAnimation)
{
isUpdateScroll = true;
newAnimationSelectedIndex = -1;
}
ImGui::PopFont();
if (isUpdateScroll && isReferenced)
{
ImGui::SetScrollHereY(0.5f);
isUpdateScroll = false;
}
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(animation.name.c_str());
ImGui::PopFont();
if (isDefault)
{
ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::TextUnformatted(localize.get(BASIC_DEFAULT));
ImGui::PopFont();
}
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_LENGTH), std::make_format_args(animation.frameNum)).c_str());
auto loopLabel = animation.isLoop ? localize.get(BASIC_YES) : localize.get(BASIC_NO);
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_LOOP), std::make_format_args(loopLabel)).c_str());
ImGui::EndTooltip();
}
if (ImGui::BeginDragDropSource())
{
static std::vector<int> dragDropSelection{};
dragDropSelection.assign(selection.begin(), selection.end());
ImGui::SetDragDropPayload("Animation Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
for (auto& i : dragDropSelection)
ImGui::Text("%s", anm2.animations.items[(int)i].name.c_str());
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget())
{
if (auto payload = ImGui::AcceptDragDropPayload("Animation Drag Drop"))
{
auto payloadIndices = (int*)(payload->Data);
auto payloadCount = payload->DataSize / sizeof(int);
std::vector<int> indices(payloadIndices, payloadIndices + payloadCount);
std::sort(indices.begin(), indices.end());
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_ANIMATIONS), Document::ANIMATIONS,
selection = vector::move_indices(anm2.animations.items, indices, i));
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_RENAME], shortcut::FOCUSED)) rename();
if (shortcut(manager.chords[SHORTCUT_MERGE], shortcut::FOCUSED)) merge_quick();
if (shortcut(manager.chords[SHORTCUT_CUT], shortcut::FOCUSED)) cut();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_RENAME), settings.shortcutRename.c_str(), false,
selection.size() == 1))
rename();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_DUPLICATE), settings.shortcutDuplicate.c_str())) duplicate();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_MERGE), settings.shortcutMerge.c_str())) merge_quick();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_REMOVE), settings.shortcutRemove.c_str())) remove();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_DEFAULT), settings.shortcutDefault.c_str(), false,
selection.size() == 1))
default_set();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_CUT), settings.shortcutCut.c_str(), false, !selection.empty())) cut();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(5);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_ANIMATION), settings.shortcutAdd);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
shortcut(manager.chords[SHORTCUT_DUPLICATE]);
if (ImGui::Button(localize.get(BASIC_DUPLICATE), widgetSize)) duplicate();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_DUPLICATE_ANIMATION), settings.shortcutDuplicate);
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize)) merge_popup_open();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_OPEN_MERGE_POPUP), settings.shortcutMerge);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize)) remove();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_ANIMATION), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
shortcut(manager.chords[SHORTCUT_DEFAULT]);
if (ImGui::Button(localize.get(BASIC_DEFAULT), widgetSize)) default_set();
ImGui::EndDisabled();
set_item_tooltip_shortcut(localize.get(TOOLTIP_SET_DEFAULT_ANIMATION), settings.shortcutDefault);
mergePopup.trigger();
if (ImGui::BeginPopupModal(mergePopup.label(), &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto close = [&]()
{
mergeSelection.clear();
mergePopup.close();
};
auto& type = settings.mergeType;
auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter;
auto footerSize = footer_size_get();
auto optionsSize = child_size_get(2);
auto deleteAfterSize = child_size_get();
auto animationsSize =
ImVec2(0, ImGui::GetContentRegionAvail().y -
(optionsSize.y + deleteAfterSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 3));
if (ImGui::BeginChild(localize.get(LABEL_ANIMATIONS_CHILD), animationsSize, ImGuiChildFlags_Borders))
{
mergeSelection.start(anm2.animations.items.size());
for (int i = 0; i < (int)anm2.animations.items.size(); i++)
{
if (i == mergeReference) continue;
auto& animation = anm2.animations.items[i];
ImGui::PushID(i);
ImGui::SetNextItemSelectionUserData(i);
ImGui::Selectable(animation.name.c_str(), mergeSelection.contains(i));
ImGui::PopID();
}
mergeSelection.finish();
}
ImGui::EndChild();
if (ImGui::BeginChild("##Merge Options", optionsSize, ImGuiChildFlags_Borders))
{
auto size = ImVec2(optionsSize.x * 0.5f, optionsSize.y - ImGui::GetStyle().WindowPadding.y * 2);
if (ImGui::BeginChild("##Merge Options 1", size))
{
ImGui::RadioButton(localize.get(LABEL_APPEND_FRAMES), &type, merge::APPEND);
ImGui::RadioButton(localize.get(LABEL_PREPEND_FRAMES), &type, merge::PREPEND);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Merge Options 2", size))
{
ImGui::RadioButton(localize.get(LABEL_REPLACE_FRAMES), &type, merge::REPLACE);
ImGui::RadioButton(localize.get(LABEL_IGNORE_FRAMES), &type, merge::IGNORE);
}
ImGui::EndChild();
}
ImGui::EndChild();
if (ImGui::BeginChild("##Merge Delete After", deleteAfterSize, ImGuiChildFlags_Borders))
ImGui::Checkbox(localize.get(LABEL_DELETE_ANIMATIONS_AFTER), &isDeleteAnimationsAfter);
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
ImGui::BeginDisabled(mergeSelection.empty());
if (ImGui::Button(localize.get(LABEL_MERGE), widgetSize))
{
merge();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_CLOSE), widgetSize)) close();
ImGui::EndPopup();
}
}
ImGui::End();
auto isNextAnimation = shortcut(manager.chords[SHORTCUT_NEXT_ANIMATION], shortcut::GLOBAL);
auto isPreviousAnimation = shortcut(manager.chords[SHORTCUT_PREVIOUS_ANIMATION], shortcut::GLOBAL);
if ((isPreviousAnimation || isNextAnimation) && !anm2.animations.items.empty())
{
if (isPreviousAnimation)
reference.animationIndex = glm::clamp(--reference.animationIndex, 0, (int)anm2.animations.items.size() - 1);
if (isNextAnimation)
reference.animationIndex = glm::clamp(++reference.animationIndex, 0, (int)anm2.animations.items.size() - 1);
selection = {reference.animationIndex};
isUpdateScroll = true;
}
}
}
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
#include "strings.hpp"
namespace anm2ed::imgui
{
class Animations
{
PopupHelper mergePopup{PopupHelper(LABEL_ANIMATIONS_MERGE_POPUP)};
int newAnimationSelectedIndex{-1};
int renameQueued{-1};
bool isInContextMenu{};
bool isUpdateScroll{};
RenameState renameState{RENAME_SELECTABLE};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-207
View File
@@ -1,207 +0,0 @@
#include "events.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.event.reference;
auto& selection = document.event.selection;
auto rename_format_get = [&](int id) { return std::format("###Document #{} Event #{}", manager.selected, id); };
auto rename = [&]()
{
if (!selection.empty()) renameQueued = *selection.begin();
};
auto add = [&]()
{
auto behavior = [&]()
{
auto id = map::next_id_get(anm2.content.events);
anm2::Event event{};
event.name = localize.get(TEXT_NEW_EVENT);
anm2.content.events[id] = event;
selection = {id};
reference = {id};
newEventId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_EVENT), Document::EVENTS, behavior());
};
auto remove_unused = [&]()
{
auto unused = anm2.events_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
for (auto& animation : anm2.animations.items)
for (auto& trigger : animation.triggers.frames)
if (trigger.eventID == id) trigger.eventID = -1;
anm2.content.events.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_EVENTS), Document::EVENTS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.events[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxEventIdBefore = anm2.content.events.empty() ? -1 : anm2.content.events.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_EVENTS));
if (anm2.events_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.events.empty())
{
auto maxEventIdAfter = anm2.content.events.rbegin()->first;
if (maxEventIdAfter > maxEventIdBefore)
{
newEventId = maxEventIdAfter;
selection = {maxEventIdAfter};
reference = maxEventIdAfter;
}
}
document.change(Document::EVENTS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_EVENTS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_EVENTS), Document::EVENTS, behavior());
};
if (ImGui::Begin(localize.get(LABEL_EVENTS_WINDOW), &settings.windowIsEvents))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Events Child", childSize, true))
{
selection.start(anm2.content.events.size());
for (auto& [id, event] : anm2.content.events)
{
auto isNewEvent = (newEventId == id);
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
if (isNewEvent || renameQueued == id)
{
renameState = RENAME_FORCE_EDIT;
renameQueued = -1;
}
if (selectable_input_text(event.name, rename_format_get(id), event.name, selection.contains(id),
ImGuiSelectableFlags_None, renameState))
{
if (renameState == RENAME_BEGIN)
document.snapshot(localize.get(EDIT_RENAME_EVENT));
else if (renameState == RENAME_FINISHED)
document.change(Document::EVENTS);
}
if (isNewEvent)
{
ImGui::SetScrollHereY(0.5f);
newEventId = -1;
}
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(event.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_RENAME], shortcut::FOCUSED)) rename();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_RENAME), settings.shortcutRename.c_str(), false,
selection.size() == 1))
rename();
if (ImGui::MenuItem(localize.get(SHORTCUT_STRING_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_EVENT), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_EVENTS), settings.shortcutRemove);
}
ImGui::End();
}
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Events
{
int newEventId{-1};
int renameQueued{-1};
RenameState renameState{RENAME_SELECTABLE};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
+218 -114
View File
@@ -1,13 +1,14 @@
#include "frame_properties.hpp"
#include <glm/gtc/type_ptr.hpp>
#include <limits>
#include <ranges>
#include <string>
#include <vector>
#include "math_.hpp"
#include "math.hpp"
#include "strings.hpp"
#include "types.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::util::math;
using namespace anm2ed::types;
@@ -17,11 +18,70 @@ namespace anm2ed::imgui
{
void FrameProperties::update(Manager& manager, Settings& settings)
{
if (ImGui::Begin(localize.get(LABEL_FRAME_PROPERTIES_WINDOW), &settings.windowIsFrameProperties))
auto& document = *manager.get();
auto frameSelectionCount =
document.frames.references.empty() ? document.frames.selection.size() : document.frames.references.size();
auto isSingleFrameSelection = frameSelectionCount == 1;
auto isMultiFrameSelection = frameSelectionCount > 1;
auto isSingleFrameBatchMode =
isSingleFrameSelection && document.reference.itemType != TRIGGER && isBatchMode;
auto isBatchFrameProperties = isMultiFrameSelection || isSingleFrameBatchMode;
auto windowLabel = std::string(localize.get(isBatchFrameProperties ? LABEL_CHANGE_ALL_FRAME_PROPERTIES
: LABEL_FRAME_PROPERTIES_WINDOW));
if (isBatchFrameProperties) windowLabel += "###Frame Properties";
if (ImGui::Begin(windowLabel.c_str(), &settings.windowIsFrameProperties))
{
auto& document = *manager.get();
auto& frames = document.frames.selection;
auto& type = document.reference.itemType;
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& type = reference.itemType;
auto itemType = static_cast<ItemType>(type);
auto frame = anm2.element_get(reference.animationIndex, itemType, reference.frameIndex, reference.itemID);
auto frame_edit = [&](auto message, auto behavior)
{
auto queuedReference = reference;
manager.command_push({manager.selected,
[=](Manager&, Document& document) mutable
{
auto itemType = static_cast<ItemType>(queuedReference.itemType);
auto frame = document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.frameIndex,
queuedReference.itemID);
auto item =
document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.itemID);
if (!frame) return;
document.snapshot(localize.get(message));
behavior(document, *frame, item, queuedReference);
document.anm2_change(Document::FRAMES);
}});
};
auto persistent_edit = [&](auto state, auto message, auto behavior)
{
if (state == edit::NONE) return;
auto queuedReference = reference;
manager.command_push({manager.selected,
[=](Manager&, Document& document) mutable
{
auto itemType = static_cast<ItemType>(queuedReference.itemType);
auto frame = document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.frameIndex,
queuedReference.itemID);
auto item =
document.anm2.element_get(queuedReference.animationIndex, itemType,
queuedReference.itemID);
if (!frame) return;
if (state == edit::START) document.snapshot(localize.get(message));
behavior(document, *frame, item, queuedReference);
if (state == edit::END) document.anm2_change(Document::FRAMES);
}});
};
auto regionLabelsString = std::vector<std::string>{localize.get(BASIC_NONE)};
auto regionLabels = std::vector<const char*>{regionLabelsString[0].c_str()};
auto regionIds = std::vector<int>{-1};
@@ -33,17 +93,14 @@ namespace anm2ed::imgui
interpolationLabelsString[2].c_str(), interpolationLabelsString[3].c_str(),
interpolationLabelsString[4].c_str()};
auto interpolationValues =
std::vector<int>{anm2::Frame::Interpolation::NONE, anm2::Frame::Interpolation::LINEAR,
anm2::Frame::Interpolation::EASE_IN, anm2::Frame::Interpolation::EASE_OUT,
anm2::Frame::Interpolation::EASE_IN_OUT};
std::vector<int>{(int)Interpolation::NONE, (int)Interpolation::LINEAR, (int)Interpolation::EASE_IN,
(int)Interpolation::EASE_OUT, (int)Interpolation::EASE_IN_OUT};
if (type == anm2::LAYER && document.reference.itemID != -1)
if (type == LAYER && reference.itemID != -1)
{
if (auto layerIt = document.anm2.content.layers.find(document.reference.itemID);
layerIt != document.anm2.content.layers.end())
if (auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID))
{
auto spritesheetID = layerIt->second.spritesheetID;
auto regionIt = document.regionBySpritesheet.find(spritesheetID);
auto regionIt = document.regionBySpritesheet.find(layer->spritesheetId);
if (regionIt != document.regionBySpritesheet.end() && !regionIt->second.ids.empty() &&
!regionIt->second.labels.empty())
{
@@ -53,37 +110,70 @@ namespace anm2ed::imgui
}
}
if (frames.size() <= 1)
auto mode_selector_draw = [&]()
{
auto frame = document.frame_get();
auto useFrame = frame ? *frame : anm2::Frame();
auto displayFrame = frame && type == anm2::LAYER && document.reference.itemID != -1
? document.anm2.frame_effective(document.reference.itemID, *frame)
if (!isSingleFrameSelection || type == TRIGGER) return;
ImGui::SeparatorText(localize.get(BASIC_MODE));
int mode = isBatchMode ? 1 : 0;
ImGui::RadioButton(localize.get(BASIC_SINGLE), &mode, 0);
ImGui::SameLine();
ImGui::RadioButton(localize.get(BASIC_BATCH), &mode, 1);
isBatchMode = mode == 1;
};
if (isSingleFrameBatchMode)
{
changeAllFrameProperties.update(manager, document, settings);
mode_selector_draw();
}
else if (!isMultiFrameSelection)
{
auto useFrame = frame ? *frame : Element();
auto displayFrame = frame && type == LAYER && reference.itemID != -1
? anm2.frame_effective(reference.itemID, *frame)
: useFrame;
ImGui::BeginDisabled(!frame);
{
if (type == anm2::TRIGGER)
if (type == TRIGGER)
{
if (combo_id_mapped(localize.get(BASIC_EVENT), frame ? &useFrame.eventID : &dummy_value_negative<int>(),
if (combo_id_mapped(localize.get(BASIC_EVENT), frame ? &useFrame.eventId : &dummy_value_negative<int>(),
document.event.ids, document.event.labels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_EVENT), Document::FRAMES,
frame->eventID = useFrame.eventID);
{
auto eventId = useFrame.eventId;
frame_edit(EDIT_TRIGGER_EVENT,
[eventId](Document&, Element& frame, Element*, const Reference&) { frame.eventId = eventId; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_EVENT));
if (input_int_range(localize.get(BASIC_AT_FRAME), frame ? useFrame.atFrame : dummy_value<int>(), 0,
std::numeric_limits<int>::max(), STEP, STEP_FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_AT_FRAME), Document::FRAMES,
frame->atFrame = useFrame.atFrame);
{
auto atFrame = useFrame.atFrame;
frame_edit(EDIT_TRIGGER_AT_FRAME,
[atFrame](Document& document, Element& frame, Element* item, const Reference&)
{
frame.atFrame = atFrame;
if (!item) return;
frames_sort_by_at_frame(*item);
document.reference.frameIndex = frame_index_from_at_frame_get(*item, atFrame);
document.frames.selection = {document.reference.frameIndex};
document.frames.references = {document.reference};
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_AT_FRAME));
if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value<bool>()) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_VISIBILITY), Document::FRAMES,
frame->isVisible = useFrame.isVisible);
{
auto isVisible = useFrame.isVisible;
frame_edit(EDIT_TRIGGER_VISIBILITY,
[isVisible](Document&, Element& frame, Element*, const Reference&)
{ frame.isVisible = isVisible; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_VISIBILITY));
ImGui::SeparatorText(localize.get(LABEL_SOUNDS));
@@ -92,16 +182,23 @@ namespace anm2ed::imgui
if (ImGui::BeginChild("##Sounds Child", childSize, ImGuiChildFlags_Borders))
{
if (!useFrame.soundIDs.empty())
if (!useFrame.soundIds.empty())
{
for (auto [i, id] : std::views::enumerate(useFrame.soundIDs))
for (auto [i, id] : std::views::enumerate(useFrame.soundIds))
{
ImGui::PushID(i);
if (combo_id_mapped("##Sound", frame ? &id : &dummy_value_negative<int>(), document.sound.ids,
document.sound.labels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs[i] = id);
{
auto soundIndex = (std::size_t)i;
auto soundId = id;
frame_edit(EDIT_TRIGGER_SOUND,
[soundIndex, soundId](Document&, Element& frame, Element*, const Reference&)
{
if (soundIndex < frame.soundIds.size()) frame.soundIds[soundIndex] = soundId;
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIGGER_SOUND));
ImGui::PopID();
}
@@ -112,125 +209,127 @@ namespace anm2ed::imgui
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize) && frame)
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs.push_back(-1));
frame_edit(EDIT_ADD_TRIGGER_SOUND,
[](Document&, Element& frame, Element*, const Reference&) { frame.soundIds.push_back(-1); });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ADD_TRIGGER_SOUND));
ImGui::SameLine();
ImGui::BeginDisabled(useFrame.soundIDs.empty());
ImGui::BeginDisabled(useFrame.soundIds.empty());
if (ImGui::Button(localize.get(BASIC_REMOVE), widgetSize) && frame)
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_TRIGGER_SOUND), Document::FRAMES,
frame->soundIDs.pop_back());
frame_edit(EDIT_REMOVE_TRIGGER_SOUND,
[](Document&, Element& frame, Element*, const Reference&)
{
if (!frame.soundIds.empty()) frame.soundIds.pop_back();
});
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REMOVE_TRIGGER_SOUND));
ImGui::EndDisabled();
}
else
{
bool isRegionSet = frame && displayFrame.regionID != -1 && displayFrame.crop == frame->crop &&
bool isRegionSet = frame && displayFrame.regionId != -1 && displayFrame.crop == frame->crop &&
displayFrame.size == frame->size && displayFrame.pivot == frame->pivot;
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_ || isRegionSet);
ImGui::BeginDisabled(type == ROOT || type == NULL_ || isRegionSet);
{
auto cropDisplay = frame ? displayFrame.crop : vec2();
auto cropEdit =
drag_float2_persistent(localize.get(BASIC_CROP), frame ? &cropDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.crop) : "");
if (cropEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_CROP));
if (frame && cropEdit != edit::NONE) frame->crop = cropDisplay;
if (cropEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(cropEdit, EDIT_FRAME_CROP,
[cropDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.crop = cropDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_CROP));
auto sizeDisplay = frame ? displayFrame.size : vec2();
auto sizeEdit =
drag_float2_persistent(localize.get(BASIC_SIZE), frame ? &sizeDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.size) : "");
if (sizeEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_SIZE));
if (frame && sizeEdit != edit::NONE) frame->size = sizeDisplay;
if (sizeEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(sizeEdit, EDIT_FRAME_SIZE,
[sizeDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.size = sizeDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SIZE));
}
ImGui::EndDisabled();
auto positionEdit =
drag_float2_persistent(localize.get(BASIC_POSITION), frame ? &frame->position : &dummy_value<vec2>(),
drag_float2_persistent(localize.get(BASIC_POSITION),
frame ? &useFrame.position : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->position) : "");
if (positionEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_POSITION));
else if (positionEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(positionEdit, EDIT_FRAME_POSITION,
[position = useFrame.position](Document&, Element& frame, Element*, const Reference&)
{ frame.position = position; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_POSITION));
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_ || isRegionSet);
ImGui::BeginDisabled(type == ROOT || type == NULL_ || isRegionSet);
{
auto pivotDisplay = frame ? displayFrame.pivot : vec2();
auto pivotEdit =
drag_float2_persistent(localize.get(BASIC_PIVOT), frame ? &pivotDisplay : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(displayFrame.pivot) : "");
if (pivotEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_PIVOT));
if (frame && pivotEdit != edit::NONE) frame->pivot = pivotDisplay;
if (pivotEdit == edit::END)
document.change(Document::FRAMES);
persistent_edit(pivotEdit, EDIT_FRAME_PIVOT,
[pivotDisplay](Document&, Element& frame, Element*, const Reference&)
{ frame.pivot = pivotDisplay; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PIVOT));
}
ImGui::EndDisabled();
auto scaleEdit =
drag_float2_persistent(localize.get(BASIC_SCALE), frame ? &frame->scale : &dummy_value<vec2>(),
drag_float2_persistent(localize.get(BASIC_SCALE), frame ? &useFrame.scale : &dummy_value<vec2>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? vec2_format_get(frame->scale) : "");
persistent_edit(scaleEdit, EDIT_FRAME_SCALE,
[scale = useFrame.scale](Document&, Element& frame, Element*, const Reference&)
{ frame.scale = scale; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SCALE));
if (scaleEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_SCALE));
else if (scaleEdit == edit::END)
document.change(Document::FRAMES);
auto rotationEdit =
drag_float_persistent(localize.get(BASIC_ROTATION), frame ? &frame->rotation : &dummy_value<float>(),
drag_float_persistent(localize.get(BASIC_ROTATION), frame ? &useFrame.rotation : &dummy_value<float>(),
DRAG_SPEED, 0.0f, 0.0f, frame ? float_format_get(frame->rotation) : "");
persistent_edit(rotationEdit, EDIT_FRAME_ROTATION,
[rotation = useFrame.rotation](Document&, Element& frame, Element*, const Reference&)
{ frame.rotation = rotation; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ROTATION));
if (rotationEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_ROTATION));
else if (rotationEdit == edit::END)
document.change(Document::FRAMES);
if (input_int_range(localize.get(BASIC_DURATION), frame ? useFrame.duration : dummy_value<int>(),
frame ? anm2::FRAME_DURATION_MIN : 0, anm2::FRAME_DURATION_MAX, STEP, STEP_FAST,
frame ? FRAME_DURATION_MIN : 0, FRAME_DURATION_MAX, STEP, STEP_FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_DURATION), Document::FRAMES,
frame->duration = useFrame.duration);
{
auto duration = useFrame.duration;
frame_edit(EDIT_FRAME_DURATION,
[duration](Document&, Element& frame, Element*, const Reference&) { frame.duration = duration; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_DURATION));
auto tintEdit =
color_edit4_persistent(localize.get(BASIC_TINT), frame ? &frame->tint : &dummy_value<vec4>());
color_edit4_persistent(localize.get(BASIC_TINT), frame ? &useFrame.tint : &dummy_value<vec4>());
persistent_edit(tintEdit, EDIT_FRAME_TINT,
[tint = useFrame.tint](Document&, Element& frame, Element*, const Reference&)
{ frame.tint = tint; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TINT));
if (tintEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_TINT));
else if (tintEdit == edit::END)
document.change(Document::FRAMES);
auto colorOffsetEdit = color_edit3_persistent(localize.get(BASIC_COLOR_OFFSET),
frame ? &frame->colorOffset : &dummy_value<vec3>());
frame ? &useFrame.colorOffset : &dummy_value<vec3>());
persistent_edit(colorOffsetEdit, EDIT_FRAME_COLOR_OFFSET,
[colorOffset = useFrame.colorOffset](Document&, Element& frame, Element*, const Reference&)
{ frame.colorOffset = colorOffset; });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_COLOR_OFFSET));
if (colorOffsetEdit == edit::START)
document.snapshot(localize.get(EDIT_FRAME_COLOR_OFFSET));
else if (colorOffsetEdit == edit::END)
document.change(Document::FRAMES);
ImGui::BeginDisabled(type != anm2::LAYER);
if (combo_id_mapped(localize.get(BASIC_REGION), frame ? &useFrame.regionID : &dummy_value_negative<int>(),
regionIds, regionLabels) &&
ImGui::BeginDisabled(type != LAYER);
if (combo_id_mapped(localize.get(BASIC_REGION), frame ? &useFrame.regionId : &dummy_value_negative<int>(),
regionIds, regionLabels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_SET_REGION_PROPERTIES), Document::FRAMES,
frame->regionID = useFrame.regionID;
auto effectiveFrame = document.anm2.frame_effective(document.reference.itemID, *frame);
frame->crop = effectiveFrame.crop;
frame->size = effectiveFrame.size;
frame->pivot = effectiveFrame.pivot);
{
auto regionId = useFrame.regionId;
frame_edit(EDIT_SET_REGION_PROPERTIES,
[regionId](Document& document, Element& frame, Element*, const Reference& reference)
{
frame.regionId = regionId;
auto effectiveFrame = document.anm2.frame_effective(reference.itemID, frame);
frame.crop = effectiveFrame.crop;
frame.size = effectiveFrame.size;
frame.pivot = effectiveFrame.pivot;
});
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REGION));
ImGui::EndDisabled();
@@ -238,14 +337,19 @@ namespace anm2ed::imgui
if (combo_id_mapped(localize.get(BASIC_INTERPOLATED), &interpolationValue, interpolationValues,
interpolationLabels) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_INTERPOLATION), Document::FRAMES,
frame->interpolation = static_cast<anm2::Frame::Interpolation>(interpolationValue));
frame_edit(EDIT_FRAME_INTERPOLATION,
[interpolationValue](Document&, Element& frame, Element*, const Reference&)
{ frame.interpolation = static_cast<Interpolation>(interpolationValue); });
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_INTERPOLATION));
if (ImGui::Checkbox(localize.get(BASIC_VISIBLE), frame ? &useFrame.isVisible : &dummy_value<bool>()) &&
frame)
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_VISIBILITY), Document::FRAMES,
frame->isVisible = useFrame.isVisible);
{
auto isVisible = useFrame.isVisible;
frame_edit(EDIT_FRAME_VISIBILITY,
[isVisible](Document&, Element& frame, Element*, const Reference&)
{ frame.isVisible = isVisible; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FRAME_VISIBILITY));
auto widgetSize = widget_size_with_row_get(2);
@@ -253,40 +357,40 @@ namespace anm2ed::imgui
if (ImGui::Button(localize.get(LABEL_FLIP_X), widgetSize) && frame)
{
if (ImGui::IsKeyDown(ImGuiMod_Ctrl))
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_X), Document::FRAMES,
frame->scale.x = -frame->scale.x;
frame->position.x = -frame->position.x);
}
frame_edit(EDIT_FRAME_FLIP_X,
[](Document&, Element& frame, Element*, const Reference&)
{
frame.scale.x = -frame.scale.x;
frame.position.x = -frame.position.x;
});
else
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_X), Document::FRAMES,
frame->scale.x = -frame->scale.x);
}
frame_edit(EDIT_FRAME_FLIP_X,
[](Document&, Element& frame, Element*, const Reference&) { frame.scale.x = -frame.scale.x; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FLIP_X));
ImGui::SameLine();
if (ImGui::Button(localize.get(LABEL_FLIP_Y), widgetSize) && frame)
{
if (ImGui::IsKeyDown(ImGuiMod_Ctrl))
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_Y), Document::FRAMES,
frame->scale.y = -frame->scale.y;
frame->position.y = -frame->position.y);
}
frame_edit(EDIT_FRAME_FLIP_Y,
[](Document&, Element& frame, Element*, const Reference&)
{
frame.scale.y = -frame.scale.y;
frame.position.y = -frame.position.y;
});
else
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_FLIP_Y), Document::FRAMES,
frame->scale.y = -frame->scale.y);
}
frame_edit(EDIT_FRAME_FLIP_Y,
[](Document&, Element& frame, Element*, const Reference&) { frame.scale.y = -frame.scale.y; });
}
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_FLIP_Y));
}
}
ImGui::EndDisabled();
mode_selector_draw();
}
else
changeAllFrameProperties.update(document, settings);
changeAllFrameProperties.update(manager, document, settings);
}
ImGui::End();
+1
View File
@@ -10,6 +10,7 @@ namespace anm2ed::imgui
class FrameProperties
{
wizard::ChangeAllFrameProperties changeAllFrameProperties{};
bool isBatchMode{};
public:
void update(Manager&, Settings&);
-235
View File
@@ -1,235 +0,0 @@
#include "layers.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Layers::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.layer.reference;
auto& selection = document.layer.selection;
auto& propertiesPopup = manager.layerPropertiesPopup;
auto add = [&]() { manager.layer_properties_open(); };
auto remove_unused = [&]()
{
auto unused = anm2.layers_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.layers.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_LAYERS), Document::LAYERS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.layers[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxLayerIdBefore = anm2.content.layers.empty() ? -1 : anm2.content.layers.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_LAYERS));
if (anm2.layers_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.layers.empty())
{
auto maxLayerIdAfter = anm2.content.layers.rbegin()->first;
if (maxLayerIdAfter > maxLayerIdBefore)
{
newLayerId = maxLayerIdAfter;
selection = {maxLayerIdAfter};
reference = maxLayerIdAfter;
}
}
document.change(Document::NULLS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_LAYERS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_LAYERS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_LAYERS), Document::LAYERS, behavior());
};
auto properties = [&](int id) { manager.layer_properties_open(id); };
if (ImGui::Begin(localize.get(LABEL_LAYERS_WINDOW), &settings.windowIsLayers))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Layers Child", childSize, true))
{
selection.start(anm2.content.layers.size());
for (auto& [id, layer] : anm2.content.layers)
{
auto isSelected = selection.contains(id);
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
ImGui::Selectable(
std::vformat(localize.get(FORMAT_LAYER), std::make_format_args(id, layer.name, layer.spritesheetID))
.c_str(),
isSelected);
if (newLayerId == id)
{
ImGui::SetScrollHereY(0.5f);
newLayerId = -1;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id);
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(layer.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SPRITESHEET_ID), std::make_format_args(layer.spritesheetID)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_LAYER), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_LAYERS), settings.shortcutRemove);
}
ImGui::End();
manager.layer_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = child_size_get(2);
auto& layer = manager.editLayer;
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &layer.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
combo_id_mapped(localize.get(LABEL_SPRITESHEET), &layer.spritesheetID, document.spritesheet.ids,
document.spritesheet.labels);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_LAYER_SPRITESHEET));
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto layer_add = [&]()
{
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = layer;
selection = {id};
newLayerId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_LAYER), Document::LAYERS, layer_add());
}
else
{
auto layer_set = [&]()
{
anm2.content.layers[reference] = layer;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_LAYER_PROPERTIES), Document::LAYERS, layer_set());
}
manager.layer_properties_close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) manager.layer_properties_close();
manager.layer_properties_end();
ImGui::EndPopup();
}
}
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Layers
{
int newLayerId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-233
View File
@@ -1,233 +0,0 @@
#include "nulls.hpp"
#include <format>
#include <ranges>
#include "log.hpp"
#include "map_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace anm2ed::types;
namespace anm2ed::imgui
{
void Nulls::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.null.reference;
auto& selection = document.null.selection;
auto& propertiesPopup = manager.nullPropertiesPopup;
auto add = [&]() { manager.null_properties_open(); };
auto remove_unused = [&]()
{
auto unused = anm2.nulls_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.nulls.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_NULLS), Document::NULLS, behavior());
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.nulls[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxNullIdBefore = anm2.content.nulls.empty() ? -1 : anm2.content.nulls.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_NULLS));
if (anm2.nulls_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!anm2.content.nulls.empty())
{
auto maxNullIdAfter = anm2.content.nulls.rbegin()->first;
if (maxNullIdAfter > maxNullIdBefore)
{
newNullId = maxNullIdAfter;
selection = {maxNullIdAfter};
reference = maxNullIdAfter;
}
}
document.change(Document::NULLS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_NULLS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_NULLS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_NULLS), Document::NULLS, behavior());
};
auto properties = [&](int id) { manager.null_properties_open(id); };
if (ImGui::Begin(localize.get(LABEL_NULLS_WINDOW), &settings.windowIsNulls))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Nulls Child", childSize, true))
{
selection.start(anm2.content.nulls.size());
for (auto& [id, null] : anm2.content.nulls)
{
auto isSelected = selection.contains(id);
auto isReferenced = reference == id;
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::Selectable(std::vformat(localize.get(FORMAT_NULL), std::make_format_args(id, null.name)).c_str(),
isSelected);
if (newNullId == id)
{
ImGui::SetScrollHereY(0.5f);
newNullId = -1;
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) properties(id);
if (isReferenced) ImGui::PopFont();
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(null.name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::EndTooltip();
}
ImGui::PopID();
}
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty()))
copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_NULL), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_NULLS), settings.shortcutRemove);
}
ImGui::End();
manager.null_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = child_size_get(2);
auto& null = manager.editNull;
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &null.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_NAME));
ImGui::Checkbox(localize.get(LABEL_RECT), &null.isShowRect);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_NULL_RECT));
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto null_add = [&]()
{
auto id = map::next_id_get(anm2.content.nulls);
anm2.content.nulls[id] = null;
selection = {id};
newNullId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_NULL), Document::NULLS, null_add());
}
else
{
auto null_set = [&]()
{
anm2.content.nulls[reference] = null;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_NULL_PROPERTIES), Document::NULLS, null_set());
}
manager.null_properties_close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) manager.null_properties_close();
ImGui::EndPopup();
}
manager.null_properties_end();
}
}
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Nulls
{
int newNullId{-1};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
+1 -1
View File
@@ -2,7 +2,7 @@
#include <glm/gtc/type_ptr.hpp>
#include "imgui_.hpp"
#include "util/imgui/imgui.hpp"
#include "strings.hpp"
using namespace anm2ed::types;
-605
View File
@@ -1,605 +0,0 @@
#include "regions.hpp"
#include <algorithm>
#include <ranges>
#include <format>
#include "document.hpp"
#include "log.hpp"
#include "map_.hpp"
#include "math_.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "vector_.hpp"
#include "../../util/map_.hpp"
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
void Regions::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& selection = document.region.selection;
auto& frame = document.frames;
auto& spritesheetReference = document.spritesheet.reference;
auto& reference = document.region.reference;
auto style = ImGui::GetStyle();
auto spritesheet = map::find(anm2.content.spritesheets, spritesheetReference);
if (manager.isMakeRegionRequested)
{
spritesheetReference = manager.makeRegionSpritesheetId;
reference = -1;
editRegion = manager.makeRegion;
isPreserveEditRegionOnOpen = true;
propertiesPopup.open();
manager.isMakeRegionRequested = false;
spritesheet = map::find(anm2.content.spritesheets, spritesheetReference);
}
auto remove_unused = [&]()
{
if (!spritesheet) return;
auto unused = anm2.regions_unused(*spritesheet);
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
for (auto& animation : anm2.animations.items)
for (auto& layerAnimation : animation.layerAnimations | std::views::values)
for (auto& frame : layerAnimation.frames)
if (frame.regionID == id) frame.regionID = -1;
spritesheet->regions.erase(id);
auto& order = spritesheet->regionOrder;
order.erase(std::remove(order.begin(), order.end(), id), order.end());
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_REGIONS), Document::SPRITESHEETS, behavior());
};
auto trim = [&]()
{
if (!spritesheet || selection.empty()) return;
auto behavior = [&]()
{
if (anm2.regions_trim(spritesheetReference, selection))
{
if (reference != -1 && !selection.contains(reference)) reference = *selection.begin();
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_TRIM_REGIONS), Document::SPRITESHEETS, behavior());
};
auto copy = [&]()
{
if (!spritesheet || selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += spritesheet->region_to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (!spritesheet || clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxRegionIdBefore = spritesheet->regions.empty() ? -1 : spritesheet->regions.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_REGIONS));
if (spritesheet->regions_deserialize(clipboard.get(), merge::APPEND, &errorString))
{
if (!spritesheet->regions.empty())
{
auto maxRegionIdAfter = spritesheet->regions.rbegin()->first;
if (maxRegionIdAfter > maxRegionIdBefore)
{
newRegionId = maxRegionIdAfter;
selection = {maxRegionIdAfter};
reference = maxRegionIdAfter;
}
}
document.change(Document::SPRITESHEETS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_DESERIALIZE_REGIONS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_REGIONS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_REGIONS), Document::SPRITESHEETS, behavior());
};
auto add_open = [&]()
{
reference = -1;
editRegion = anm2::Spritesheet::Region{};
propertiesPopup.open();
};
auto properties_open = [&](int id)
{
if (!spritesheet || !spritesheet->regions.contains(id)) return;
reference = id;
propertiesPopup.open();
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Region Context Menu");
if (ImGui::BeginPopup("##Region Context Menu"))
{
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(BASIC_PROPERTIES), nullptr, false, selection.size() == 1))
properties_open(*selection.begin());
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
if (ImGui::MenuItem(localize.get(BASIC_TRIM), nullptr, false, !selection.empty())) trim();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_TRIM_REGIONS));
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_REGIONS_WINDOW), &settings.windowIsRegions))
{
if (!spritesheet)
{
ImGui::TextUnformatted(localize.get(TEXT_SELECT_SPRITESHEET));
ImGui::End();
return;
}
auto childSize = size_without_footer_get();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Regions Child", childSize, ImGuiChildFlags_Borders))
{
auto regionChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
auto rebuild_order = [&]()
{
spritesheet->regionOrder.clear();
spritesheet->regionOrder.reserve(spritesheet->regions.size());
for (auto id : spritesheet->regions | std::views::keys)
spritesheet->regionOrder.push_back(id);
};
if (spritesheet->regionOrder.size() != spritesheet->regions.size())
rebuild_order();
else
{
bool isOrderValid = true;
for (auto id : spritesheet->regionOrder)
if (!spritesheet->regions.contains(id))
{
isOrderValid = false;
break;
}
if (!isOrderValid) rebuild_order();
}
selection.set_index_map(&spritesheet->regionOrder);
selection.start(spritesheet->regionOrder.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : spritesheet->regionOrder)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
auto& order = spritesheet->regionOrder;
if (!order.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(order.begin(), order.end(), current);
int index = it == order.end() ? 0 : (int)std::distance(order.begin(), it);
index = std::clamp(index + delta, 0, (int)order.size() - 1);
int nextId = order[index];
selection = {nextId};
reference = nextId;
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
scrollTargetId = nextId;
}
}
bool isValid = spritesheet->is_valid();
auto& texture = isValid ? spritesheet->texture : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
static std::vector<int> dragDropSelection{};
bool isRegionDragDropSourceSubmitted = false;
for (int i = 0; i < (int)spritesheet->regionOrder.size(); i++)
{
int id = spritesheet->regionOrder[i];
auto regionIt = spritesheet->regions.find(id);
if (regionIt == spritesheet->regions.end()) continue;
auto& region = regionIt->second;
auto isNewRegion = newRegionId == id;
auto nameCStr = region.name.c_str();
auto isSelected = selection.contains(id);
auto isReferenced = id == reference;
ImGui::PushID(id);
scroll_to_item(regionChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Region Child", regionChildSize, ImGuiChildFlags_Borders))
{
auto cursorPos = ImGui::GetCursorPos();
ImGui::SetNextItemSelectionUserData(i);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Region Selectable", isSelected, 0, regionChildSize))
{
reference = id;
document.reference = {document.reference.animationIndex};
frame.reference = -1;
frame.selection.clear();
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) propertiesPopup.open();
auto viewport = ImGui::GetMainViewport();
auto maxPreviewSize = to_vec2(viewport->Size) * 0.5f;
vec2 regionSize = glm::max(region.size, vec2(1.0f));
vec2 previewSize = regionSize;
if (previewSize.x > maxPreviewSize.x || previewSize.y > maxPreviewSize.y)
{
auto scale = glm::min(maxPreviewSize.x / previewSize.x, maxPreviewSize.y / previewSize.y);
previewSize *= scale;
}
vec2 uvMin{};
vec2 uvMax{1.0f, 1.0f};
if (isValid)
{
uvMin = region.crop / vec2(texture.size);
uvMax = (region.crop + region.size) / vec2(texture.size);
}
auto textWidth = ImGui::CalcTextSize(nameCStr).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = previewSize.x + style.ItemSpacing.x + textWidth + tooltipPadding;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip) && !ImGui::IsMouseDragging(ImGuiMouseButton_Left))
{
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
if (ImGui::BeginItemTooltip())
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
auto childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
auto noScrollFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
if (ImGui::BeginChild("##Region Tooltip Image Child", to_imvec2(previewSize), childFlags,
noScrollFlags))
ImGui::ImageWithBg(texture.id, to_imvec2(previewSize), to_imvec2(uvMin), to_imvec2(uvMax), ImVec4(),
tintColor);
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SameLine();
auto infoChildFlags = ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
if (ImGui::BeginChild("##Region Info Tooltip Child", ImVec2(), infoChildFlags, noScrollFlags))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(nameCStr);
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(region.crop.x, region.crop.y))
.c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region.size.x, region.size.y))
.c_str());
if (region.origin == anm2::Spritesheet::Region::CUSTOM)
{
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
.c_str());
}
else
{
StringType originString = LABEL_REGION_ORIGIN_CENTER;
if (region.origin == anm2::Spritesheet::Region::TOP_LEFT)
originString = LABEL_REGION_ORIGIN_TOP_LEFT;
auto originLabel = localize.get(originString);
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_ORIGIN), std::make_format_args(originLabel)).c_str());
}
}
ImGui::EndChild();
ImGui::EndTooltip();
}
}
ImGui::PopStyleVar(2);
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip))
{
isRegionDragDropSourceSubmitted = true;
if (selection.contains(id))
dragDropSelection.assign(selection.begin(), selection.end());
else
dragDropSelection = {id};
ImGui::SetDragDropPayload("Region Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget())
{
if (auto payload = ImGui::AcceptDragDropPayload("Region Drag Drop"))
{
auto payloadIds = (int*)(payload->Data);
int payloadCount = (int)(payload->DataSize / sizeof(int));
std::vector<int> indices{};
indices.reserve(payloadCount);
for (int payloadIndex = 0; payloadIndex < payloadCount; payloadIndex++)
{
int payloadId = payloadIds[payloadIndex];
int index = vector::find_index(spritesheet->regionOrder, payloadId);
if (index != -1) indices.push_back(index);
}
if (!indices.empty())
{
std::sort(indices.begin(), indices.end());
DOCUMENT_EDIT(document, localize.get(EDIT_MOVE_REGIONS), Document::SPRITESHEETS,
vector::move_indices(spritesheet->regionOrder, indices, i));
}
}
ImGui::EndDragDropTarget();
}
auto imageSize = to_imvec2(vec2(regionChildSize.y));
auto aspectRatio = region.size.y != 0.0f ? (float)region.size.x / region.size.y : 1.0f;
if (imageSize.x / imageSize.y > aspectRatio)
imageSize.x = imageSize.y * aspectRatio;
else
imageSize.y = imageSize.x / aspectRatio;
ImGui::SetCursorPos(cursorPos);
ImGui::ImageWithBg(texture.id, imageSize, to_imvec2(uvMin), to_imvec2(uvMax), ImVec4(), tintColor);
ImGui::SetCursorPos(ImVec2(regionChildSize.y + style.ItemSpacing.x,
regionChildSize.y - regionChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::TextUnformatted(nameCStr);
if (isReferenced) ImGui::PopFont();
}
ImGui::EndChild();
if (isNewRegion)
{
ImGui::SetScrollHereY(0.5f);
newRegionId = -1;
}
ImGui::PopID();
}
auto regionDragPayload = ImGui::GetDragDropPayload();
bool isRegionDragActive = regionDragPayload && regionDragPayload->IsDataType("Region Drag Drop");
if (isRegionDragActive && !isRegionDragDropSourceSubmitted && !dragDropSelection.empty() &&
ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceExtern | ImGuiDragDropFlags_SourceNoPreviewTooltip))
{
ImGui::SetDragDropPayload("Region Drag Drop", dragDropSelection.data(),
dragDropSelection.size() * sizeof(int));
ImGui::EndDragDropSource();
}
if (isRegionDragActive && !dragDropSelection.empty())
{
auto mousePos = ImGui::GetIO().MousePos;
auto tooltipOffset = ImVec2(16.0f, 16.0f);
ImGui::SetNextWindowPos(ImVec2(mousePos.x + tooltipOffset.x, mousePos.y + tooltipOffset.y));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::BeginTooltip())
{
for (auto regionId : dragDropSelection)
{
auto dragIt = spritesheet->regions.find(regionId);
if (dragIt == spritesheet->regions.end()) continue;
ImGui::TextUnformatted(dragIt->second.name.c_str());
}
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
}
ImGui::PopStyleVar();
selection.finish();
if (shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto rowOneWidgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) add_open();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_REGION), settings.shortcutAdd);
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), rowOneWidgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_REGIONS), settings.shortcutAdd);
}
ImGui::End();
propertiesPopup.trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label(), &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
if (!spritesheet || (reference != -1 && !spritesheet->regions.contains(reference)))
{
propertiesPopup.close();
ImGui::EndPopup();
return;
}
auto childSize = child_size_get(5);
auto& region = reference == -1 ? editRegion : spritesheet->regions.at(reference);
if (propertiesPopup.isJustOpened)
{
if (!isPreserveEditRegionOnOpen)
editRegion = anm2::Spritesheet::Region{};
isPreserveEditRegionOnOpen = false;
}
if (ImGui::BeginChild("##Child", childSize, ImGuiChildFlags_Borders))
{
const char* originOptions[] = {localize.get(LABEL_REGION_ORIGIN_TOP_LEFT),
localize.get(LABEL_REGION_ORIGIN_CENTER),
localize.get(LABEL_REGION_ORIGIN_CUSTOM)};
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string(localize.get(BASIC_NAME), &region.name);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_ITEM_NAME));
ImGui::DragFloat2(localize.get(BASIC_CROP), value_ptr(region.crop), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.crop));
ImGui::DragFloat2(localize.get(BASIC_SIZE), value_ptr(region.size), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.size));
ImGui::BeginDisabled(region.origin != anm2::Spritesheet::Region::CUSTOM);
ImGui::DragFloat2(localize.get(BASIC_PIVOT), value_ptr(region.pivot), DRAG_SPEED, 0.0f, 0.0f,
math::vec2_format_get(region.pivot));
ImGui::EndDisabled();
if (ImGui::Combo(localize.get(LABEL_REGION_PROPERTIES_ORIGIN), (int*)&region.origin, originOptions,
IM_ARRAYSIZE(originOptions)))
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REGION_PROPERTIES_ORIGIN));
if (region.origin == anm2::Spritesheet::Region::TOP_LEFT)
region.pivot = {};
else if (region.origin == anm2::Spritesheet::Region::ORIGIN_CENTER)
region.pivot = {(int)(region.size.x / 2.0f), (int)(region.size.y / 2.0f)};
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
if (ImGui::Button(reference == -1 ? localize.get(BASIC_ADD) : localize.get(BASIC_CONFIRM), widgetSize))
{
if (reference == -1)
{
auto add = [&]()
{
auto id = map::next_id_get(spritesheet->regions);
spritesheet->regions[id] = region;
spritesheet->regionOrder.push_back(id);
selection = {id};
newRegionId = id;
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_REGION), Document::SPRITESHEETS, add());
}
else
{
auto set = [&]()
{
spritesheet->regions.at(reference) = region;
selection = {reference};
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_REGION_PROPERTIES), Document::SPRITESHEETS, set());
}
frame.reference = -1;
frame.selection.clear();
propertiesPopup.close();
}
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) propertiesPopup.close();
ImGui::EndPopup();
}
propertiesPopup.end();
}
}
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Regions
{
public:
anm2::Spritesheet::Region editRegion{};
int newRegionId{-1};
bool isPreserveEditRegionOnOpen{};
imgui::PopupHelper propertiesPopup{imgui::PopupHelper(LABEL_REGION_PROPERTIES, imgui::POPUP_SMALL_NO_HEIGHT)};
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}
-391
View File
@@ -1,391 +0,0 @@
#include "sounds.hpp"
#include <algorithm>
#include <format>
#include <ranges>
#include <vector>
#include "log.hpp"
#include "path_.hpp"
#include "strings.hpp"
#include "toast.hpp"
using namespace anm2ed::util;
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace glm;
namespace anm2ed::imgui
{
void Sounds::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.sound.reference;
auto& selection = document.sound.selection;
auto style = ImGui::GetStyle();
auto add_open = [&]() { dialog.file_open(Dialog::SOUND_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SOUND_REPLACE); };
auto play = [&](anm2::Sound& sound) { sound.play(); };
auto add = [&](const std::filesystem::path& path)
{
auto behavior = [&]()
{
int id{};
auto pathString = path::to_utf8(path);
if (anm2.sound_add(document.directory_get(), path, id))
{
selection = {id};
newSoundId = id;
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZED), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SOUND_INITIALIZED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED), std::make_format_args(pathString)));
logger.error(std::vformat(localize.get(TOAST_SOUND_INITIALIZE_FAILED, anm2ed::ENGLISH),
std::make_format_args(pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_ADD_SOUND), Document::SOUNDS, behavior());
};
auto remove_unused = [&]()
{
auto unused = anm2.sounds_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
anm2.content.sounds.erase(id);
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SOUNDS), Document::SOUNDS, behavior());
};
auto reload = [&]()
{
auto behavior = [&]()
{
for (auto& id : selection)
{
anm2::Sound& sound = anm2.content.sounds[id];
sound.reload(document.directory_get());
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_RELOAD_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_RELOAD_SOUNDS), Document::SOUNDS, behavior());
};
auto replace = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto& id = *selection.begin();
anm2::Sound& sound = anm2.content.sounds[id];
sound = anm2::Sound(document.directory_get(), path);
auto pathString = path::to_utf8(sound.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SOUND), std::make_format_args(id, pathString)));
logger.info(
std::vformat(localize.get(TOAST_REPLACE_SOUND, anm2ed::ENGLISH), std::make_format_args(id, pathString)));
};
DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SOUND), Document::SOUNDS, behavior());
};
auto open_directory = [&](anm2::Sound& sound)
{
if (sound.path.empty()) return;
std::error_code ec{};
auto absolutePath = std::filesystem::weakly_canonical(document.directory_get() / sound.path, ec);
if (ec) absolutePath = document.directory_get() / sound.path;
auto target = std::filesystem::is_directory(absolutePath) ? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
dialog.file_explorer_open(target);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.sounds[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxSoundIdBefore = anm2.content.sounds.empty() ? -1 : anm2.content.sounds.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(TOAST_SOUNDS_PASTE));
if (anm2.sounds_deserialize(clipboard.get(), document.directory_get(), merge::APPEND, &errorString))
{
if (!anm2.content.sounds.empty())
{
auto maxSoundIdAfter = anm2.content.sounds.rbegin()->first;
if (maxSoundIdAfter > maxSoundIdBefore)
{
newSoundId = maxSoundIdAfter;
selection = {maxSoundIdAfter};
reference = maxSoundIdAfter;
}
}
document.change(Document::SOUNDS);
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SOUNDS_DESERIALIZE_ERROR), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_SOUNDS_DESERIALIZE_ERROR, anm2ed::ENGLISH),
std::make_format_args(errorString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_SOUNDS), Document::SOUNDS, behavior());
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Sound Context Menu");
if (ImGui::BeginPopup("##Sound Context Menu"))
{
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_PLAY), nullptr, false, selection.size() == 1))
play(anm2.content.sounds[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_OPEN_DIRECTORY), nullptr, false, selection.size() == 1))
open_directory(anm2.content.sounds[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
if (ImGui::MenuItem(localize.get(BASIC_RELOAD), nullptr, false, !selection.empty())) reload();
if (ImGui::MenuItem(localize.get(BASIC_REPLACE), nullptr, false, selection.size() == 1)) replace_open();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_SOUNDS_WINDOW), &settings.windowIsSounds))
{
auto childSize = imgui::size_without_footer_get();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Sounds Child", childSize, ImGuiChildFlags_Borders))
{
auto soundChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 2);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
selection.start(anm2.content.sounds.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : anm2.content.sounds | std::views::keys)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
std::vector<int> ids{};
ids.reserve(anm2.content.sounds.size());
for (auto& [id, sound] : anm2.content.sounds)
ids.push_back(id);
if (!ids.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(ids.begin(), ids.end(), current);
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
int nextId = ids[index];
selection = {nextId};
reference = nextId;
scrollTargetId = nextId;
}
}
for (auto& [id, sound] : anm2.content.sounds)
{
auto isNewSound = newSoundId == id;
ImGui::PushID(id);
scroll_to_item(soundChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Sound Child", soundChildSize, ImGuiChildFlags_Borders))
{
auto isSelected = selection.contains(id);
auto cursorPos = ImGui::GetCursorPos();
bool isValid = sound.is_valid();
auto& soundIcon = isValid ? resources.icons[icon::SOUND] : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = path::to_utf8(sound.path);
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Sound Selectable", isSelected, 0, soundChildSize))
{
reference = id;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) play(sound);
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) open_directory(sound);
auto textWidth = ImGui::CalcTextSize(pathString.c_str()).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = textWidth + style.ItemSpacing.x + tooltipPadding;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(pathString.c_str());
ImGui::PopFont();
ImGui::Text("%s: %d", localize.get(BASIC_ID), id);
if (!isValid)
{
ImGui::Spacing();
ImGui::TextWrapped("%s", localize.get(TOOLTIP_SOUND_INVALID));
}
else
{
ImGui::Text("%s", localize.get(TEXT_SOUND_PLAY));
ImGui::Text("%s", localize.get(TEXT_OPEN_DIRECTORY));
}
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
ImGui::SetCursorPos(cursorPos);
auto imageSize = to_imvec2(vec2(soundChildSize.y));
ImGui::ImageWithBg(soundIcon.id, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::SetCursorPos(ImVec2(soundChildSize.y + style.ItemSpacing.x,
soundChildSize.y - soundChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SOUND), std::make_format_args(id, pathString)).c_str());
}
ImGui::EndChild();
if (isNewSound)
{
ImGui::SetScrollHereY(0.5f);
newSoundId = -1;
}
ImGui::PopID();
}
ImGui::PopStyleVar();
selection.finish();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto widgetSize = imgui::widget_size_with_row_get(4);
imgui::shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), widgetSize)) add_open();
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_SOUND_ADD), settings.shortcutAdd);
if (dialog.is_selected(Dialog::SOUND_OPEN))
{
add(dialog.path);
dialog.reset();
}
ImGui::SameLine();
imgui::shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), widgetSize)) remove_unused();
imgui::set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_SOUNDS), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_RELOAD), widgetSize)) reload();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RELOAD_SOUNDS));
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(BASIC_REPLACE), widgetSize)) replace_open();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SOUND));
ImGui::EndDisabled();
if (dialog.is_selected(Dialog::SOUND_REPLACE))
{
replace(dialog.path);
dialog.reset();
}
if (imgui::shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (imgui::shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (imgui::shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (imgui::shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
}
ImGui::End();
}
}
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "dialog.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Sounds
{
int newSoundId{-1};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
};
}
+335 -203
View File
@@ -2,11 +2,13 @@
#include <cmath>
#include <format>
#include <set>
#include <utility>
#include "imgui_.hpp"
#include "actions.hpp"
#include "util/imgui/imgui.hpp"
#include "imgui_internal.h"
#include "math_.hpp"
#include "math.hpp"
#include "strings.hpp"
#include "tool.hpp"
#include "types.hpp"
@@ -20,12 +22,17 @@ namespace anm2ed::imgui
{
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2()) {}
bool SpritesheetEditor::is_focused_get() const { return isFocused; }
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
{
isFocused = false;
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& referenceSpritesheet = document.spritesheet.reference;
auto referenceItemType = static_cast<ItemType>(reference.itemType);
auto& pan = document.editorPan;
auto& zoom = document.editorZoom;
auto& backgroundColor = settings.editorBackgroundColor;
@@ -38,7 +45,8 @@ namespace anm2ed::imgui
auto& zoomStep = settings.inputZoomStep;
auto& isBorder = settings.editorIsBorder;
auto& isTransparent = settings.editorIsTransparent;
auto spritesheet = document.spritesheet_get();
auto spritesheet = anm2.element_get(ElementType::SPRITESHEET, referenceSpritesheet);
auto texture = document.texture_get(referenceSpritesheet);
auto& tool = settings.tool;
auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE];
@@ -79,8 +87,8 @@ namespace anm2ed::imgui
auto fit_view = [&]()
{
if (spritesheet && spritesheet->texture.is_valid())
set_to_rect(zoom, pan, {0, 0, (float)spritesheet->texture.size.x, (float)spritesheet->texture.size.y});
if (texture && texture->is_valid())
set_to_rect(zoom, pan, {0, 0, (float)texture->size.x, (float)texture->size.y});
};
auto zoom_adjust = [&](float delta)
@@ -94,8 +102,12 @@ namespace anm2ed::imgui
auto zoom_in = [&]() { zoom_adjust(zoomStep); };
auto zoom_out = [&]() { zoom_adjust(-zoomStep); };
auto region_get = [&](int id)
{ return spritesheet ? element_child_id_get(*spritesheet, ElementType::REGION, id) : nullptr; };
if (ImGui::Begin(localize.get(LABEL_SPRITESHEET_EDITOR_WINDOW), &settings.windowIsSpritesheetEditor))
{
isFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
auto childSize = ImVec2(imgui::row_widget_width_get(3),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
@@ -196,18 +208,17 @@ namespace anm2ed::imgui
viewport_set();
clear(isTransparent ? vec4(0) : vec4(backgroundColor, 1.0f));
auto frame = document.frame_get();
auto item = document.item_get();
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
if (spritesheet && spritesheet->texture.is_valid())
if (spritesheet && texture && texture->is_valid())
{
auto& texture = spritesheet->texture;
auto transform = transform_get(zoom, pan);
auto spritesheetModel = math::quad_model_get(texture.size);
auto spritesheetModel = math::quad_model_get(texture->size);
auto spritesheetTransform = transform * spritesheetModel;
texture_render(shaderTexture, texture.id, spritesheetTransform);
texture_render(shaderTexture, texture->id, spritesheetTransform);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
@@ -217,32 +228,29 @@ namespace anm2ed::imgui
if (hoveredRegionId != -1)
{
auto regionIt = spritesheet->regions.find(hoveredRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(hoveredRegionId))
{
auto& region = regionIt->second;
auto cropModel = math::quad_model_get(region.size, region.crop);
auto cropModel = math::quad_model_get(region->size, region->crop);
auto cropTransform = transform * cropModel;
rect_fill_render(shaderLine, cropTransform, cropModel, vec4(1.0f, 1.0f, 1.0f, 0.5f));
}
}
auto layerIt = anm2.content.layers.find(reference.itemID);
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID);
bool isReferenceLayerOnSpritesheet =
frame && reference.itemID > -1 && layerIt != anm2.content.layers.end() &&
layerIt->second.spritesheetID == referenceSpritesheet;
frame && reference.itemID > -1 && layer && layer->spritesheetId == referenceSpritesheet;
int highlightedRegionId = -1;
if (isReferenceLayerOnSpritesheet && frame->regionID != -1 && spritesheet->regions.contains(frame->regionID))
if (isReferenceLayerOnSpritesheet && frame->regionId != -1 && region_get(frame->regionId))
{
highlightedRegionId = frame->regionID;
highlightedRegionId = frame->regionId;
}
else if (regionReference != -1 && spritesheet->regions.contains(regionReference))
else if (regionReference != -1 && region_get(regionReference))
{
highlightedRegionId = regionReference;
}
auto draw_region_rect = [&](anm2::Spritesheet::Region& region, vec4 regionColor)
auto draw_region_rect = [&](Element& region, vec4 regionColor)
{
auto cropModel = math::quad_model_get(region.size, region.crop);
auto cropTransform = transform * cropModel;
@@ -250,8 +258,10 @@ namespace anm2ed::imgui
BORDER_DASH_OFFSET);
};
for (auto& [id, region] : spritesheet->regions)
for (auto& region : spritesheet->children)
{
if (region.type != ElementType::REGION) continue;
auto id = region.id;
if (id == highlightedRegionId) continue;
draw_region_rect(region, color::WHITE);
@@ -262,26 +272,25 @@ namespace anm2ed::imgui
if (highlightedRegionId != -1)
{
auto regionIt = spritesheet->regions.find(highlightedRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(highlightedRegionId))
{
auto& region = regionIt->second;
draw_region_rect(region, color::RED);
draw_region_rect(*region, color::RED);
auto pivotTransform =
transform * math::quad_model_get(PIVOT_SIZE, region.crop + region.pivot, PIVOT_SIZE * 0.5f);
transform * math::quad_model_get(PIVOT_SIZE, region->crop + region->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
}
}
bool isFrameOnSpritesheet = isReferenceLayerOnSpritesheet;
if (isFrameOnSpritesheet && frame->regionID == -1)
if (isFrameOnSpritesheet && frame->regionId == -1)
{
auto frameModel = math::quad_model_get(frame->size, frame->crop);
auto frameTransform = transform * frameModel;
rect_render(shaderLine, frameTransform, frameModel, color::RED);
auto pivotTransform = transform * math::quad_model_get(PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
auto pivotTransform =
transform * math::quad_model_get(PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
}
}
@@ -293,7 +302,7 @@ namespace anm2ed::imgui
render_checker_background(drawList, min, max, -size * 0.5f - checkerPan, CHECKER_SIZE);
else
drawList->AddRectFilled(min, max, ImGui::GetColorU32(to_imvec4(vec4(backgroundColor, 1.0f))));
ImGui::Image(texture, to_imvec2(size));
ImGui::Image(this->texture, to_imvec2(size));
if (ImGui::IsItemHovered())
{
@@ -330,8 +339,8 @@ 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 isBegin = isMouseClicked || isKeyJustPressed;
auto isDuring = isMouseDown || isKeyDown;
@@ -339,11 +348,12 @@ namespace anm2ed::imgui
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto layerIt = anm2.content.layers.find(reference.itemID);
auto frame =
anm2.element_get(reference.animationIndex, referenceItemType, reference.frameIndex, reference.itemID);
auto item = anm2.element_get(reference.animationIndex, referenceItemType, reference.itemID);
auto layer = anm2.element_get(ElementType::LAYER_ELEMENT, reference.itemID);
bool isReferenceLayerOnSpritesheet =
frame && reference.itemID > -1 && layerIt != anm2.content.layers.end() &&
layerIt->second.spritesheetID == referenceSpritesheet;
frame && reference.itemID > -1 && layer && layer->spritesheetId == referenceSpritesheet;
auto useTool = tool;
auto step = isMod ? STEP_FAST : STEP;
auto stepX = isGridSnap ? step * gridSize.x : step;
@@ -373,31 +383,212 @@ namespace anm2ed::imgui
return std::pair{minPoint, maxPoint};
};
auto frame_change_apply = [&](anm2::FrameChange frameChange, anm2::ChangeType changeType = anm2::ADJUST)
{ item->frames_change(frameChange, reference.itemType, changeType, frames); };
auto clamp_vec2_to_int = [](const vec2& value)
{ return vec2(ivec2(value)); };
auto clamp_vec2_to_int = [](const vec2& value) { return vec2(ivec2(value)); };
auto snapshot_push = [&](StringType messageType)
{
auto message = std::string(localize.get(messageType));
manager.command_push(
{manager.selected, [message](Manager&, Document& document) { document.snapshot(message); }});
};
auto document_change_push = [&](Document::ChangeType changeType)
{
manager.command_push({manager.selected,
[changeType](Manager&, Document& document) { document.anm2_change(changeType); }});
};
auto frame_change_apply = [&](FrameChange frameChange, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
std::set<int> queuedFrames(frames.begin(), frames.end());
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 frame_change_from_current_apply = [&](auto frameChangeGet, ChangeType changeType = ChangeType::ADJUST)
{
auto queuedReference = reference;
auto queuedReferenceItemType = referenceItemType;
std::set<int> queuedFrames(frames.begin(), frames.end());
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto item = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.itemID);
auto frame = document.anm2.element_get(queuedReference.animationIndex,
queuedReferenceItemType,
queuedReference.frameIndex,
queuedReference.itemID);
if (!item || !frame) return;
frames_change(*item, frameChangeGet(*frame), queuedReferenceItemType, changeType,
queuedFrames);
}});
};
auto frame_crop_normalize_apply = [&](bool isSnap, ivec2 snapGridSize, ivec2 snapGridOffset)
{
frame_change_from_current_apply(
[=](const Element& frame)
{
auto minPoint = glm::min(frame.crop, frame.crop + frame.size);
auto maxPoint = glm::max(frame.crop, frame.crop + frame.size);
if (isSnap)
{
if (snapGridSize.x != 0)
{
auto offsetX = (float)snapGridOffset.x;
auto sizeX = (float)snapGridSize.x;
minPoint.x = std::floor((minPoint.x - offsetX) / sizeX) * sizeX + offsetX;
maxPoint.x = std::ceil((maxPoint.x - offsetX) / sizeX) * sizeX + offsetX;
}
if (snapGridSize.y != 0)
{
auto offsetY = (float)snapGridOffset.y;
auto sizeY = (float)snapGridSize.y;
minPoint.y = std::floor((minPoint.y - offsetY) / sizeY) * sizeY + offsetY;
maxPoint.y = std::ceil((maxPoint.y - offsetY) / sizeY) * sizeY + offsetY;
}
}
return FrameChange{.cropX = minPoint.x,
.cropY = minPoint.y,
.sizeX = maxPoint.x - minPoint.x,
.sizeY = maxPoint.y - minPoint.y};
});
};
auto region_update = [&](int id, auto update)
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto spritesheet = document.anm2.element_get(ElementType::SPRITESHEET,
queuedSpritesheet);
if (!spritesheet) return;
auto region = element_child_id_get(*spritesheet, ElementType::REGION, id);
if (!region) return;
update(*region);
}});
};
auto region_update_all = [&](auto update)
{
auto queuedSpritesheet = referenceSpritesheet;
std::set<int> queuedSelection(regionSelection.begin(), regionSelection.end());
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto spritesheet = document.anm2.element_get(ElementType::SPRITESHEET,
queuedSpritesheet);
if (!spritesheet) return;
for (auto id : queuedSelection)
{
auto region = element_child_id_get(*spritesheet, ElementType::REGION, id);
if (!region) continue;
update(*region);
}
}});
};
auto region_set_all = [&](const vec2& crop, const vec2& size)
{
if (!spritesheet) return;
for (auto id : regionSelection)
{
auto it = spritesheet->regions.find(id);
if (it == spritesheet->regions.end()) continue;
it->second.crop = clamp_vec2_to_int(crop);
it->second.size = clamp_vec2_to_int(size);
}
auto queuedCrop = clamp_vec2_to_int(crop);
auto queuedSize = clamp_vec2_to_int(size);
region_update_all(
[=](Element& region)
{
region.crop = queuedCrop;
region.size = queuedSize;
});
};
auto region_offset_all = [&](const vec2& delta)
{
if (!spritesheet) return;
for (auto id : regionSelection)
{
auto it = spritesheet->regions.find(id);
if (it == spritesheet->regions.end()) continue;
it->second.crop = clamp_vec2_to_int(it->second.crop + delta);
it->second.size = clamp_vec2_to_int(it->second.size);
}
region_update_all(
[=](Element& region)
{
region.crop = clamp_vec2_to_int(region.crop + delta);
region.size = clamp_vec2_to_int(region.size);
});
};
auto region_crop_normalize_all = [&](bool isSnap, ivec2 snapGridSize, ivec2 snapGridOffset)
{
region_update_all(
[=](Element& region)
{
auto minPoint = glm::min(region.crop, region.crop + region.size);
auto maxPoint = glm::max(region.crop, region.crop + region.size);
if (isSnap)
{
if (snapGridSize.x != 0)
{
auto offsetX = (float)snapGridOffset.x;
auto sizeX = (float)snapGridSize.x;
minPoint.x = std::floor((minPoint.x - offsetX) / sizeX) * sizeX + offsetX;
maxPoint.x = std::ceil((maxPoint.x - offsetX) / sizeX) * sizeX + offsetX;
}
if (snapGridSize.y != 0)
{
auto offsetY = (float)snapGridOffset.y;
auto sizeY = (float)snapGridSize.y;
minPoint.y = std::floor((minPoint.y - offsetY) / sizeY) * sizeY + offsetY;
maxPoint.y = std::ceil((maxPoint.y - offsetY) / sizeY) * sizeY + offsetY;
}
}
region.crop = clamp_vec2_to_int(minPoint);
region.size = clamp_vec2_to_int(maxPoint - minPoint);
});
};
auto texture_line_apply = [&](ivec2 start, ivec2 end, vec4 color)
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
auto texture = document.texture_get(queuedSpritesheet);
if (!texture) return;
texture->pixel_line(start, end, color);
}});
};
auto texture_change_push = [&]()
{
auto queuedSpritesheet = referenceSpritesheet;
manager.command_push({manager.selected,
[=](Manager&, Document& document) { document.texture_change(queuedSpritesheet); }});
};
auto region_selection_set = [&](int id)
{
manager.command_push({manager.selected,
[=](Manager&, Document& document)
{
document.region.reference = id;
document.region.selection = {id};
}});
};
auto region_pivot_set = [&](int id, vec2 pivot)
{
auto queuedPivot = clamp_vec2_to_int(pivot);
region_update(id,
[=](Element& region)
{
region.origin = Origin::CUSTOM;
region.pivot = queuedPivot;
});
};
auto region_pivot_offset = [&](int id, vec2 delta)
{
region_update(id,
[=](Element& region)
{
region.origin = Origin::CUSTOM;
region.pivot = clamp_vec2_to_int(region.pivot + delta);
});
};
if (isMouseMiddleDown) useTool = tool::PAN;
@@ -408,16 +599,17 @@ namespace anm2ed::imgui
hoveredRegionId = -1;
if (useTool == tool::PAN && spritesheet && spritesheet->texture.is_valid() && isMouseOverCanvas)
if (useTool == tool::PAN && spritesheet && texture && texture->is_valid() && isMouseOverCanvas)
{
for (auto& [id, region] : spritesheet->regions)
for (auto& region : spritesheet->children)
{
if (region.type != ElementType::REGION) continue;
auto minPoint = glm::min(region.crop, region.crop + region.size);
auto maxPoint = glm::max(region.crop, region.crop + region.size);
if (hoverMousePos.x >= minPoint.x && hoverMousePos.x <= maxPoint.x && hoverMousePos.y >= minPoint.y &&
hoverMousePos.y <= maxPoint.y)
{
hoveredRegionId = id;
hoveredRegionId = region.id;
break;
}
}
@@ -428,12 +620,12 @@ namespace anm2ed::imgui
bool isAreaAllowed = areaType == tool::ALL || areaType == tool::SPRITESHEET_EDITOR;
bool isFrameRequired =
!(useTool == tool::PAN || useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER);
bool isRegionInUse = frame && frame->regionID != -1 && (useTool == tool::CROP || useTool == tool::MOVE);
bool isRegionInUse = frame && frame->regionId != -1 && (useTool == tool::CROP || useTool == tool::MOVE);
bool isFrameAvailable = !isFrameRequired || (frame && !isRegionInUse) ||
(useTool == tool::CROP && !frame && !regionSelection.empty()) ||
(useTool == tool::MOVE && !frame && regionReference != -1);
bool isSpritesheetRequired = useTool == tool::DRAW || useTool == tool::ERASE || useTool == tool::COLOR_PICKER;
bool isSpritesheetAvailable = !isSpritesheetRequired || (spritesheet && spritesheet->texture.is_valid());
bool isSpritesheetAvailable = !isSpritesheetRequired || (texture && texture->is_valid());
auto cursor = (isAreaAllowed && isFrameAvailable && isSpritesheetAvailable) ? toolInfo.cursor
: ImGuiMouseCursor_NotAllowed;
ImGui::SetMouseCursor(cursor);
@@ -444,29 +636,23 @@ namespace anm2ed::imgui
case tool::PAN:
if (isMouseLeftClicked && hoveredRegionId != -1)
{
regionReference = hoveredRegionId;
regionSelection = {hoveredRegionId};
region_selection_set(hoveredRegionId);
if (isReferenceLayerOnSpritesheet)
{
DOCUMENT_EDIT(document, localize.get(EDIT_FRAME_REGION), Document::FRAMES,
{
anm2::FrameChange change{};
change.regionID = hoveredRegionId;
if (spritesheet)
{
auto regionIt = spritesheet->regions.find(hoveredRegionId);
if (regionIt != spritesheet->regions.end())
{
change.cropX = regionIt->second.crop.x;
change.cropY = regionIt->second.crop.y;
change.sizeX = regionIt->second.size.x;
change.sizeY = regionIt->second.size.y;
change.pivotX = regionIt->second.pivot.x;
change.pivotY = regionIt->second.pivot.y;
}
}
frame_change_apply(change);
});
snapshot_push(EDIT_FRAME_REGION);
FrameChange change{};
change.regionId = hoveredRegionId;
if (auto region = region_get(hoveredRegionId))
{
change.cropX = region->crop.x;
change.cropY = region->crop.y;
change.sizeX = region->size.x;
change.sizeY = region->size.y;
change.pivotX = region->pivot.x;
change.pivotY = region->pivot.y;
}
frame_change_apply(change);
document_change_push(Document::FRAMES);
}
}
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
@@ -476,48 +662,48 @@ namespace anm2ed::imgui
if (!frame && regionReference != -1)
{
if (!spritesheet) break;
auto regionIt = spritesheet->regions.find(regionReference);
if (regionIt == spritesheet->regions.end()) break;
auto region = region_get(regionReference);
if (!region) break;
auto& region = regionIt->second;
if (isBegin) document.snapshot(localize.get(EDIT_REGION_MOVE));
bool isPivotEdited = isMouseDown || isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
if (isPivotEdited) region.origin = anm2::Spritesheet::Region::CUSTOM;
if (isMouseDown) region.pivot = ivec2(mousePos) - ivec2(region.crop);
if (isLeftPressed) region.pivot.x -= step;
if (isRightPressed) region.pivot.x += step;
if (isUpPressed) region.pivot.y -= step;
if (isDownPressed) region.pivot.y += step;
if (isBegin) snapshot_push(EDIT_REGION_MOVE);
if (isMouseDown) region_pivot_set(regionReference, ivec2(mousePos) - ivec2(region->crop));
if (isLeftPressed) region_pivot_offset(regionReference, vec2(-step, 0));
if (isRightPressed) region_pivot_offset(regionReference, vec2(step, 0));
if (isUpPressed) region_pivot_offset(regionReference, vec2(0, -step));
if (isDownPressed) region_pivot_offset(regionReference, vec2(0, step));
if (isDuring)
{
if (ImGui::BeginTooltip())
{
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region->pivot.x, region->pivot.y))
.c_str());
ImGui::EndTooltip();
}
}
if (isEnd) document.change(Document::SPRITESHEETS);
if (isEnd) document_change_push(Document::SPRITESHEETS);
break;
}
if (!item || frames.empty()) break;
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_PIVOT));
if (isBegin) snapshot_push(EDIT_FRAME_PIVOT);
if (isMouseDown)
{
frame->crop = ivec2(frame->crop);
frame_change_apply(
{.pivotX = (int)(mousePos.x - frame->crop.x), .pivotY = (int)(mousePos.y - frame->crop.y)});
auto frameCrop = ivec2(frame->crop);
frame_change_apply({.pivotX = (int)(mousePos.x - frameCrop.x),
.pivotY = (int)(mousePos.y - frameCrop.y),
.cropX = frameCrop.x,
.cropY = frameCrop.y});
}
if (isLeftPressed) frame_change_apply({.pivotX = step}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.pivotX = step}, anm2::ADD);
if (isUpPressed) frame_change_apply({.pivotY = step}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.pivotY = step}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.pivotX = step}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.pivotX = step}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.pivotY = step}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.pivotY = step}, ChangeType::ADD);
frame_change_apply({.pivotX = (int)frame->pivot.x, .pivotY = (int)frame->pivot.y});
frame_change_from_current_apply(
[](const Element& frame) { return FrameChange{.pivotX = (int)frame.pivot.x, .pivotY = (int)frame.pivot.y}; });
if (isDuring)
{
@@ -530,14 +716,14 @@ namespace anm2ed::imgui
}
}
if (isEnd) document.change(Document::FRAMES);
if (isEnd) document_change_push(Document::FRAMES);
break;
case tool::CROP:
if (isRegionInUse) break;
if (frames.empty())
{
if (!spritesheet || regionSelection.empty()) break;
if (isBegin) document.snapshot(localize.get(EDIT_REGION_CROP));
if (isBegin) snapshot_push(EDIT_REGION_CROP);
if (isMouseClicked)
{
@@ -557,49 +743,29 @@ namespace anm2ed::imgui
if (isDuring)
{
if (!isMouseDown)
{
for (auto id : regionSelection)
{
auto it = spritesheet->regions.find(id);
if (it == spritesheet->regions.end()) continue;
auto& region = it->second;
auto minPoint = glm::min(region.crop, region.crop + region.size);
auto maxPoint = glm::max(region.crop, region.crop + region.size);
region.crop = clamp_vec2_to_int(minPoint);
region.size = clamp_vec2_to_int(maxPoint - minPoint);
if (isGridSnap)
{
auto [snapMin, snapMax] = snap_rect(region.crop, region.crop + region.size);
region.crop = clamp_vec2_to_int(snapMin);
region.size = clamp_vec2_to_int(snapMax - snapMin);
}
}
}
region_crop_normalize_all(isGridSnap, gridSize, gridOffset);
if (ImGui::BeginTooltip())
{
auto it = spritesheet->regions.find(*regionSelection.begin());
if (it != spritesheet->regions.end())
if (auto region = region_get(*regionSelection.begin()))
{
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_CROP),
std::make_format_args(it->second.crop.x, it->second.crop.y))
.c_str());
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_SIZE),
std::make_format_args(it->second.size.x, it->second.size.y))
.c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(region->crop.x, region->crop.y))
.c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region->size.x, region->size.y))
.c_str());
}
ImGui::EndTooltip();
}
}
if (isEnd) document.change(Document::SPRITESHEETS);
if (isEnd) document_change_push(Document::SPRITESHEETS);
break;
}
if (!item) break;
if (isBegin) document.snapshot(localize.get(EDIT_FRAME_CROP));
if (isBegin) snapshot_push(EDIT_FRAME_CROP);
if (isMouseClicked)
{
@@ -614,10 +780,10 @@ namespace anm2ed::imgui
.sizeX = maxPoint.x - minPoint.x,
.sizeY = maxPoint.y - minPoint.y});
}
if (isLeftPressed) frame_change_apply({.cropX = stepX}, anm2::SUBTRACT);
if (isRightPressed) frame_change_apply({.cropX = stepX}, anm2::ADD);
if (isUpPressed) frame_change_apply({.cropY = stepY}, anm2::SUBTRACT);
if (isDownPressed) frame_change_apply({.cropY = stepY}, anm2::ADD);
if (isLeftPressed) frame_change_apply({.cropX = stepX}, ChangeType::SUBTRACT);
if (isRightPressed) frame_change_apply({.cropX = stepX}, ChangeType::ADD);
if (isUpPressed) frame_change_apply({.cropY = stepY}, ChangeType::SUBTRACT);
if (isDownPressed) frame_change_apply({.cropY = stepY}, ChangeType::ADD);
frame_change_apply(
{.cropX = frame->crop.x, .cropY = frame->crop.y, .sizeX = frame->size.x, .sizeY = frame->size.y});
@@ -626,23 +792,7 @@ namespace anm2ed::imgui
{
if (!isMouseDown)
{
auto minPoint = glm::min(frame->crop, frame->crop + frame->size);
auto maxPoint = glm::max(frame->crop, frame->crop + frame->size);
frame_change_apply({.cropX = minPoint.x,
.cropY = minPoint.y,
.sizeX = maxPoint.x - minPoint.x,
.sizeY = maxPoint.y - minPoint.y});
if (isGridSnap)
{
auto [snapMin, snapMax] = snap_rect(frame->crop, frame->crop + frame->size);
frame_change_apply({.cropX = snapMin.x,
.cropY = snapMin.y,
.sizeX = snapMax.x - snapMin.x,
.sizeY = snapMax.y - snapMin.y});
}
frame_crop_normalize_apply(isGridSnap, gridSize, gridOffset);
}
if (ImGui::BeginTooltip())
{
@@ -655,27 +805,24 @@ namespace anm2ed::imgui
ImGui::EndTooltip();
}
}
if (isEnd) document.change(Document::FRAMES);
if (isEnd) document_change_push(Document::FRAMES);
break;
case tool::DRAW:
case tool::ERASE:
{
if (!spritesheet) break;
if (!texture) break;
auto color = useTool == tool::DRAW ? toolColor : vec4();
if (isMouseClicked)
document.snapshot(useTool == tool::DRAW ? localize.get(EDIT_DRAW) : localize.get(EDIT_ERASE));
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased)
{
document.change(Document::SPRITESHEETS);
}
snapshot_push(useTool == tool::DRAW ? EDIT_DRAW : EDIT_ERASE);
if (isMouseDown) texture_line_apply(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased) texture_change_push();
break;
}
case tool::COLOR_PICKER:
{
if (spritesheet && isDuring)
if (texture && isDuring)
{
toolColor = spritesheet->texture.pixel_read(mousePos);
toolColor = texture->pixel_read(mousePos);
if (ImGui::BeginTooltip())
{
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
@@ -696,31 +843,31 @@ namespace anm2ed::imgui
if (tool == tool::PAN && hoveredRegionId != -1 && spritesheet)
{
auto regionIt = spritesheet->regions.find(hoveredRegionId);
if (regionIt != spritesheet->regions.end())
if (auto region = region_get(hoveredRegionId))
{
if (ImGui::BeginTooltip())
{
auto& region = regionIt->second;
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(region.name.c_str());
ImGui::TextUnformatted(region->name.c_str());
ImGui::PopFont();
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_ID), std::make_format_args(hoveredRegionId)).c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(region.crop.x, region.crop.y)).c_str());
std::vformat(localize.get(FORMAT_CROP), std::make_format_args(region->crop.x, region->crop.y))
.c_str());
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region.size.x, region.size.y)).c_str());
if (region.origin == anm2::Spritesheet::Region::CUSTOM)
std::vformat(localize.get(FORMAT_SIZE), std::make_format_args(region->size.x, region->size.y))
.c_str());
if (region->origin == Origin::CUSTOM)
{
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region.pivot.x, region.pivot.y))
std::vformat(localize.get(FORMAT_PIVOT), std::make_format_args(region->pivot.x, region->pivot.y))
.c_str());
}
else
{
StringType originString = LABEL_REGION_ORIGIN_CENTER;
if (region.origin == anm2::Spritesheet::Region::TOP_LEFT) originString = LABEL_REGION_ORIGIN_TOP_LEFT;
if (region->origin == Origin::TOP_LEFT) originString = LABEL_REGION_ORIGIN_TOP_LEFT;
auto originLabel = localize.get(originString);
ImGui::TextUnformatted(
std::vformat(localize.get(FORMAT_ORIGIN), std::make_format_args(originLabel)).c_str());
@@ -766,8 +913,7 @@ namespace anm2ed::imgui
if (mouseWheel != 0 || isZoomIn || isZoomOut)
{
auto focus = mouseWheel != 0 ? vec2(mousePos) : vec2();
if (auto spritesheet = document.spritesheet_get(); spritesheet && mouseWheel == 0)
focus = spritesheet->texture.size / 2;
if (texture && mouseWheel == 0) focus = texture->size / 2;
auto previousZoom = zoom;
zoom_set(zoom, pan, focus, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
@@ -776,31 +922,17 @@ namespace anm2ed::imgui
}
}
if (tool == tool::PAN &&
ImGui::BeginPopupContextWindow("##Spritesheet Editor 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,
spritesheet && spritesheet->texture.is_valid()))
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 texture && texture->is_valid(); }, 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("##Spritesheet Editor Context Menu", actions, settings);
}
if (!document.isSpritesheetEditorSet)
+2
View File
@@ -17,10 +17,12 @@ namespace anm2ed::imgui
float checkerSyncZoom{};
bool isCheckerPanInitialized{};
bool hasPendingZoomPanAdjust{};
bool isFocused{};
int hoveredRegionId{-1};
public:
SpritesheetEditor();
bool is_focused_get() const;
void update(Manager&, Settings&, Resources&);
};
}
-709
View File
@@ -1,709 +0,0 @@
#include "spritesheets.hpp"
#include <algorithm>
#include <ranges>
#include <vector>
#include <filesystem>
#include <format>
#include <functional>
#include "document.hpp"
#include "log.hpp"
#include "path_.hpp"
#include "strings.hpp"
#include "toast.hpp"
#include "working_directory.hpp"
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
static constexpr auto PADDING_MAX = 100;
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& selection = document.spritesheet.selection;
auto& reference = document.spritesheet.reference;
auto& region = document.region;
auto style = ImGui::GetStyle();
std::function<void()> pack{};
auto add_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_OPEN); };
auto replace_open = [&]() { dialog.file_open(Dialog::SPRITESHEET_REPLACE); };
auto set_file_path_open = [&]() { dialog.file_save(Dialog::SPRITESHEET_PATH_SET); };
auto merge_open = [&]()
{
if (selection.size() <= 1) return;
mergeSelection = selection;
mergePopup.open();
};
auto pack_open = [&]()
{
if (selection.size() != 1) return;
auto id = *selection.begin();
if (!anm2.content.spritesheets.contains(id)) return;
if (anm2.content.spritesheets.at(id).regions.empty()) return;
packId = id;
packPopup.open();
};
auto add = [&](const std::filesystem::path& path)
{
if (path.empty()) return;
document.spritesheet_add(path);
newSpritesheetId = document.spritesheet.reference;
};
auto remove_unused = [&]()
{
auto unused = anm2.spritesheets_unused();
if (unused.empty()) return;
auto behavior = [&]()
{
for (auto& id : unused)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REMOVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
anm2.content.spritesheets.erase(id);
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_REMOVE_UNUSED_SPRITESHEETS), Document::ALL, behavior());
};
auto reload = [&]()
{
if (selection.empty()) return;
auto behavior = [&]()
{
for (auto& id : selection)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get());
document.spritesheet_hash_set_saved(id);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_RELOAD_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_RELOAD_SPRITESHEETS), Document::SPRITESHEETS, behavior());
};
auto replace = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto& id = *selection.begin();
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get(), path);
document.spritesheet_hash_set_saved(id);
auto pathString = path::to_utf8(spritesheet.path);
toasts.push(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_REPLACE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
};
DOCUMENT_EDIT(document, localize.get(EDIT_REPLACE_SPRITESHEET), Document::SPRITESHEETS, behavior());
};
auto set_file_path = [&](const std::filesystem::path& path)
{
if (selection.size() != 1 || path.empty()) return;
auto behavior = [&]()
{
auto id = *selection.begin();
if (!anm2.content.spritesheets.contains(id)) return;
WorkingDirectory workingDirectory(document.directory_get());
anm2.content.spritesheets[id].path = path::make_relative(path);
};
DOCUMENT_EDIT(document, localize.get(EDIT_SET_SPRITESHEET_FILE_PATH), Document::SPRITESHEETS, behavior());
};
auto save = [&](const std::set<int>& ids)
{
if (ids.empty()) return;
for (auto& id : ids)
{
if (!anm2.content.spritesheets.contains(id)) continue;
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
auto pathString = path::to_utf8(spritesheet.path);
if (spritesheet.save(document.directory_get()))
{
document.spritesheet_hash_set_saved(id);
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET), std::make_format_args(id, pathString)));
logger.info(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
else
{
toasts.push(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED), std::make_format_args(id, pathString)));
logger.error(std::vformat(localize.get(TOAST_SAVE_SPRITESHEET_FAILED, anm2ed::ENGLISH),
std::make_format_args(id, pathString)));
}
}
};
auto save_open = [&]()
{
if (selection.empty()) return;
if (settings.fileIsWarnOverwrite)
{
saveSelection = selection;
overwritePopup.open();
}
else
{
save(selection);
}
};
auto merge = [&]()
{
if (mergeSelection.size() <= 1) return;
auto behavior = [&]()
{
auto baseID = *mergeSelection.begin();
if (anm2.spritesheets_merge(mergeSelection, (anm2::SpritesheetMergeOrigin)settings.mergeSpritesheetsOrigin,
settings.mergeSpritesheetsIsMakeRegions,
settings.mergeSpritesheetsIsMakePrimaryRegion,
(origin::Type)settings.mergeSpritesheetsRegionOrigin))
{
selection = {baseID};
reference = baseID;
region.reference = -1;
region.selection.clear();
toasts.push(localize.get(TOAST_MERGE_SPRITESHEETS));
logger.info(localize.get(TOAST_MERGE_SPRITESHEETS, anm2ed::ENGLISH));
}
else
{
toasts.push(localize.get(TOAST_MERGE_SPRITESHEETS_FAILED));
logger.error(localize.get(TOAST_MERGE_SPRITESHEETS_FAILED, anm2ed::ENGLISH));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_MERGE_SPRITESHEETS), Document::ALL, behavior());
};
pack = [&]()
{
int id = packId != -1 ? packId : (selection.size() == 1 ? *selection.begin() : -1);
if (id == -1) return;
if (!anm2.content.spritesheets.contains(id)) return;
if (anm2.content.spritesheets.at(id).regions.empty()) return;
auto behavior = [&]()
{
auto padding = std::max(0, settings.packPadding);
if (anm2.spritesheet_pack(id, padding))
{
toasts.push(localize.get(TOAST_PACK_SPRITESHEET));
logger.info(localize.get(TOAST_PACK_SPRITESHEET, anm2ed::ENGLISH));
}
else
{
toasts.push(localize.get(TOAST_PACK_SPRITESHEET_FAILED));
logger.error(localize.get(TOAST_PACK_SPRITESHEET_FAILED, anm2ed::ENGLISH));
}
};
DOCUMENT_EDIT(document, localize.get(EDIT_PACK_SPRITESHEET), Document::SPRITESHEETS, behavior());
};
auto open_directory = [&](anm2::Spritesheet& spritesheet)
{
if (spritesheet.path.empty()) return;
std::error_code ec{};
auto absolutePath = std::filesystem::weakly_canonical(document.directory_get() / spritesheet.path, ec);
if (ec) absolutePath = document.directory_get() / spritesheet.path;
auto target = std::filesystem::is_directory(absolutePath) ? absolutePath
: std::filesystem::is_directory(absolutePath.parent_path()) ? absolutePath.parent_path()
: document.directory_get();
dialog.file_explorer_open(target);
};
auto copy = [&]()
{
if (selection.empty()) return;
std::string clipboardText{};
for (auto& id : selection)
clipboardText += anm2.content.spritesheets[id].to_string(id);
clipboard.set(clipboardText);
};
auto paste = [&]()
{
if (clipboard.is_empty()) return;
auto behavior = [&]()
{
auto maxSpritesheetIdBefore =
anm2.content.spritesheets.empty() ? -1 : anm2.content.spritesheets.rbegin()->first;
std::string errorString{};
document.snapshot(localize.get(EDIT_PASTE_SPRITESHEETS));
if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get(), merge::APPEND, &errorString))
{
if (!anm2.content.spritesheets.empty())
{
auto maxSpritesheetIdAfter = anm2.content.spritesheets.rbegin()->first;
if (maxSpritesheetIdAfter > maxSpritesheetIdBefore)
{
newSpritesheetId = maxSpritesheetIdAfter;
selection = {maxSpritesheetIdAfter};
reference = maxSpritesheetIdAfter;
region.reference = -1;
region.selection.clear();
}
}
document.change(Document::SPRITESHEETS);
}
else
{
toasts.push(
std::vformat(localize.get(TOAST_DESERIALIZE_SPRITESHEETS_FAILED), std::make_format_args(errorString)));
logger.error(std::vformat(localize.get(TOAST_DESERIALIZE_SPRITESHEETS_FAILED, anm2ed::ENGLISH),
std::make_format_args(errorString)));
};
};
DOCUMENT_EDIT(document, localize.get(EDIT_PASTE_SPRITESHEETS), Document::SPRITESHEETS, behavior());
};
auto context_menu = [&]()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlappedByWindow) &&
ImGui::IsMouseClicked(ImGuiMouseButton_Right))
ImGui::OpenPopup("##Spritesheet Context Menu");
if (ImGui::BeginPopup("##Spritesheet Context Menu"))
{
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(BASIC_OPEN_DIRECTORY), nullptr, false, selection.size() == 1))
open_directory(anm2.content.spritesheets[*selection.begin()]);
if (ImGui::MenuItem(localize.get(BASIC_SET_FILE_PATH), nullptr, false, selection.size() == 1))
set_file_path_open();
if (ImGui::MenuItem(localize.get(BASIC_ADD), settings.shortcutAdd.c_str())) add_open();
if (ImGui::MenuItem(localize.get(BASIC_REMOVE_UNUSED), settings.shortcutRemove.c_str())) remove_unused();
bool isPackable = selection.size() == 1 && anm2.content.spritesheets.contains(*selection.begin()) &&
!anm2.content.spritesheets.at(*selection.begin()).regions.empty();
if (ImGui::MenuItem(localize.get(BASIC_RELOAD), nullptr, false, !selection.empty())) reload();
if (ImGui::MenuItem(localize.get(BASIC_REPLACE), nullptr, false, selection.size() == 1)) replace_open();
if (ImGui::MenuItem(localize.get(BASIC_MERGE), settings.shortcutMerge.c_str(), false, selection.size() > 1))
merge_open();
if (ImGui::MenuItem(localize.get(BASIC_PACK), nullptr, false, isPackable)) pack_open();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_PACK_SPRITESHEET));
if (ImGui::MenuItem(localize.get(BASIC_SAVE), nullptr, false, !selection.empty())) save_open();
ImGui::Separator();
if (ImGui::MenuItem(localize.get(BASIC_COPY), settings.shortcutCopy.c_str(), false, !selection.empty())) copy();
if (ImGui::MenuItem(localize.get(BASIC_PASTE), settings.shortcutPaste.c_str(), false, !clipboard.is_empty()))
paste();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
if (ImGui::Begin(localize.get(LABEL_SPRITESHEETS_WINDOW), &settings.windowIsSpritesheets))
{
auto childSize = size_without_footer_get(2);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Spritesheets Child", childSize, ImGuiChildFlags_Borders))
{
auto spritesheetChildSize = ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetTextLineHeightWithSpacing() * 4);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
selection.start(anm2.content.spritesheets.size());
if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, ImGuiInputFlags_RouteFocused))
{
selection.clear();
for (auto& id : anm2.content.spritesheets | std::views::keys)
selection.insert(id);
}
if (ImGui::Shortcut(ImGuiKey_Escape, ImGuiInputFlags_RouteFocused)) selection.clear();
auto scroll_to_item = [&](float itemHeight, bool isTarget)
{
if (!isTarget) return;
auto windowHeight = ImGui::GetWindowHeight();
auto targetTop = ImGui::GetCursorPosY();
auto targetBottom = targetTop + itemHeight;
auto visibleTop = ImGui::GetScrollY();
auto visibleBottom = visibleTop + windowHeight;
if (targetTop < visibleTop)
ImGui::SetScrollY(targetTop);
else if (targetBottom > visibleBottom)
ImGui::SetScrollY(targetBottom - windowHeight);
};
int scrollTargetId = -1;
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
(ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) || ImGui::IsKeyPressed(ImGuiKey_DownArrow, true)))
{
std::vector<int> ids{};
ids.reserve(anm2.content.spritesheets.size());
for (auto& [id, sheet] : anm2.content.spritesheets)
ids.push_back(id);
if (!ids.empty())
{
int delta = ImGui::IsKeyPressed(ImGuiKey_UpArrow, true) ? -1 : 1;
int current = reference;
if (current == -1 && !selection.empty()) current = *selection.begin();
auto it = std::find(ids.begin(), ids.end(), current);
int index = it == ids.end() ? 0 : (int)std::distance(ids.begin(), it);
index = std::clamp(index + delta, 0, (int)ids.size() - 1);
int nextId = ids[index];
selection = {nextId};
reference = nextId;
region.reference = -1;
region.selection.clear();
scrollTargetId = nextId;
}
}
for (auto& [id, spritesheet] : anm2.content.spritesheets)
{
auto isNewSpritesheet = newSpritesheetId == id;
ImGui::PushID(id);
scroll_to_item(spritesheetChildSize.y, scrollTargetId == id);
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
{
auto isSelected = selection.contains(id);
auto isReferenced = id == reference;
auto cursorPos = ImGui::GetCursorPos();
bool isValid = spritesheet.texture.is_valid();
auto& texture = isValid ? spritesheet.texture : resources.icons[icon::NONE];
auto tintColor = !isValid ? ImVec4(1.0f, 0.25f, 0.25f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
auto pathString = path::to_utf8(spritesheet.path);
auto pathCStr = pathString.c_str();
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize))
{
reference = id;
region.reference = -1;
region.selection.clear();
}
if (scrollTargetId == id) ImGui::SetItemDefaultFocus();
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
open_directory(spritesheet);
auto viewport = ImGui::GetMainViewport();
auto maxPreviewSize = to_vec2(viewport->Size) * 0.5f;
vec2 textureSize = vec2(glm::max(texture.size.x, 1), glm::max(texture.size.y, 1));
if (textureSize.x > maxPreviewSize.x || textureSize.y > maxPreviewSize.y)
{
auto scale = glm::min(maxPreviewSize.x / textureSize.x, maxPreviewSize.y / textureSize.y);
textureSize *= scale;
}
auto textWidth = ImGui::CalcTextSize(pathCStr).x;
auto tooltipPadding = style.WindowPadding.x * 4.0f;
auto minWidth = textureSize.x + style.ItemSpacing.x + textWidth + tooltipPadding;
ImGui::SetNextWindowSize(ImVec2(minWidth, 0), ImGuiCond_Appearing);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
if (ImGui::BeginItemTooltip())
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
auto childFlags = ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
auto noScrollFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize), childFlags,
noScrollFlags))
ImGui::ImageWithBg(texture.id, to_imvec2(textureSize), ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SameLine();
auto infoChildFlags = ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY;
if (ImGui::BeginChild("##Spritesheet Info Tooltip Child", ImVec2(), infoChildFlags, noScrollFlags))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(pathCStr);
ImGui::PopFont();
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_ID), std::make_format_args(id)).c_str());
if (!isValid)
ImGui::TextUnformatted(localize.get(TOOLTIP_SPRITESHEET_INVALID));
else
ImGui::TextUnformatted(std::vformat(localize.get(FORMAT_TEXTURE_SIZE),
std::make_format_args(texture.size.x, texture.size.y))
.c_str());
ImGui::TextUnformatted(localize.get(TEXT_OPEN_DIRECTORY));
}
ImGui::EndChild();
ImGui::EndTooltip();
}
ImGui::PopStyleVar(2);
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
auto aspectRatio = (float)texture.size.x / texture.size.y;
if (imageSize.x / imageSize.y > aspectRatio)
imageSize.x = imageSize.y * aspectRatio;
else
imageSize.y = imageSize.x / aspectRatio;
ImGui::SetCursorPos(cursorPos);
ImGui::ImageWithBg(texture.id, imageSize, ImVec2(), ImVec2(1, 1), ImVec4(), tintColor);
ImGui::SetCursorPos(
ImVec2(spritesheetChildSize.y + style.ItemSpacing.x,
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
auto spritesheetLabel = std::vformat(localize.get(FORMAT_SPRITESHEET), std::make_format_args(id, pathCStr));
if (document.spritesheet_is_dirty(id))
spritesheetLabel =
std::vformat(localize.get(FORMAT_SPRITESHEET_NOT_SAVED), std::make_format_args(spritesheetLabel));
ImGui::TextUnformatted(spritesheetLabel.c_str());
if (isReferenced) ImGui::PopFont();
}
ImGui::EndChild();
if (isNewSpritesheet)
{
ImGui::SetScrollHereY(0.5f);
newSpritesheetId = -1;
}
ImGui::PopID();
}
ImGui::PopStyleVar();
selection.finish();
}
ImGui::EndChild();
ImGui::PopStyleVar();
context_menu();
auto rowOneWidgetSize = widget_size_with_row_get(3);
shortcut(manager.chords[SHORTCUT_ADD]);
if (ImGui::Button(localize.get(BASIC_ADD), rowOneWidgetSize)) add_open();
set_item_tooltip_shortcut(localize.get(TOOLTIP_ADD_SPRITESHEET), settings.shortcutAdd);
if (dialog.is_selected(Dialog::SPRITESHEET_OPEN))
{
add(dialog.path);
dialog.reset();
}
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_RELOAD), rowOneWidgetSize)) reload();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_RELOAD_SPRITESHEETS));
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1);
if (ImGui::Button(localize.get(BASIC_REPLACE), rowOneWidgetSize)) replace_open();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_REPLACE_SPRITESHEET));
ImGui::EndDisabled();
if (dialog.is_selected(Dialog::SPRITESHEET_REPLACE))
{
replace(dialog.path);
dialog.reset();
}
if (dialog.is_selected(Dialog::SPRITESHEET_PATH_SET))
{
set_file_path(dialog.path);
dialog.reset();
}
auto rowTwoWidgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_REMOVE]);
if (ImGui::Button(localize.get(BASIC_REMOVE_UNUSED), rowTwoWidgetSize)) remove_unused();
set_item_tooltip_shortcut(localize.get(TOOLTIP_REMOVE_UNUSED_SPRITESHEETS), settings.shortcutRemove);
ImGui::SameLine();
ImGui::BeginDisabled(selection.empty());
if (ImGui::Button(localize.get(BASIC_SAVE), rowTwoWidgetSize)) save_open();
ImGui::EndDisabled();
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_SAVE_SPRITESHEETS));
if (imgui::shortcut(manager.chords[SHORTCUT_ADD], shortcut::FOCUSED)) add_open();
if (imgui::shortcut(manager.chords[SHORTCUT_REMOVE], shortcut::FOCUSED)) remove_unused();
if (imgui::shortcut(manager.chords[SHORTCUT_COPY], shortcut::FOCUSED)) copy();
if (imgui::shortcut(manager.chords[SHORTCUT_PASTE], shortcut::FOCUSED)) paste();
if (imgui::shortcut(manager.chords[SHORTCUT_MERGE], shortcut::FOCUSED) && selection.size() > 1) merge_open();
}
ImGui::End();
mergePopup.trigger();
if (ImGui::BeginPopupModal(mergePopup.label(), &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
{
settings.mergeSpritesheetsRegionOrigin =
glm::clamp(settings.mergeSpritesheetsRegionOrigin, (int)origin::TOP_LEFT, (int)origin::ORIGIN_CENTER);
auto close = [&]()
{
mergeSelection.clear();
mergePopup.close();
};
auto optionsSize = child_size_get(6);
if (ImGui::BeginChild("##Merge Spritesheets Options", optionsSize, ImGuiChildFlags_Borders))
{
ImGui::SeparatorText(localize.get(LABEL_REGION_PROPERTIES_ORIGIN));
ImGui::RadioButton(localize.get(LABEL_MERGE_SPRITESHEETS_APPEND_BOTTOM), &settings.mergeSpritesheetsOrigin,
anm2::APPEND_BOTTOM);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_SPRITESHEETS_BOTTOM_LEFT));
ImGui::SameLine();
ImGui::RadioButton(localize.get(LABEL_MERGE_SPRITESHEETS_APPEND_RIGHT), &settings.mergeSpritesheetsOrigin,
anm2::APPEND_RIGHT);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_SPRITESHEETS_TOP_RIGHT));
ImGui::SeparatorText(localize.get(LABEL_OPTIONS));
ImGui::Checkbox(localize.get(LABEL_MERGE_MAKE_SPRITESHEET_REGIONS), &settings.mergeSpritesheetsIsMakeRegions);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_MAKE_SPRITESHEET_REGIONS));
ImGui::BeginDisabled(!settings.mergeSpritesheetsIsMakeRegions);
ImGui::Checkbox(localize.get(LABEL_MERGE_MAKE_PRIMARY_SPRITESHEET_REGION),
&settings.mergeSpritesheetsIsMakePrimaryRegion);
ImGui::SetItemTooltip("%s", localize.get(TOOLTIP_MERGE_MAKE_PRIMARY_SPRITESHEET_REGION));
const char* regionOriginOptions[] = {localize.get(LABEL_REGION_ORIGIN_TOP_LEFT),
localize.get(LABEL_REGION_ORIGIN_CENTER)};
ImGui::Combo(localize.get(LABEL_REGION_PROPERTIES_ORIGIN), &settings.mergeSpritesheetsRegionOrigin,
regionOriginOptions, IM_ARRAYSIZE(regionOriginOptions));
ImGui::EndDisabled();
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
ImGui::BeginDisabled(mergeSelection.size() <= 1);
if (ImGui::Button(localize.get(BASIC_MERGE), widgetSize))
{
merge();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) close();
ImGui::EndPopup();
}
mergePopup.end();
packPopup.trigger();
if (ImGui::BeginPopupModal(packPopup.label(), &packPopup.isOpen, ImGuiWindowFlags_NoResize))
{
settings.packPadding = std::max(0, settings.packPadding);
auto close = [&]()
{
packId = -1;
packPopup.close();
};
auto optionsSize = child_size_get(1);
if (ImGui::BeginChild("##Pack Spritesheet Options", optionsSize, ImGuiChildFlags_Borders))
{
ImGui::DragInt(localize.get(LABEL_PACK_PADDING), &settings.packPadding, DRAG_SPEED, 0, PADDING_MAX);
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(manager.chords[SHORTCUT_CONFIRM]);
bool isPackable = packId != -1 && anm2.content.spritesheets.contains(packId) &&
!anm2.content.spritesheets.at(packId).regions.empty();
ImGui::BeginDisabled(!isPackable);
if (ImGui::Button(localize.get(BASIC_PACK), widgetSize))
{
if (pack) pack();
close();
}
ImGui::EndDisabled();
ImGui::SameLine();
shortcut(manager.chords[SHORTCUT_CANCEL]);
if (ImGui::Button(localize.get(BASIC_CANCEL), widgetSize)) close();
ImGui::EndPopup();
}
packPopup.end();
overwritePopup.trigger();
if (ImGui::BeginPopupModal(overwritePopup.label(), &overwritePopup.isOpen, ImGuiWindowFlags_NoResize))
{
ImGui::TextUnformatted(localize.get(LABEL_OVERWRITE_CONFIRMATION));
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_YES), widgetSize))
{
save(saveSelection);
saveSelection.clear();
overwritePopup.close();
}
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_NO), widgetSize))
{
saveSelection.clear();
overwritePopup.close();
}
ImGui::EndPopup();
}
overwritePopup.end();
}
}
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include "clipboard.hpp"
#include "dialog.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
namespace anm2ed::imgui
{
class Spritesheets
{
int newSpritesheetId{-1};
PopupHelper mergePopup{PopupHelper(LABEL_SPRITESHEETS_MERGE_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
PopupHelper packPopup{PopupHelper(LABEL_SPRITESHEETS_PACK_POPUP, imgui::POPUP_SMALL_NO_HEIGHT)};
PopupHelper overwritePopup{PopupHelper(LABEL_TASKBAR_OVERWRITE_FILE, imgui::POPUP_SMALL_NO_HEIGHT)};
std::set<int> mergeSelection{};
int packId{-1};
std::set<int> saveSelection{};
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
};
}
File diff suppressed because it is too large Load Diff
+47 -4
View File
@@ -1,5 +1,7 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include "clipboard.hpp"
@@ -8,17 +10,41 @@
#include "resources.hpp"
#include "settings.hpp"
#include "strings.hpp"
#include "util/imgui/popup.hpp"
namespace anm2ed::imgui
{
struct TimelineGroupReference
{
int documentIndex{-1};
int animationIndex{-1};
int type{NONE};
int id{-1};
auto operator<=>(const TimelineGroupReference&) const = default;
};
struct TimelineRowReference
{
int documentIndex{-1};
int animationIndex{-1};
int type{NONE};
int id{-1};
int index{-1};
bool isGroup{};
auto operator<=>(const TimelineRowReference&) const = default;
};
struct FrameMoveDrag
{
anm2::Type type{anm2::NONE};
int type{NONE};
int itemID{-1};
int animationIndex{-1};
int frameIndex{-1};
int duration{1};
std::vector<int> indices{};
std::vector<Reference> references{};
bool isActive{};
};
@@ -30,8 +56,22 @@ namespace anm2ed::imgui
popup::ItemProperties itemProperties{};
PopupHelper bakePopup{PopupHelper(LABEL_TIMELINE_BAKE_POPUP, POPUP_SMALL_NO_HEIGHT)};
int hoveredTime{};
anm2::Frame* draggedFrame{};
anm2::Type draggedFrameType{};
bool isFrameBoxSelecting{};
bool isFrameBoxAdditive{};
ImVec2 frameBoxStart{};
ImVec2 frameBoxEnd{};
std::set<Reference> frameBoxSelection{};
std::set<TimelineGroupReference> groupReferences{};
TimelineRowReference rowSelectionAnchor{};
bool isRowSelectionAnchorSet{};
PopupHelper groupPropertiesPopup{PopupHelper(LABEL_GROUP_PROPERTIES, POPUP_SMALL_NO_HEIGHT)};
std::string groupName{};
int groupAnimationIndex{-1};
int groupType{NONE};
int groupId{-1};
Reference draggedFrameReference{};
bool isDraggedFrameActive{};
int draggedFrameType{};
int draggedFrameIndex{-1};
int draggedFrameStart{-1};
int draggedFrameStartDuration{-1};
@@ -42,7 +82,10 @@ namespace anm2ed::imgui
std::vector<int> frameSelectionSnapshot{};
std::vector<int> frameSelectionLocked{};
bool isFrameSelectionLocked{};
anm2::Reference frameSelectionSnapshotReference{};
Reference frameSelectionSnapshotReference{};
Reference frameSelectionAnchor{};
bool isFrameSelectionAnchorSet{};
std::vector<TimelineRowReference> rowDragReferences{};
glm::vec2 scroll{};
ImDrawList* pickerLineDrawList{};
ImGuiStyle style{};
+5 -2
View File
@@ -6,6 +6,7 @@
#include "strings.hpp"
#include "tool.hpp"
#include "types.hpp"
#include "util/imgui/imgui.hpp"
using namespace anm2ed::resource;
using namespace anm2ed::types;
@@ -30,10 +31,12 @@ namespace anm2ed::imgui
switch (type)
{
case tool::UNDO:
if (document.is_able_to_undo()) document.undo();
if (document.is_able_to_undo())
manager.command_push({manager.selected, [](Manager&, Document& document) { document.undo(); }});
break;
case tool::REDO:
if (document.is_able_to_redo()) document.redo();
if (document.is_able_to_redo())
manager.command_push({manager.selected, [](Manager&, Document& document) { document.redo(); }});
break;
case tool::COLOR:
colorEditPopup.open();
+5 -6
View File
@@ -3,7 +3,7 @@
#include <format>
#include <ranges>
#include "path_.hpp"
#include "path.hpp"
#include "strings.hpp"
using namespace anm2ed::util;
@@ -15,8 +15,7 @@ namespace anm2ed::imgui
{
auto viewport = ImGui::GetMainViewport();
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
if (windowHeight < 1.0f)
windowHeight = 1.0f;
if (windowHeight < 1.0f) windowHeight = 1.0f;
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
@@ -37,10 +36,10 @@ namespace anm2ed::imgui
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(localize.get(BASIC_NEW), widgetSize))
dialog.file_save(Dialog::ANM2_NEW); // handled in taskbar.cpp
dialog.file_save(Dialog::ANM2_CREATE); // handled in taskbar.cpp
ImGui::SameLine();
if (ImGui::Button(localize.get(BASIC_OPEN), widgetSize))
dialog.file_open(Dialog::ANM2_OPEN); // handled in taskbar.cpp
dialog.file_open(Dialog::ANM2_OPEN, true); // handled in taskbar.cpp
if (ImGui::BeginChild("##Recent Files Child", {}, ImGuiChildFlags_Borders))
{
@@ -53,7 +52,7 @@ namespace anm2ed::imgui
if (ImGui::Selectable(label.c_str()))
{
manager.open(file);
manager.command_push({.runManager = [file](Manager& manager) { manager.open(file); }});
ImGui::PopID();
break;
}
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <functional>
#include <set>
#include <string>
#include <vector>
#include "clipboard.hpp"
#include "dialog.hpp"
#include "util/imgui/imgui.hpp"
#include "manager.hpp"
#include "resources.hpp"
#include "settings.hpp"
#include "storage.hpp"
namespace anm2ed::imgui
{
enum WindowFlag
{
WINDOW_ADD = 1 << 0,
WINDOW_REMOVE = 1 << 1,
WINDOW_REMOVE_UNUSED = 1 << 2,
WINDOW_DUPLICATE = 1 << 3,
WINDOW_MERGE = 1 << 4,
WINDOW_DEFAULT = 1 << 5,
WINDOW_CUT = 1 << 6,
WINDOW_COPY = 1 << 7,
WINDOW_PASTE = 1 << 8,
WINDOW_RENAME = 1 << 9,
WINDOW_PROPERTIES = 1 << 10,
WINDOW_REFERENCE_ITALIC = 1 << 11
};
using WindowFlags = int;
constexpr bool window_flag_has(WindowFlags flags, WindowFlag flag) { return (flags & flag) != 0; }
struct Window
{
using StorageGet = std::function<Storage&(Document&)>;
using ElementGet = std::function<Element*(Anm2&, int)>;
using ElementKeyGet = std::function<int(const Element&, int)>;
using RowLabelGet = std::function<std::string(Document&, const Element&)>;
using RowFontGet = std::function<resource::font::Type(Document&, const Element&, int)>;
using RowSelect = std::function<void(Window&, Document&, int)>;
using RenameFinish = std::function<void(Document&, Element&, int, int)>;
using TooltipDraw = std::function<void(Document&, Resources&, const Element&)>;
using RowDragDropUpdate = std::function<bool(Window&, Manager&, Document&, const Element&, int)>;
using PropertiesOpen = std::function<void(Manager&, int)>;
using Command = std::function<void(Window&, Manager&, Settings&, Document&, Clipboard&)>;
using IsAvailable = std::function<bool(Document&)>;
using Update = std::function<void(Window&, Manager&, Settings&, Resources&, Clipboard&, Document&)>;
using RowsUpdate = std::function<void(Window&, Manager&, Settings&, Resources&, Clipboard&, Document&, ImVec2)>;
StringType title{};
bool Settings::* isOpen{};
Document::ChangeType changeType{};
ElementType containerType{ElementType::UNKNOWN};
ElementType elementType{ElementType::UNKNOWN};
const char* childLabel{"##Window Child"};
StringType addTooltip{};
StringType duplicateTooltip{};
StringType mergeTooltip{};
StringType removeTooltip{};
StringType removeUnusedTooltip{};
StringType defaultTooltip{};
StringType addEdit{};
StringType renameEdit{};
StringType pasteEdit{};
StringType removeUnusedEdit{};
StringType deserializeFailedToast{};
StringType unavailableText{STRING_UNDEFINED};
int newElementId{-1};
int scrollQueued{-1};
int renameQueued{-1};
int renameId{-1};
int editId{-1};
int footerRows{-1};
RenameState renameState{RENAME_SELECTABLE};
std::string renameText{};
PopupHelper popup{STRING_UNDEFINED};
PopupHelper popup2{STRING_UNDEFINED};
PopupHelper popup3{STRING_UNDEFINED};
std::set<int> selection{};
std::set<int> selection2{};
std::vector<int> dragSelection{};
std::vector<int> order{};
Element editElement{};
Dialog* dialog{};
WindowFlags flags{WINDOW_COPY | WINDOW_PASTE};
bool isChildPaddingZero{};
bool isPreserveEditElementOnOpen{};
ImVec2 tooltipWindowPadding{};
ImVec2 tooltipItemSpacing{};
StorageGet storage_get{};
ElementGet element_get{};
ElementKeyGet element_key_get{};
RowLabelGet row_label_get{};
RowFontGet row_font_get{};
RowSelect row_select{};
RenameFinish rename_finish{};
TooltipDraw tooltip_draw{};
RowDragDropUpdate row_drag_drop_update{};
PropertiesOpen properties_open{};
Command add{};
Command remove{};
Command duplicate{};
Command merge{};
Command merge_open{};
Command default_set{};
Command cut{};
Command copy{};
Command paste{};
Command reload{};
Command replace{};
Command save{};
Command pack{};
Command trim{};
Command properties{};
Command open{};
Command path_set{};
IsAvailable is_available{};
Update begin_update{};
RowsUpdate rows_update{};
Update context_update{};
Update footer_update{};
Update body_update{};
Update popup_update{};
Update post_update{};
};
Window animations_window_register();
Window regions_window_register();
Window sounds_window_register();
Window spritesheets_window_register();
Window layers_window_register();
Window nulls_window_register();
Window events_window_register();
void window_update(Window&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
}