Big refactor, shuffling a lot of files around

This commit is contained in:
2025-11-01 19:51:19 -04:00
parent 99b7d9f49d
commit 62cd94ca78
125 changed files with 4073 additions and 3011 deletions

View File

@@ -0,0 +1,482 @@
#include "animation_preview.h"
#include <ranges>
#include <glm/gtc/type_ptr.hpp>
#include "log.h"
#include "math_.h"
#include "toast.h"
#include "tool.h"
#include "types.h"
using namespace anm2ed::canvas;
using namespace anm2ed::types;
using namespace anm2ed::util;
using namespace anm2ed::resource;
using namespace glm;
namespace anm2ed::imgui
{
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
constexpr auto TARGET_SIZE = vec2(32, 32);
constexpr auto POINT_SIZE = vec2(4, 4);
constexpr auto NULL_RECT_SIZE = vec2(100);
constexpr auto TRIGGER_TEXT_COLOR = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
AnimationPreview::AnimationPreview() : Canvas(vec2())
{
}
void AnimationPreview::tick(Manager& manager, Document& document, Settings& settings)
{
auto& anm2 = document.anm2;
auto& playback = document.playback;
if (playback.isPlaying)
{
auto& isSound = settings.timelineIsSound;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
if (isSound && !anm2.content.sounds.empty())
if (auto animation = document.animation_get(); animation)
if (animation->triggers.isVisible && !isOnlyShowLayers)
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
anm2.content.sounds[anm2.content.events[trigger.eventID].soundID].audio.play();
document.reference.frameTime = playback.time;
}
if (manager.isRecording)
{
auto pixels = pixels_get();
renderFrames.push_back(Texture(pixels.data(), size));
if (playback.isFinished)
{
auto& ffmpegPath = settings.renderFFmpegPath;
auto& path = settings.renderPath;
auto& type = settings.renderType;
if (type == render::PNGS)
{
auto& format = settings.renderFormat;
bool isSuccess{true};
for (auto [i, frame] : std::views::enumerate(renderFrames))
{
std::filesystem::path outputPath =
std::filesystem::path(path) / std::vformat(format, std::make_format_args(i));
if (!frame.write_png(outputPath))
{
isSuccess = false;
break;
}
logger.info(std::format("Saved frame to PNG: {}", outputPath.string()));
}
if (isSuccess)
toasts.info(std::format("Exported rendered frames to: {}", path));
else
toasts.warning(std::format("Could not export frames to: {}", path));
}
else
{
if (animation_render(ffmpegPath, path, renderFrames, (render::Type)type, size, anm2.info.fps))
toasts.info(std::format("Exported rendered animation to: {}", path));
else
toasts.warning(std::format("Could not output rendered animation: {}", path));
}
renderFrames.clear();
playback.isPlaying = false;
playback.isFinished = false;
manager.isRecording = false;
manager.progressPopup.close();
}
}
}
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference;
auto animation = document.animation_get();
auto& pan = document.previewPan;
auto& zoom = document.previewZoom;
auto& backgroundColor = settings.previewBackgroundColor;
auto& axesColor = settings.previewAxesColor;
auto& gridColor = settings.previewGridColor;
auto& gridSize = settings.previewGridSize;
auto& gridOffset = settings.previewGridOffset;
auto& zoomStep = settings.viewZoomStep;
auto& isGrid = settings.previewIsGrid;
auto& overlayTransparency = settings.previewOverlayTransparency;
auto& overlayIndex = document.overlayIndex;
auto& isRootTransform = settings.previewIsRootTransform;
auto& isPivots = settings.previewIsPivots;
auto& isAxes = settings.previewIsAxes;
auto& isAltIcons = settings.previewIsAltIcons;
auto& isBorder = settings.previewIsBorder;
auto& tool = settings.tool;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
auto& shaderLine = resources.shaders[shader::LINE];
auto& shaderAxes = resources.shaders[shader::AXIS];
auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE];
settings.previewPan = pan;
settings.previewZoom = zoom;
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
{
auto childSize = ImVec2(imgui::row_widget_width_get(4),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::Checkbox("Grid", &isGrid);
ImGui::SameLine();
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
ImGui::InputInt2("Size", value_ptr(gridSize));
ImGui::InputInt2("Offset", value_ptr(gridOffset));
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutCenterView);
if (ImGui::Button("Center View", widgetSize)) pan = vec2();
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
ImGui::SameLine();
imgui::shortcut(settings.shortcutFit);
if (ImGui::Button("Fit", widgetSize))
if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform));
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
ImGui::SameLine();
ImGui::Checkbox("Axes", &isAxes);
ImGui::SameLine();
ImGui::ColorEdit4("Color", value_ptr(axesColor), ImGuiColorEditFlags_NoInputs);
imgui::combo_strings("Overlay", &overlayIndex, document.animationNamesCStr);
ImGui::InputFloat("Alpha", &overlayTransparency, 0, 0, "%.0f");
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Helpers Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
auto helpersChildSize = ImVec2(imgui::row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
if (ImGui::BeginChild("##Helpers Child 1", helpersChildSize))
{
ImGui::Checkbox("Root Transform", &isRootTransform);
ImGui::Checkbox("Pivots", &isPivots);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Helpers Child 2", helpersChildSize))
{
ImGui::Checkbox("Alt Icons", &isAltIcons);
ImGui::Checkbox("Border", &isBorder);
}
ImGui::EndChild();
}
ImGui::EndChild();
auto cursorScreenPos = ImGui::GetCursorScreenPos();
size_set(to_vec2(ImGui::GetContentRegionAvail()));
bind();
viewport_set();
clear(backgroundColor);
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
bool isOnionskin = false)
{
auto transform = transform_get(zoom, pan);
auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
if (isRootTransform)
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
if (!isOnlyShowLayers && root.isVisible && animation->rootAnimation.isVisible)
{
auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
math::percent_to_unit(root.scale), root.rotation);
vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color);
}
for (auto& id : animation->layerOrder)
{
auto& layerAnimation = animation->layerAnimations.at(id);
if (!layerAnimation.isVisible) continue;
auto& layer = anm2.content.layers.at(id);
if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.isVisible)
{
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
if (!spritesheet) continue;
auto& texture = spritesheet->texture;
if (!texture.is_valid()) continue;
auto layerModel = math::quad_model_get(frame.size, frame.position, frame.pivot,
math::percent_to_unit(frame.scale), frame.rotation);
auto layerTransform = transform * layerModel;
auto uvMin = frame.crop / vec2(texture.size);
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
auto vertices = math::uv_vertices_get(uvMin, uvMax);
vec3 frameColorOffset = frame.colorOffset + colorOffset;
vec4 frameTint = frame.tint;
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
texture_render(shaderTexture, texture.id, layerTransform, frameTint, frameColorOffset, vertices.data());
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED;
if (isBorder) rect_render(shaderLine, layerTransform, layerModel, color);
if (isPivots)
{
auto pivotModel = math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
auto pivotTransform = transform * pivotModel;
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
}
}
}
for (auto& [id, nullAnimation] : animation->nullAnimations)
{
if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
auto& isShowRect = anm2.content.nulls[id].isShowRect;
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
{
auto icon = isShowRect ? icon::POINT : icon::TARGET;
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
: id == reference.itemID && reference.itemType == anm2::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);
auto nullTransform = transform * nullModel;
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
if (isShowRect)
{
auto rectModel = math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
auto rectTransform = transform * rectModel;
rect_render(shaderLine, rectTransform, rectModel, color);
}
}
}
};
auto onionskin_render = [&](float time, int count, int direction, vec3 color)
{
for (int i = 1; i <= count; i++)
{
float useTime = time + (float)(direction * i);
if (useTime < 0.0f || useTime > animation->frameNum) continue;
float alphaOffset = (1.0f / (count + 1)) * i;
render(animation, useTime, color, alphaOffset, true);
}
};
auto onionskins_render = [&](float time)
{
onionskin_render(time, settings.onionskinBeforeCount, -1, settings.onionskinBeforeColor);
onionskin_render(time, settings.onionskinAfterCount, 1, settings.onionskinAfterColor);
};
auto frameTime = reference.frameTime > -1 && !playback.isPlaying ? reference.frameTime : playback.time;
if (animation)
{
auto& drawOrder = settings.onionskinDrawOrder;
auto& isEnabled = settings.onionskinIsEnabled;
if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime);
render(animation, frameTime);
if (overlayIndex > 0)
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
1.0f - math::uint8_to_float(overlayTransparency));
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
}
unbind();
ImGui::Image(texture, to_imvec2(size));
isPreviewHovered = ImGui::IsItemHovered();
if (animation && animation->triggers.isVisible && !isOnlyShowLayers)
{
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER); trigger.isVisible)
{
auto clipMin = ImGui::GetItemRectMin();
auto clipMax = ImGui::GetItemRectMax();
auto drawList = ImGui::GetWindowDrawList();
auto textPos = to_imvec2(to_vec2(cursorScreenPos) + to_vec2(ImGui::GetStyle().WindowPadding));
drawList->PushClipRect(clipMin, clipMax);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
drawList->AddText(textPos, ImGui::GetColorU32(TRIGGER_TEXT_COLOR),
anm2.content.events.at(trigger.eventID).name.c_str());
ImGui::PopFont();
drawList->PopClipRect();
}
}
if (isPreviewHovered)
{
ImGui::SetKeyboardFocusHere(-1);
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel;
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto useTool = tool;
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isBegin = isMouseClick || isKeyPressed;
auto isEnd = isMouseReleased || isKeyReleased;
if (isMouseMiddleDown) useTool = tool::PAN;
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE;
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
switch (useTool)
{
case tool::PAN:
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
break;
case tool::MOVE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Position");
if (isMouseDown) frame->position = mousePos;
if (isLeft) frame->position.x -= step;
if (isRight) frame->position.x += step;
if (isUp) frame->position.y -= step;
if (isDown) frame->position.y += step;
if (isEnd) document.change(Document::FRAMES);
break;
case tool::SCALE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Scale");
if (isMouseDown) frame->scale += mouseDelta;
if (isLeft) frame->scale.x -= step;
if (isRight) frame->scale.x += step;
if (isUp) frame->scale.y -= step;
if (isDown) frame->scale.y += step;
if (isEnd) document.change(Document::FRAMES);
break;
case tool::ROTATE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Rotation");
if (isMouseDown) frame->rotation += mouseDelta.y;
if (isLeft || isDown) frame->rotation -= step;
if (isUp || isRight) frame->rotation += step;
if (isEnd) document.change(Document::FRAMES);
break;
default:
break;
}
if (mouseWheel != 0 || isZoomIn || isZoomOut)
zoom_set(zoom, pan, vec2(mousePos), (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
}
}
ImGui::End();
manager.progressPopup.trigger();
if (ImGui::BeginPopupModal(manager.progressPopup.label, &manager.progressPopup.isOpen, ImGuiWindowFlags_NoResize))
{
if (!animation) return;
auto& start = manager.recordingStart;
auto& end = manager.recordingEnd;
auto progress = (playback.time - start) / (end - start);
ImGui::ProgressBar(progress);
if (ImGui::Button("Cancel", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
{
playback.isPlaying = false;
manager.isRecording = false;
manager.progressPopup.close();
}
ImGui::EndPopup();
}
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "canvas.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class AnimationPreview : public Canvas
{
bool isPreviewHovered{};
glm::ivec2 mousePos{};
std::vector<resource::Texture> renderFrames{};
public:
AnimationPreview();
void tick(Manager&, Document&, Settings&);
void update(Manager&, Settings&, Resources&);
};
}

View File

@@ -0,0 +1,272 @@
#include "animations.h"
#include <ranges>
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& hovered = document.hoveredAnimation;
auto& multiSelect = document.animationMultiSelect;
auto& mergeMultiSelect = document.animationMergeMultiSelect;
auto& mergeTarget = document.mergeTarget;
hovered = -1;
if (ImGui::Begin("Animations", &settings.windowIsAnimations))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
{
multiSelect.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{
ImGui::PushID(i);
auto isDefault = anm2.animations.defaultAnimation == animation.name;
auto isReferenced = reference.animationIndex == 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(i);
if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i),
animation.name, multiSelect.contains(i)))
document.animation_set(i);
if (ImGui::IsItemHovered()) hovered = i;
ImGui::PopFont();
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("(Default Animation)");
ImGui::PopFont();
}
ImGui::Text("Length: %d", animation.frameNum);
ImGui::Text("Loop: %s", animation.isLoop ? "true" : "false");
ImGui::EndTooltip();
}
if (ImGui::BeginDragDropSource())
{
static std::vector<int> selection;
selection.assign(multiSelect.begin(), multiSelect.end());
ImGui::SetDragDropPayload("Animation Drag Drop", selection.data(), selection.size() * sizeof(int));
for (auto& i : selection)
ImGui::TextUnformatted(anm2.animations.items[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.animations_move(indices, i);
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
multiSelect.finish();
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& i : multiSelect)
clipboardText += anm2.animations.items[i].to_string();
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.animations.items[hovered].to_string());
};
auto cut = [&]()
{
copy();
document.animations_remove();
};
auto paste = [&]()
{
auto clipboardText = clipboard.get();
document.animations_deserialize(clipboardText);
};
if (shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste();
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Cut", settings.shortcutCut.c_str())) cut();
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
if (ImGui::MenuItem("Paste", settings.shortcutPaste.c_str())) paste();
ImGui::EndDisabled();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(5);
shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) document.animation_add();
set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
ImGui::SameLine();
ImGui::BeginDisabled(multiSelect.empty());
{
shortcut(settings.shortcutDuplicate);
if (ImGui::Button("Duplicate", widgetSize)) document.animation_duplicate();
set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
ImGui::SameLine();
if (shortcut(settings.shortcutMerge, shortcut::FOCUSED))
if (multiSelect.size() > 0) document.animations_merge_quick();
ImGui::BeginDisabled(multiSelect.size() != 1);
{
if (ImGui::Button("Merge", widgetSize))
{
mergePopup.open();
mergeMultiSelect.clear();
mergeTarget = *multiSelect.begin();
}
}
ImGui::EndDisabled();
set_item_tooltip_shortcut("Open the merge popup.\nUsing the shortcut will merge the animations with\nthe last "
"configured merge settings.",
settings.shortcutMerge);
ImGui::SameLine();
shortcut(settings.shortcutRemove);
if (ImGui::Button("Remove", widgetSize)) document.animations_remove();
set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
ImGui::SameLine();
shortcut(settings.shortcutDefault);
ImGui::BeginDisabled(multiSelect.size() != 1);
if (ImGui::Button("Default", widgetSize)) document.animation_default();
ImGui::EndDisabled();
set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
}
ImGui::EndDisabled();
mergePopup.trigger();
if (ImGui::BeginPopupModal(mergePopup.label, &mergePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto merge_close = [&]()
{
mergeMultiSelect.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("Animations", animationsSize, ImGuiChildFlags_Borders))
{
mergeMultiSelect.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{
ImGui::PushID(i);
ImGui::SetNextItemSelectionUserData(i);
ImGui::Selectable(animation.name.c_str(), mergeMultiSelect.contains(i));
ImGui::PopID();
}
mergeMultiSelect.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("Append Frames", &type, merge::APPEND);
ImGui::RadioButton("Prepend Frames", &type, merge::PREPEND);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("Merge Options 2", size))
{
ImGui::RadioButton("Replace Frames", &type, merge::REPLACE);
ImGui::RadioButton("Ignore Frames", &type, merge::IGNORE);
}
ImGui::EndChild();
}
ImGui::EndChild();
if (ImGui::BeginChild("Merge Delete After", deleteAfterSize, ImGuiChildFlags_Borders))
ImGui::Checkbox("Delete Animations After", &isDeleteAnimationsAfter);
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button("Merge", widgetSize))
{
document.animations_merge((merge::Type)type, isDeleteAnimationsAfter);
merge_close();
}
ImGui::SameLine();
if (ImGui::Button("Close", widgetSize)) merge_close();
ImGui::EndPopup();
}
}
ImGui::End();
}
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "clipboard.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Animations
{
PopupHelper mergePopup{PopupHelper("Merge Animations")};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}

160
src/imgui/window/events.cpp Normal file
View File

@@ -0,0 +1,160 @@
#include "events.h"
#include <ranges>
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& unused = document.unusedEventIDs;
auto& hovered = document.hoveredEvent;
auto& reference = document.referenceEvent;
auto& multiSelect = document.eventMultiSelect;
hovered = -1;
if (ImGui::Begin("Events", &settings.windowIsEvents))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Events Child", childSize, true))
{
multiSelect.start(anm2.content.events.size());
for (auto& [id, event] : anm2.content.events)
{
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
ImGui::Selectable(event.name.c_str(), multiSelect.contains(id));
if (ImGui::IsItemHovered())
{
hovered = id;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
reference = id;
editEvent = document.anm2.content.events[reference];
propertiesPopup.open();
}
}
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(event.name.c_str());
ImGui::PopFont();
ImGui::EndTooltip();
}
ImGui::PopID();
}
multiSelect.finish();
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& id : multiSelect)
clipboardText += anm2.content.events[id].to_string(id);
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.content.events[hovered].to_string(hovered));
};
auto paste = [&](merge::Type type)
{
auto clipboardText = clipboard.get();
document.events_deserialize(clipboardText, type);
};
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled();
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
ImGui::EndDisabled();
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
{
if (ImGui::BeginMenu("Paste"))
{
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
ImGui::EndMenu();
}
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize))
{
reference = -1;
editEvent = anm2::Event();
propertiesPopup.open();
}
set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
ImGui::SameLine();
shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unused.empty());
if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
ImGui::EndDisabled();
set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
settings.shortcutRemove);
}
ImGui::End();
propertiesPopup.trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = child_size_get(2);
auto& event = editEvent;
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
input_text_string("Name", &event.name);
ImGui::SetItemTooltip("Set the event's name.");
combo_strings("Sound", &event.soundID, document.soundNames);
ImGui::SetItemTooltip("Set the event sound; it will play when a trigger associated with this event activates.");
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{
document.event_set(event);
propertiesPopup.close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) propertiesPopup.close();
propertiesPopup.end();
ImGui::EndPopup();
}
}
}

18
src/imgui/window/events.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "clipboard.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Events
{
anm2::Event editEvent{};
PopupHelper propertiesPopup{PopupHelper("Event Properties", POPUP_SMALL_NO_HEIGHT)};
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}

View File

@@ -0,0 +1,125 @@
#include "frame_properties.h"
#include <ranges>
#include <glm/gtc/type_ptr.hpp>
#include "math_.h"
#include "types.h"
using namespace anm2ed::util::math;
using namespace anm2ed::types;
using namespace glm;
namespace anm2ed::imgui
{
void FrameProperties::update(Manager& manager, Settings& settings)
{
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& type = reference.itemType;
auto frame = document.frame_get();
auto useFrame = frame ? *frame : anm2::Frame();
ImGui::BeginDisabled(!frame);
{
if (type == anm2::TRIGGER)
{
std::vector<std::string> eventNames{};
for (auto& event : anm2.content.events | std::views::values)
eventNames.emplace_back(event.name);
if (imgui::combo_strings("Event", frame ? &useFrame.eventID : &dummy_value<int>(), eventNames))
DOCUMENT_EDIT(document, "Trigger Event", Document::FRAMES, frame->eventID = useFrame.eventID);
ImGui::SetItemTooltip("Change the event this trigger uses.");
if (ImGui::InputInt("At Frame", frame ? &useFrame.atFrame : &dummy_value<int>(), imgui::STEP,
imgui::STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
DOCUMENT_EDIT(document, "Trigger At Frame", Document::FRAMES, frame->atFrame = useFrame.atFrame);
ImGui::SetItemTooltip("Change the frame the trigger will be activated at.");
}
else
{
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
{
if (ImGui::InputFloat2("Crop", frame ? value_ptr(useFrame.crop) : &dummy_value<float>(),
frame ? vec2_format_get(useFrame.crop) : ""))
DOCUMENT_EDIT(document, "Frame Crop", Document::FRAMES, frame->crop = useFrame.crop);
ImGui::SetItemTooltip("Change the crop position the frame uses.");
if (ImGui::InputFloat2("Size", frame ? value_ptr(useFrame.size) : &dummy_value<float>(),
frame ? vec2_format_get(useFrame.size) : ""))
DOCUMENT_EDIT(document, "Frame Size", Document::FRAMES, frame->size = useFrame.size);
ImGui::SetItemTooltip("Change the size of the crop the frame uses.");
}
ImGui::EndDisabled();
if (ImGui::InputFloat2("Position", frame ? value_ptr(useFrame.position) : &dummy_value<float>(),
frame ? vec2_format_get(useFrame.position) : ""))
DOCUMENT_EDIT(document, "Frame Position", Document::FRAMES, frame->position = useFrame.position);
ImGui::SetItemTooltip("Change the position of the frame.");
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
{
if (ImGui::InputFloat2("Pivot", frame ? value_ptr(useFrame.pivot) : &dummy_value<float>(),
frame ? vec2_format_get(useFrame.pivot) : ""))
DOCUMENT_EDIT(document, "Frame Pivot", Document::FRAMES, frame->pivot = useFrame.pivot);
ImGui::SetItemTooltip("Change the pivot of the frame; i.e., where it is centered.");
}
ImGui::EndDisabled();
if (ImGui::InputFloat2("Scale", frame ? value_ptr(useFrame.scale) : &dummy_value<float>(),
frame ? vec2_format_get(useFrame.scale) : ""))
DOCUMENT_EDIT(document, "Frame Scale", Document::FRAMES, frame->scale = useFrame.scale);
ImGui::SetItemTooltip("Change the scale of the frame, in percent.");
if (ImGui::InputFloat("Rotation", frame ? &useFrame.rotation : &dummy_value<float>(), imgui::STEP,
imgui::STEP_FAST, frame ? float_format_get(useFrame.rotation) : ""))
DOCUMENT_EDIT(document, "Frame Rotation", Document::FRAMES, frame->rotation = useFrame.rotation);
ImGui::SetItemTooltip("Change the rotation of the frame.");
if (ImGui::InputInt("Duration", frame ? &useFrame.delay : &dummy_value<int>(), imgui::STEP, imgui::STEP_FAST,
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
DOCUMENT_EDIT(document, "Frame Duration", Document::FRAMES, frame->delay = useFrame.delay);
ImGui::SetItemTooltip("Change how long the frame lasts.");
if (ImGui::ColorEdit4("Tint", frame ? value_ptr(useFrame.tint) : &dummy_value<float>()))
DOCUMENT_EDIT(document, "Frame Tint", Document::FRAMES, frame->tint = useFrame.tint);
ImGui::SetItemTooltip("Change the tint of the frame.");
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
DOCUMENT_EDIT(document, "Frame Color Offset", Document::FRAMES, frame->colorOffset = useFrame.colorOffset);
ImGui::SetItemTooltip("Change the color added onto the frame.");
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
DOCUMENT_EDIT(document, "Frame Visibility", Document::FRAMES, frame->isVisible = useFrame.isVisible);
ImGui::SetItemTooltip("Toggle the frame's visibility.");
ImGui::SameLine();
if (ImGui::Checkbox("Interpolated", frame ? &useFrame.isInterpolated : &dummy_value<bool>()))
DOCUMENT_EDIT(document, "Frame Interpolation", Document::FRAMES,
frame->isInterpolated = useFrame.isInterpolated);
ImGui::SetItemTooltip(
"Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.");
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("Flip X", widgetSize))
DOCUMENT_EDIT(document, "Frame Flip X", Document::FRAMES, frame->scale.x = -frame->scale.x);
ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame "
"horizontally.\n(Note: the format does not support mirroring.)");
ImGui::SameLine();
if (ImGui::Button("Flip Y", widgetSize))
DOCUMENT_EDIT(document, "Frame Flip Y", Document::FRAMES, frame->scale.y = -frame->scale.y);
ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame "
"vertically.\n(Note: the format does not support mirroring.)");
}
}
ImGui::EndDisabled();
}
ImGui::End();
}
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "manager.h"
#include "settings.h"
namespace anm2ed::imgui
{
class FrameProperties
{
public:
void update(Manager&, Settings&);
};
}

158
src/imgui/window/layers.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "layers.h"
#include <ranges>
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.referenceLayer;
auto& unused = document.unusedLayerIDs;
auto& hovered = document.hoveredLayer;
auto& multiSelect = document.layersMultiSelect;
auto& propertiesPopup = manager.layerPropertiesPopup;
hovered = -1;
if (ImGui::Begin("Layers", &settings.windowIsLayers))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Layers Child", childSize, true))
{
multiSelect.start(anm2.content.layers.size());
for (auto& [id, layer] : anm2.content.layers)
{
auto isSelected = multiSelect.contains(id);
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
ImGui::Selectable(std::format(anm2::LAYER_FORMAT, id, layer.name, layer.spritesheetID).c_str(), isSelected);
if (ImGui::IsItemHovered())
{
hovered = id;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.layer_properties_open(id);
}
else
hovered = -1;
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(layer.name.c_str());
ImGui::PopFont();
ImGui::Text("ID: %d", id);
ImGui::Text("Spritesheet ID: %d", layer.spritesheetID);
ImGui::EndTooltip();
}
ImGui::PopID();
}
multiSelect.finish();
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& id : multiSelect)
clipboardText += anm2.content.layers[id].to_string(id);
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.content.layers[hovered].to_string(hovered));
};
auto paste = [&](merge::Type type)
{
auto clipboardText = clipboard.get();
document.layers_deserialize(clipboardText, type);
};
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled();
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
ImGui::EndDisabled();
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
{
if (ImGui::BeginMenu("Paste"))
{
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
ImGui::EndMenu();
}
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) manager.layer_properties_open();
set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd);
ImGui::SameLine();
shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unused.empty());
if (ImGui::Button("Remove Unused", widgetSize)) document.layers_remove_unused();
ImGui::EndDisabled();
set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
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("Name", &layer.name);
ImGui::SetItemTooltip("Set the item's name.");
combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
ImGui::SetItemTooltip("Set the layer item's spritesheet.");
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{
document.layer_set(layer);
manager.layer_properties_close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) manager.layer_properties_close();
manager.layer_properties_end();
ImGui::EndPopup();
}
}
}

15
src/imgui/window/layers.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "clipboard.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Layers
{
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}

159
src/imgui/window/nulls.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "nulls.h"
#include <ranges>
using namespace anm2ed::resource;
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.referenceNull;
auto& unused = document.unusedNullIDs;
auto& hovered = document.hoveredNull;
auto& multiSelect = document.nullMultiSelect;
auto& propertiesPopup = manager.nullPropertiesPopup;
hovered = -1;
if (ImGui::Begin("Nulls", &settings.windowIsNulls))
{
auto childSize = size_without_footer_get();
if (ImGui::BeginChild("##Nulls Child", childSize, true))
{
multiSelect.start(anm2.content.nulls.size());
for (auto& [id, null] : anm2.content.nulls)
{
auto isSelected = multiSelect.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::format(anm2::NULL_FORMAT, id, null.name).c_str(), isSelected);
if (ImGui::IsItemHovered())
{
hovered = id;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.null_properties_open(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::Text("ID: %d", id);
ImGui::EndTooltip();
}
ImGui::PopID();
}
multiSelect.finish();
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& id : multiSelect)
clipboardText += anm2.content.nulls[id].to_string(id);
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.content.nulls[hovered].to_string(hovered));
};
auto paste = [&](merge::Type type)
{
auto clipboardText = clipboard.get();
document.nulls_deserialize(clipboardText, type);
};
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled();
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
ImGui::EndDisabled();
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
{
if (ImGui::BeginMenu("Paste"))
{
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
ImGui::EndMenu();
}
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) manager.null_properties_open();
set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd);
ImGui::SameLine();
shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unused.empty());
if (ImGui::Button("Remove Unused", widgetSize)) document.nulls_remove_unused();
ImGui::EndDisabled();
set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)", 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("Name", &null.name);
ImGui::SetItemTooltip("Set the null's name.");
ImGui::Checkbox("Rect", &null.isShowRect);
ImGui::SetItemTooltip("The null will have a rectangle show around it.");
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{
document.null_set(null);
manager.null_properties_close();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) manager.null_properties_close();
ImGui::EndPopup();
}
manager.null_properties_end();
}
}

15
src/imgui/window/nulls.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "clipboard.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Nulls
{
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}

View File

@@ -0,0 +1,55 @@
#include "onionskin.h"
#include <glm/gtc/type_ptr.hpp>
#include "imgui_.h"
using namespace anm2ed::types;
using namespace glm;
namespace anm2ed::imgui
{
constexpr auto FRAMES_MAX = 100;
void Onionskin::update(Settings& settings)
{
auto& isEnabled = settings.onionskinIsEnabled;
auto& beforeCount = settings.onionskinBeforeCount;
auto& beforeColor = settings.onionskinBeforeColor;
auto& afterCount = settings.onionskinAfterCount;
auto& afterColor = settings.onionskinAfterColor;
auto& drawOrder = settings.onionskinDrawOrder;
if (ImGui::Begin("Onionskin", &settings.windowIsOnionskin))
{
auto configure_widgets = [&](const char* separator, int& frames, vec3& color)
{
ImGui::PushID(separator);
ImGui::SeparatorText(separator);
input_int_range("Frames", frames, 0, FRAMES_MAX);
ImGui::SetItemTooltip("Change the amount of frames this onionskin will use.");
ImGui::ColorEdit3("Color", value_ptr(color));
ImGui::SetItemTooltip("Change the color of the frames this onionskin will use.");
ImGui::PopID();
};
ImGui::Checkbox("Enabled", &isEnabled);
set_item_tooltip_shortcut("Toggle onionskinning.", settings.shortcutOnionskin);
configure_widgets("Before", beforeCount, beforeColor);
configure_widgets("After", afterCount, afterColor);
ImGui::Text("Draw Order");
ImGui::SameLine();
ImGui::RadioButton("Below", &drawOrder, draw_order::BELOW);
ImGui::SetItemTooltip("The onionskin frames will draw below the original frames.");
ImGui::SameLine();
ImGui::RadioButton("Above", &drawOrder, draw_order::ABOVE);
ImGui::SetItemTooltip("The onionskin frames will draw above the original frames.");
}
ImGui::End();
if (shortcut(settings.shortcutOnionskin, shortcut::GLOBAL)) isEnabled = !isEnabled;
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "settings.h"
namespace anm2ed::imgui
{
class Onionskin
{
public:
void update(Settings&);
};
}

135
src/imgui/window/sounds.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "sounds.h"
#include <ranges>
using namespace anm2ed::dialog;
using namespace anm2ed::types;
using namespace anm2ed::resource;
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.referenceNull;
auto& unused = document.unusedNullIDs;
auto& hovered = document.hoveredNull;
auto& multiSelect = document.soundMultiSelect;
hovered = -1;
if (ImGui::Begin("Sounds", &settings.windowIsSounds))
{
auto childSize = imgui::size_without_footer_get();
if (ImGui::BeginChild("##Sounds Child", childSize, true))
{
multiSelect.start(anm2.content.sounds.size());
for (auto& [id, sound] : anm2.content.sounds)
{
auto isSelected = multiSelect.contains(id);
auto isReferenced = reference == id;
ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id);
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
if (ImGui::Selectable(std::format(anm2::SOUND_FORMAT, id, sound.path.string()).c_str(), isSelected))
sound.audio.play();
if (ImGui::IsItemHovered())
{
hovered = id;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
;
}
if (isReferenced) ImGui::PopFont();
if (ImGui::BeginItemTooltip())
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(sound.path.c_str());
ImGui::PopFont();
ImGui::Text("ID: %d", id);
ImGui::Text("Click to play.");
ImGui::EndTooltip();
}
ImGui::PopID();
}
multiSelect.finish();
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& id : multiSelect)
clipboardText += anm2.content.sounds[id].to_string(id);
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.content.sounds[hovered].to_string(hovered));
};
auto paste = [&](merge::Type type)
{
auto clipboardText = clipboard.get();
document.sounds_deserialize(clipboardText, type);
};
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled();
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
ImGui::EndDisabled();
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
{
if (ImGui::BeginMenu("Paste"))
{
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
ImGui::EndMenu();
}
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
}
ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) dialog.file_open(dialog::SOUND_OPEN);
imgui::set_item_tooltip_shortcut("Add a sound.", settings.shortcutAdd);
ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unused.empty());
if (ImGui::Button("Remove Unused", widgetSize))
;
ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Remove unused sounds (i.e., ones not used in any trigger.)",
settings.shortcutRemove);
}
ImGui::End();
if (dialog.is_selected(dialog::SOUND_OPEN))
{
document.sound_add(dialog.path);
dialog.reset();
}
}
}

16
src/imgui/window/sounds.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "clipboard.h"
#include "dialog.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Sounds
{
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
};
}

View File

@@ -0,0 +1,230 @@
#include "spritesheet_editor.h"
#include "math_.h"
#include "tool.h"
#include "types.h"
using namespace anm2ed::canvas;
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::imgui
{
constexpr auto PIVOT_COLOR = color::PINK;
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2())
{
}
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto& referenceSpritesheet = document.referenceSpritesheet;
auto& pan = document.editorPan;
auto& zoom = document.editorZoom;
auto& backgroundColor = settings.editorBackgroundColor;
auto& gridColor = settings.editorGridColor;
auto& gridSize = settings.editorGridSize;
auto& gridOffset = settings.editorGridOffset;
auto& isGrid = settings.editorIsGrid;
auto& zoomStep = settings.viewZoomStep;
auto& isBorder = settings.editorIsBorder;
auto spritesheet = document.spritesheet_get();
auto& tool = settings.tool;
auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE];
auto& dashedShader = resources.shaders[shader::DASHED];
if (ImGui::Begin("Spritesheet Editor", &settings.windowIsSpritesheetEditor))
{
auto childSize = ImVec2(imgui::row_widget_width_get(3),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::Checkbox("Grid", &isGrid);
ImGui::SameLine();
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
ImGui::InputInt2("Size", value_ptr(gridSize));
ImGui::InputInt2("Offset", value_ptr(gridOffset));
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0);
imgui::shortcut(settings.shortcutCenterView);
if (ImGui::Button("Center View", widgetSize)) pan = -size * 0.5f;
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
ImGui::SameLine();
imgui::shortcut(settings.shortcutFit);
if (ImGui::Button("Fit", widgetSize))
if (spritesheet) set_to_rect(zoom, pan, {0, 0, spritesheet->texture.size.x, spritesheet->texture.size.y});
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the spritesheet.", settings.shortcutFit);
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
ImGui::Checkbox("Border", &isBorder);
}
ImGui::EndChild();
auto cursorScreenPos = ImGui::GetCursorScreenPos();
size_set(to_vec2(ImGui::GetContentRegionAvail()));
bind();
viewport_set();
clear(backgroundColor);
auto frame = document.frame_get();
if (spritesheet && spritesheet->texture.is_valid())
{
auto& texture = spritesheet->texture;
auto transform = transform_get(zoom, pan);
auto spritesheetModel = math::quad_model_get(texture.size);
auto spritesheetTransform = transform * spritesheetModel;
texture_render(shaderTexture, texture.id, spritesheetTransform);
if (isBorder) rect_render(dashedShader, spritesheetTransform, spritesheetModel);
if (frame && reference.itemID > -1 &&
anm2.content.layers.at(reference.itemID).spritesheetID == referenceSpritesheet)
{
auto cropModel = math::quad_model_get(frame->size, frame->crop);
auto cropTransform = transform * cropModel;
rect_render(dashedShader, cropTransform, cropModel, color::RED);
auto pivotTransform =
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
}
}
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
unbind();
ImGui::Image(texture, to_imvec2(size));
if (ImGui::IsItemHovered())
{
ImGui::SetKeyboardFocusHere(-1);
previousMousePos = mousePos;
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel;
auto& toolColor = settings.toolColor;
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
auto useTool = tool;
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
auto frame = document.frame_get();
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isBegin = isMouseClick || isKeyPressed;
auto isEnd = isMouseReleased || isKeyReleased;
if (isMouseMiddleDown) useTool = tool::PAN;
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
switch (useTool)
{
case tool::PAN:
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
break;
case tool::MOVE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Pivot");
if (isMouseDown) frame->pivot = ivec2(mousePos - frame->crop);
if (isLeft) frame->pivot.x -= step;
if (isRight) frame->pivot.x += step;
if (isUp) frame->pivot.y -= step;
if (isDown) frame->pivot.y += step;
if (isEnd) document.change(Document::FRAMES);
break;
case tool::CROP:
if (!frame) break;
if (isBegin) document.snapshot(isMod ? "Frame Size" : "Frame Crop");
if (isMouseClicked) frame->crop = ivec2(mousePos);
if (isMouseDown) frame->size = ivec2(mousePos - frame->crop);
if (isLeft) isMod ? frame->size.x -= step : frame->crop.x -= step;
if (isRight) isMod ? frame->size.x += step : frame->crop.x += step;
if (isUp) isMod ? frame->size.y -= step : frame->crop.y -= step;
if (isDown) isMod ? frame->size.y += step : frame->crop.y += step;
if (isEnd) document.change(Document::FRAMES);
break;
case tool::DRAW:
case tool::ERASE:
{
if (!spritesheet) break;
auto color = tool == tool::DRAW ? toolColor : vec4();
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
if (isMouseReleased) document.change(Document::FRAMES);
break;
}
case tool::COLOR_PICKER:
{
if (isMouseDown)
{
auto position = to_vec2(ImGui::GetMousePos());
toolColor = pixel_read(position, {settings.windowSize.x, settings.windowSize.y});
if (ImGui::BeginTooltip())
{
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
ImGui::EndTooltip();
}
}
break;
}
default:
break;
}
if (mouseWheel != 0 || isZoomIn || isZoomOut)
zoom_set(zoom, pan, mousePos, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
}
}
ImGui::End();
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include "canvas.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class SpritesheetEditor : public Canvas
{
glm::vec2 mousePos{};
glm::vec2 previousMousePos{};
public:
SpritesheetEditor();
void update(Manager&, Settings&, Resources&);
};
}

View File

@@ -0,0 +1,297 @@
#include "spritesheets.h"
#include <ranges>
#include "toast.h"
using namespace anm2ed::types;
using namespace anm2ed::resource;
using namespace glm;
namespace anm2ed::imgui
{
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
Clipboard& clipboard)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& multiSelect = document.spritesheetMultiSelect;
auto& unused = document.unusedSpritesheetIDs;
auto& hovered = document.hoveredSpritesheet;
auto& reference = document.referenceSpritesheet;
hovered = -1;
if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets))
{
auto style = ImGui::GetStyle();
auto context_menu = [&]()
{
auto copy = [&]()
{
if (!multiSelect.empty())
{
std::string clipboardText{};
for (auto& id : multiSelect)
clipboardText += anm2.content.spritesheets[id].to_string(id);
clipboard.set(clipboardText);
}
else if (hovered > -1)
clipboard.set(anm2.content.spritesheets[hovered].to_string(hovered));
};
auto paste = [&](merge::Type type)
{
auto clipboardText = clipboard.get();
document.spritesheets_deserialize(clipboardText, type);
};
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
{
ImGui::BeginDisabled();
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
ImGui::EndDisabled();
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
ImGui::EndDisabled();
ImGui::BeginDisabled(clipboard.is_empty());
{
if (ImGui::BeginMenu("Paste"))
{
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
ImGui::EndMenu();
}
}
ImGui::EndDisabled();
ImGui::EndPopup();
}
ImGui::PopStyleVar(2);
};
auto childSize = imgui::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());
multiSelect.start(anm2.content.spritesheets.size());
for (auto& [id, spritesheet] : anm2.content.spritesheets)
{
ImGui::PushID(id);
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
{
auto isSelected = multiSelect.contains(id);
auto isReferenced = id == reference;
auto cursorPos = ImGui::GetCursorPos();
auto& texture = spritesheet.texture.is_valid() ? spritesheet.texture : resources.icons[icon::NONE];
auto path = spritesheet.path.empty() ? anm2::NO_PATH : spritesheet.path.c_str();
ImGui::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) reference = id;
if (ImGui::IsItemHovered()) hovered = id;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
if (ImGui::BeginItemTooltip())
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
auto viewport = ImGui::GetMainViewport();
auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
? to_vec2(viewport->Size) * 0.5f
: vec2(texture.size);
auto aspectRatio = (float)texture.size.x / texture.size.y;
if (textureSize.x / textureSize.y > aspectRatio)
textureSize.x = textureSize.y * aspectRatio;
else
textureSize.y = textureSize.x / aspectRatio;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize),
ImGuiChildFlags_Borders))
ImGui::Image(texture.id, ImGui::GetContentRegionAvail());
ImGui::PopStyleVar();
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::SameLine();
if (ImGui::BeginChild("##Spritesheet Info Tooltip Child"))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(path);
ImGui::PopFont();
ImGui::Text("ID: %d", id);
ImGui::Text("Size: %d x %d", texture.size.x, texture.size.y);
}
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::Image(texture.id, imageSize);
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);
ImGui::Text(anm2::SPRITESHEET_FORMAT_C, id, path);
if (isReferenced) ImGui::PopFont();
context_menu();
}
ImGui::EndChild();
ImGui::PopID();
}
multiSelect.finish();
ImGui::PopStyleVar(2);
context_menu();
}
ImGui::EndChild();
auto rowOneWidgetSize = imgui::widget_size_with_row_get(4);
imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_OPEN);
imgui::set_item_tooltip_shortcut("Add a new spritesheet.", settings.shortcutAdd);
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
{
document.spritesheet_add(dialog.path);
dialog.reset();
}
ImGui::SameLine();
ImGui::BeginDisabled(multiSelect.empty());
{
if (ImGui::Button("Reload", rowOneWidgetSize))
{
for (auto& id : multiSelect)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get());
toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
}
}
ImGui::SetItemTooltip("Reloads the selected spritesheets.");
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(multiSelect.size() != 1);
{
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_REPLACE);
ImGui::SetItemTooltip("Replace the selected spritesheet with a new one.");
}
ImGui::EndDisabled();
if (dialog.is_selected(dialog::SPRITESHEET_REPLACE))
{
auto& id = *multiSelect.begin();
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet = anm2::Spritesheet(document.directory_get(), dialog.path);
toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
dialog.reset();
}
ImGui::SameLine();
ImGui::BeginDisabled(unused.empty());
{
imgui::shortcut(settings.shortcutRemove);
if (ImGui::Button("Remove Unused", rowOneWidgetSize))
{
for (auto& id : unused)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
toasts.info(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string()));
anm2.spritesheet_remove(id);
}
unused.clear();
document.change(Document::SPRITESHEETS);
}
imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
settings.shortcutRemove);
}
ImGui::EndDisabled();
auto rowTwoWidgetSize = imgui::widget_size_with_row_get(3);
imgui::shortcut(settings.shortcutSelectAll);
ImGui::BeginDisabled(multiSelect.size() == anm2.content.spritesheets.size());
{
if (ImGui::Button("Select All", rowTwoWidgetSize))
for (auto& id : anm2.content.spritesheets | std::views::keys)
multiSelect.insert(id);
}
ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll);
ImGui::SameLine();
imgui::shortcut(settings.shortcutSelectNone);
ImGui::BeginDisabled(multiSelect.empty());
if (ImGui::Button("Select None", rowTwoWidgetSize)) multiSelect.clear();
imgui::set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone);
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(multiSelect.empty());
{
if (ImGui::Button("Save", rowTwoWidgetSize))
{
for (auto& id : multiSelect)
{
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
if (spritesheet.save(document.directory_get()))
toasts.info(std::format("Saved spritesheet #{}: {}", id, spritesheet.path.string()));
else
toasts.info(std::format("Unable to save spritesheet #{}: {}", id, spritesheet.path.string()));
}
}
}
ImGui::EndDisabled();
ImGui::SetItemTooltip("Save the selected spritesheets.");
}
ImGui::End();
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "clipboard.h"
#include "dialog.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Spritesheets
{
public:
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
#pragma once
#include "clipboard.h"
#include "document.h"
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Timeline
{
bool isDragging{};
bool isWindowHovered{};
bool isHorizontalScroll{};
PopupHelper propertiesPopup{PopupHelper("Item Properties", POPUP_NORMAL)};
PopupHelper bakePopup{PopupHelper("Bake", POPUP_TO_CONTENT)};
std::string addItemName{};
int addItemSpritesheetID{};
bool addItemIsRect{};
int addItemID{-1};
bool isUnusedItemsSet{};
std::set<int> unusedItems{};
glm::vec2 scroll{};
ImDrawList* pickerLineDrawList{};
ImGuiStyle style{};
void context_menu(Document&, Settings&, Clipboard&);
void item_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&);
void items_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
void frame_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&, float);
void frames_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
void popups(Document&, anm2::Animation*, Settings&);
public:
void update(Manager&, Settings&, Resources&, Clipboard&);
};
}

View File

@@ -0,0 +1,99 @@
#include "tools.h"
#include <glm/gtc/type_ptr.hpp>
#include "tool.h"
#include "types.h"
using namespace anm2ed::resource;
using namespace anm2ed::types;
using namespace glm;
namespace anm2ed::imgui
{
void Tools::update(Manager& manager, Settings& settings, Resources& resources)
{
auto& document = *manager.get();
if (ImGui::Begin("Tools", &settings.windowIsTools))
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
auto availableWidth = ImGui::GetContentRegionAvail().x;
auto size = vec2(ImGui::GetTextLineHeightWithSpacing() * 1.5f);
auto usedWidth = ImGui::GetStyle().WindowPadding.x;
auto tool_use = [&](tool::Type type)
{
switch (type)
{
case tool::UNDO:
if (document.is_able_to_undo()) document.undo();
break;
case tool::REDO:
if (document.is_able_to_redo()) document.redo();
break;
case tool::COLOR:
colorEditPopup.open();
break;
default:
settings.tool = type;
break;
}
};
for (int i = 0; i < tool::COUNT; i++)
{
auto& info = tool::INFO[i];
auto isSelected = settings.tool == i;
if (isSelected) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive));
auto member = SHORTCUT_MEMBERS[info.shortcut];
if (shortcut(settings.*member, shortcut::GLOBAL_SET)) tool_use((tool::Type)i);
if (i == tool::COLOR)
{
size += to_vec2(ImGui::GetStyle().FramePadding) * 2.0f;
if (ImGui::ColorButton(info.label, to_imvec4(settings.toolColor), ImGuiColorEditFlags_NoTooltip,
to_imvec2(size)))
tool_use((tool::Type)i);
colorEditPosition = ImGui::GetCursorScreenPos();
}
else
{
if (i == tool::UNDO) ImGui::BeginDisabled(!document.is_able_to_undo());
if (i == tool::REDO) ImGui::BeginDisabled(!document.is_able_to_redo());
if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size))) tool_use((tool::Type)i);
if (i == tool::UNDO) ImGui::EndDisabled();
if (i == tool::REDO) ImGui::EndDisabled();
}
auto widthIncrement = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;
usedWidth += widthIncrement;
if (usedWidth + widthIncrement < availableWidth)
ImGui::SameLine();
else
usedWidth = ImGui::GetStyle().WindowPadding.x;
set_item_tooltip_shortcut(info.tooltip, settings.*SHORTCUT_MEMBERS[info.shortcut]);
if (isSelected) ImGui::PopStyleColor();
}
ImGui::PopStyleVar();
colorEditPopup.trigger();
if (ImGui::BeginPopup(colorEditPopup.label))
{
ImGui::ColorPicker4(colorEditPopup.label, value_ptr(settings.toolColor));
ImGui::EndPopup();
}
}
ImGui::End();
}
}

19
src/imgui/window/tools.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "manager.h"
#include "resources.h"
#include "settings.h"
namespace anm2ed::imgui
{
class Tools
{
bool isOpenColorEdit{};
ImVec2 colorEditPosition{};
PopupHelper colorEditPopup{PopupHelper("##Color Edit", POPUP_TO_CONTENT, POPUP_BY_ITEM)};
public:
void update(Manager&, Settings&, Resources&);
};
}

View File

@@ -0,0 +1,102 @@
#include "welcome.h"
#include <ranges>
using namespace anm2ed::resource;
namespace anm2ed::imgui
{
void Welcome::update(Manager& manager, Resources& resources, Dialog& dialog, Taskbar& taskbar, Documents& documents)
{
auto viewport = ImGui::GetMainViewport();
auto windowHeight = viewport->Size.y - taskbar.height - documents.height;
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
if (ImGui::Begin("Welcome", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse))
{
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
ImGui::TextUnformatted("Anm2Ed");
ImGui::PopFont();
ImGui::TextUnformatted("Select a recent file or open a new or existing document. You can also drag and drop "
"files into the window to open them.");
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button("New", widgetSize)) dialog.file_open(dialog::ANM2_NEW); // handled in taskbar.cpp
ImGui::SameLine();
if (ImGui::Button("Open", widgetSize)) dialog.file_open(dialog::ANM2_OPEN); // handled in taskbar.cpp
if (ImGui::BeginChild("##Recent Files Child", ImVec2(), ImGuiChildFlags_Borders))
{
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
{
ImGui::PushID(i);
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
if (ImGui::Selectable(label.c_str()))
{
manager.open(file);
ImGui::PopID();
break;
}
ImGui::PopID();
}
}
ImGui::EndChild();
}
ImGui::End();
if (!manager.autosaveFiles.empty() && !restorePopup.is_open()) restorePopup.open();
restorePopup.trigger();
if (ImGui::BeginPopupModal(restorePopup.label, &restorePopup.isOpen, ImGuiWindowFlags_NoResize))
{
ImGui::TextUnformatted("Autosaved documents detected. Would you like to restore them?");
auto childSize = child_size_get(5);
if (ImGui::BeginChild("##Restore Files Child", childSize, ImGuiChildFlags_Borders,
ImGuiWindowFlags_HorizontalScrollbar))
{
for (auto& file : manager.autosaveFiles)
{
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
ImGui::TextUnformatted(label.c_str());
}
}
ImGui::EndChild();
auto widgetSize = widget_size_with_row_get(2);
if (ImGui::Button("Yes", widgetSize))
{
manager.autosave_files_open();
restorePopup.close();
}
ImGui::SameLine();
if (ImGui::Button("No", widgetSize))
{
manager.autosave_files_clear();
restorePopup.close();
}
ImGui::EndPopup();
}
}
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "documents.h"
#include "manager.h"
#include "taskbar.h"
namespace anm2ed::imgui
{
class Welcome
{
PopupHelper restorePopup{PopupHelper("Restore", imgui::POPUP_SMALL_NO_HEIGHT)};
public:
void update(Manager&, Resources&, Dialog&, Taskbar&, Documents&);
};
};