context menus, document refactoring, fixes

This commit is contained in:
2025-10-26 00:10:44 -04:00
parent 87c2db2a77
commit fe9366f9ef
62 changed files with 2138 additions and 793 deletions

View File

@@ -7,7 +7,7 @@
#include "tool.h" #include "tool.h"
#include "types.h" #include "types.h"
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::canvas; using namespace anm2ed::canvas;
using namespace anm2ed::playback; using namespace anm2ed::playback;
@@ -17,6 +17,7 @@ using namespace glm;
namespace anm2ed::animation_preview namespace anm2ed::animation_preview
{ {
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
constexpr auto TARGET_SIZE = vec2(32, 32); constexpr auto TARGET_SIZE = vec2(32, 32);
constexpr auto PIVOT_SIZE = vec2(8, 8); constexpr auto PIVOT_SIZE = vec2(8, 8);
constexpr auto POINT_SIZE = vec2(4, 4); constexpr auto POINT_SIZE = vec2(4, 4);
@@ -27,10 +28,11 @@ namespace anm2ed::animation_preview
{ {
} }
void AnimationPreview::update(DocumentManager& manager, Settings& settings, Resources& resources, Playback& playback) void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference; auto& reference = document.reference;
auto animation = document.animation_get(); auto animation = document.animation_get();
auto& pan = document.previewPan; auto& pan = document.previewPan;
@@ -143,12 +145,10 @@ namespace anm2ed::animation_preview
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor); if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor); if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
auto frameTime = reference.frameTime > -1 && !playback.isPlaying ? reference.frameTime : playback.time; auto render = [&](float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = false)
if (animation)
{ {
auto transform = transform_get(zoom, pan); auto transform = transform_get(zoom, pan);
auto root = animation->rootAnimation.frame_generate(playback.time, anm2::ROOT); auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT);
if (isRootTransform) if (isRootTransform)
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation); transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
@@ -158,7 +158,9 @@ namespace anm2ed::animation_preview
auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f, auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
math::percent_to_unit(root.scale), root.rotation); math::percent_to_unit(root.scale), root.rotation);
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color::GREEN); vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color);
} }
for (auto& id : animation->layerOrder) for (auto& id : animation->layerOrder)
@@ -168,7 +170,7 @@ namespace anm2ed::animation_preview
auto& layer = anm2.content.layers.at(id); auto& layer = anm2.content.layers.at(id);
if (auto frame = layerAnimation.frame_generate(frameTime, anm2::LAYER); frame.isVisible) if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.isVisible)
{ {
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID); auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
if (!spritesheet) continue; if (!spritesheet) continue;
@@ -183,17 +185,23 @@ namespace anm2ed::animation_preview
auto uvMin = frame.crop / vec2(texture.size) + inset; auto uvMin = frame.crop / vec2(texture.size) + inset;
auto uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset; auto uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset;
auto vertices = math::uv_vertices_get(uvMin, uvMax); auto vertices = math::uv_vertices_get(uvMin, uvMax);
vec3 frameColorOffset = frame.offset + colorOffset;
vec4 frameTint = frame.tint;
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
texture_render(shaderTexture, texture.id, layerTransform, frame.tint, frame.offset, vertices.data()); texture_render(shaderTexture, texture.id, layerTransform, frameTint, frameColorOffset, vertices.data());
if (isBorder) rect_render(shaderLine, layerTransform, color::RED); auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED;
if (isBorder) rect_render(shaderLine, layerTransform, color);
if (isPivots) if (isPivots)
{ {
auto pivotTransform = auto pivotTransform =
transform * math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f, transform * math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation); math::percent_to_unit(frame.scale), frame.rotation);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color::RED);
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
} }
} }
} }
@@ -206,11 +214,13 @@ namespace anm2ed::animation_preview
auto& isShowRect = anm2.content.nulls[id].isShowRect; auto& isShowRect = anm2.content.nulls[id].isShowRect;
if (auto frame = nullAnimation.frame_generate(frameTime, anm2::NULL_); frame.isVisible) if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
{ {
auto icon = isShowRect ? icon::POINT : icon::TARGET; auto icon = isShowRect ? icon::POINT : icon::TARGET;
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE; auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
auto& color = id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED : color::BLUE; auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
: NULL_COLOR;
auto nullTransform = transform * math::quad_model_get(size, frame.position, size * 0.5f, auto nullTransform = transform * math::quad_model_get(size, frame.position, size * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation); math::percent_to_unit(frame.scale), frame.rotation);
@@ -228,6 +238,34 @@ namespace anm2ed::animation_preview
} }
} }
} }
};
auto onionskin_render = [&](float time, int count, int direction, vec3 color)
{
for (int i = 1; i <= count; i++)
{
float useTime = time + (float)(direction * i);
float alphaOffset = (1.0f / (count + 1)) * i;
render(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(frameTime);
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
} }
unbind(); unbind();

View File

@@ -1,8 +1,7 @@
#pragma once #pragma once
#include "canvas.h" #include "canvas.h"
#include "document_manager.h" #include "manager.h"
#include "playback.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -15,7 +14,6 @@ namespace anm2ed::animation_preview
public: public:
AnimationPreview(); AnimationPreview();
void update(document_manager::DocumentManager& manager, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&);
resources::Resources& resources, playback::Playback& playback);
}; };
} }

View File

@@ -1,21 +1,24 @@
#include "animations.h" #include "animations.h"
#include <algorithm>
#include <ranges> #include <ranges>
using namespace anm2ed::document; using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::types; using namespace anm2ed::types;
namespace anm2ed::animations namespace anm2ed::animations
{ {
void Animations::update(Document& document, int& documentIndex, Settings& settings, Resources& resources) void Animations::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{ {
auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& reference = document.reference; auto& reference = document.reference;
auto& selection = document.selectedAnimations; auto& hovered = document.hoveredAnimation;
storage.user_data_set(&selection); auto& multiSelect = document.animationMultiSelect;
auto& mergeMultiSelect = document.animationMergeMultiSelect;
auto& mergeTarget = document.mergeTarget;
if (ImGui::Begin("Animations", &settings.windowIsAnimations)) if (ImGui::Begin("Animations", &settings.windowIsAnimations))
{ {
@@ -23,7 +26,7 @@ namespace anm2ed::animations
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders)) if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
{ {
storage.begin(anm2.animations.items.size()); multiSelect.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items)) for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{ {
@@ -31,7 +34,6 @@ namespace anm2ed::animations
auto isDefault = anm2.animations.defaultAnimation == animation.name; auto isDefault = anm2.animations.defaultAnimation == animation.name;
auto isReferenced = reference.animationIndex == i; auto isReferenced = reference.animationIndex == i;
auto isSelected = selection.contains(i);
auto font = isDefault && isReferenced ? font::BOLD_ITALICS auto font = isDefault && isReferenced ? font::BOLD_ITALICS
: isDefault ? font::BOLD : isDefault ? font::BOLD
@@ -41,9 +43,10 @@ namespace anm2ed::animations
ImGui::PushFont(resources.fonts[font].get(), font::SIZE); ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
ImGui::SetNextItemSelectionUserData(i); ImGui::SetNextItemSelectionUserData(i);
if (imgui::selectable_input_text(animation.name, if (imgui::selectable_input_text(animation.name,
std::format("###Document #{} Animation #{}", documentIndex, i), std::format("###Document #{} Animation #{}", manager.selected, i),
animation.name, isSelected)) animation.name, multiSelect.contains(i)))
if (!isReferenced) reference = {(int)i}; reference = {(int)i};
if (ImGui::IsItemHovered()) hovered = i;
ImGui::PopFont(); ImGui::PopFont();
if (ImGui::BeginItemTooltip()) if (ImGui::BeginItemTooltip())
@@ -68,10 +71,11 @@ namespace anm2ed::animations
if (ImGui::BeginDragDropSource()) if (ImGui::BeginDragDropSource())
{ {
std::vector<int> sorted = {}; static std::vector<int> selection;
ImGui::SetDragDropPayload("Animation Drag Drop", sorted.data(), sorted.size() * sizeof(int)); selection.assign(multiSelect.begin(), multiSelect.end());
for (auto& index : sorted) ImGui::SetDragDropPayload("Animation Drag Drop", selection.data(), selection.size() * sizeof(int));
ImGui::TextUnformatted(anm2.animations.items[index].name.c_str()); for (auto& i : selection)
ImGui::TextUnformatted(anm2.animations.items[i].name.c_str());
ImGui::EndDragDropSource(); ImGui::EndDragDropSource();
} }
@@ -79,16 +83,11 @@ namespace anm2ed::animations
{ {
if (auto payload = ImGui::AcceptDragDropPayload("Animation Drag Drop")) if (auto payload = ImGui::AcceptDragDropPayload("Animation Drag Drop"))
{ {
auto count = payload->DataSize / sizeof(int); auto payloadIndices = (int*)(payload->Data);
auto data = (int*)(payload->Data); auto payloadCount = payload->DataSize / sizeof(int);
std::vector<int> indices(data, data + count); std::vector<int> indices(payloadIndices, payloadIndices + payloadCount);
//std::vector<int> destinationIndices = vector::indices_move(anm2.animations.items, indices, i); std::sort(indices.begin(), indices.end());
document.animations_move(indices, i);
selection.clear();
/*
for (const auto& index : destinationIndices)
selection.insert((int)index);
*/
} }
ImGui::EndDragDropTarget(); ImGui::EndDragDropTarget();
} }
@@ -96,63 +95,90 @@ namespace anm2ed::animations
ImGui::PopID(); ImGui::PopID();
} }
storage.end(); 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();
if (!multiSelect.empty())
{
for (auto& i : multiSelect | std::views::reverse)
anm2.animations.items.erase(anm2.animations.items.begin() + i);
multiSelect.clear();
}
else if (hovered > -1)
{
anm2.animations.items.erase(anm2.animations.items.begin() + hovered);
hovered = -1;
}
};
auto paste = [&]()
{
auto clipboardText = clipboard.get();
document.animations_deserialize(clipboardText);
};
if (imgui::shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
if (imgui::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(); ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(5); auto widgetSize = imgui::widget_size_with_row_get(5);
imgui::shortcut(settings.shortcutAdd); imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) if (ImGui::Button("Add", widgetSize)) document.animation_add();
{
anm2::Animation 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 = selection.empty() ? (int)anm2.animations.items.size() - 1 : (int)std::ranges::max(selection) + 1;
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
selection = {index};
reference = {index};
document.change(change::ANIMATIONS);
}
imgui::set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd); imgui::set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.empty()); ImGui::BeginDisabled(multiSelect.empty());
{ {
imgui::shortcut(settings.shortcutDuplicate); imgui::shortcut(settings.shortcutDuplicate);
if (ImGui::Button("Duplicate", widgetSize)) if (ImGui::Button("Duplicate", widgetSize)) document.animation_duplicate();
{
auto duplicated = selection;
auto duplicatedEnd = std::ranges::max(duplicated);
for (auto& id : duplicated)
{
anm2.animations.items.insert(anm2.animations.items.begin() + duplicatedEnd, anm2.animations.items[id]);
selection.insert(++duplicatedEnd);
selection.erase(id);
}
document.change(change::ANIMATIONS);
}
imgui::set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate); imgui::set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1); if (imgui::shortcut(settings.shortcutMerge, shortcut::FOCUSED))
if (multiSelect.size() > 0) document.animations_merge_quick();
ImGui::BeginDisabled(multiSelect.size() != 1);
{ {
if (ImGui::Button("Merge", widgetSize)) if (ImGui::Button("Merge", widgetSize))
{ {
mergePopup.open(); mergePopup.open();
mergeSelection.clear(); mergeMultiSelect.clear();
mergeTarget = *selection.begin(); mergeTarget = *multiSelect.begin();
} }
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
@@ -165,28 +191,16 @@ namespace anm2ed::animations
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
if (ImGui::Button("Remove", widgetSize)) if (ImGui::Button("Remove", widgetSize)) document.animation_remove();
{
/*
auto selectionErase = set::to_size_t(selection);
if (selectionErase.contains(document.reference.animationIndex)) document.reference.animationIndex = -1;
vector::range_erase(anm2.animations.items, selectionErase);
*/
document.change(change::ANIMATIONS);
selection.clear();
}
imgui::set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate); imgui::set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1); imgui::shortcut(settings.shortcutDefault);
{ ImGui::BeginDisabled(multiSelect.size() != 1);
imgui::shortcut(settings.shortcutDefault); if (ImGui::Button("Default", widgetSize)) document.animation_default();
if (ImGui::Button("Default", widgetSize))
anm2.animations.defaultAnimation = anm2.animations.items[*selection.begin()].name;
imgui::set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDuplicate);
}
ImGui::EndDisabled(); ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
@@ -196,15 +210,13 @@ namespace anm2ed::animations
{ {
auto merge_close = [&]() auto merge_close = [&]()
{ {
mergeSelection.clear(); mergeMultiSelect.clear();
mergePopup.close(); mergePopup.close();
}; };
auto& type = settings.mergeType; auto& type = settings.mergeType;
auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter; auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter;
mergeStorage.user_data_set(&mergeSelection);
auto footerSize = imgui::footer_size_get(); auto footerSize = imgui::footer_size_get();
auto optionsSize = imgui::child_size_get(2); auto optionsSize = imgui::child_size_get(2);
auto deleteAfterSize = imgui::child_size_get(); auto deleteAfterSize = imgui::child_size_get();
@@ -214,21 +226,19 @@ namespace anm2ed::animations
if (ImGui::BeginChild("Animations", animationsSize, ImGuiChildFlags_Borders)) if (ImGui::BeginChild("Animations", animationsSize, ImGuiChildFlags_Borders))
{ {
mergeSelection.begin(); mergeMultiSelect.start(anm2.animations.items.size());
for (auto [i, animation] : std::views::enumerate(anm2.animations.items)) for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
{ {
auto isSelected = mergeSelection.contains(i);
ImGui::PushID(i); ImGui::PushID(i);
ImGui::SetNextItemSelectionUserData(i); ImGui::SetNextItemSelectionUserData(i);
ImGui::Selectable(animation.name.c_str(), isSelected); ImGui::Selectable(animation.name.c_str(), mergeMultiSelect.contains(i));
ImGui::PopID(); ImGui::PopID();
} }
mergeSelection.end(); mergeMultiSelect.finish();
} }
ImGui::EndChild(); ImGui::EndChild();
@@ -262,12 +272,7 @@ namespace anm2ed::animations
if (ImGui::Button("Merge", widgetSize)) if (ImGui::Button("Merge", widgetSize))
{ {
/* document.animations_merge((merge::Type)type, isDeleteAnimationsAfter);
std::set<int> sources = set::to_set(mergeSelection);
const auto merged = anm2.animations.merge(mergeTarget, sources, (MergeType)type, isDeleteAnimationsAfter);
selection = {merged};
reference = {merged};
*/
merge_close(); merge_close();
} }
ImGui::SameLine(); ImGui::SameLine();

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#include "document.h" #include "clipboard.h"
#include "imgui.h" #include "imgui.h"
#include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -10,13 +11,8 @@ namespace anm2ed::animations
class Animations class Animations
{ {
imgui::PopupHelper mergePopup{imgui::PopupHelper("Merge Animations")}; imgui::PopupHelper mergePopup{imgui::PopupHelper("Merge Animations")};
imgui::MultiSelectStorage mergeStorage{};
imgui::MultiSelectStorage storage{};
std::set<int> mergeSelection{};
int mergeTarget{};
public: public:
void update(document::Document& document, int& documentIndex, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
resources::Resources& resources);
}; };
} }

View File

@@ -13,6 +13,7 @@ using namespace anm2ed::filesystem;
using namespace anm2ed::texture; using namespace anm2ed::texture;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util; using namespace anm2ed::util;
using namespace glm;
namespace anm2ed::anm2 namespace anm2ed::anm2
{ {
@@ -64,14 +65,14 @@ namespace anm2ed::anm2
// If it doesn't work beyond that then that's on the user :^) // If it doesn't work beyond that then that's on the user :^)
if (!path_is_exist(path)) path = string::to_lower(path); if (!path_is_exist(path)) path = string::to_lower(path);
if (!path_is_exist(path)) path = string::replace_backslash(path); if (!path_is_exist(path)) path = string::replace_backslash(path);
texture = Texture(path, true); texture = Texture(path);
} }
Spritesheet::Spritesheet(const std::string& directory, const std::string& path) Spritesheet::Spritesheet(const std::string& directory, const std::string& path)
{ {
this->path = !path.empty() ? path : this->path.string(); this->path = !path.empty() ? path : this->path.string();
WorkingDirectory workingDirectory(directory); WorkingDirectory workingDirectory(directory);
texture = Texture(this->path, true); texture = Texture(this->path);
} }
bool Spritesheet::save(const std::string& directory, const std::string& path) bool Spritesheet::save(const std::string& directory, const std::string& path)
@@ -99,6 +100,22 @@ namespace anm2ed::anm2
return texture.is_valid(); return texture.is_valid();
} }
std::string Spritesheet::to_string(int id)
{
XMLDocument document{};
auto* element = document.NewElement("Spritesheet");
element->SetAttribute("Id", id);
element->SetAttribute("Path", path.c_str());
document.InsertFirstChild(element);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
Layer::Layer() = default; Layer::Layer() = default;
Layer::Layer(XMLElement* element, int& id) Layer::Layer(XMLElement* element, int& id)
@@ -118,6 +135,23 @@ namespace anm2ed::anm2
parent->InsertEndChild(element); parent->InsertEndChild(element);
} }
std::string Layer::to_string(int id)
{
XMLDocument document{};
auto* element = document.NewElement("Layer");
element->SetAttribute("Id", id);
element->SetAttribute("Name", name.c_str());
element->SetAttribute("SpritesheetId", spritesheetID);
document.InsertFirstChild(element);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
Null::Null() = default; Null::Null() = default;
Null::Null(XMLElement* element, int& id) Null::Null(XMLElement* element, int& id)
@@ -138,6 +172,22 @@ namespace anm2ed::anm2
parent->InsertEndChild(element); parent->InsertEndChild(element);
} }
std::string Null::to_string(int id)
{
XMLDocument document{};
auto* element = document.NewElement("Null");
element->SetAttribute("Id", id);
element->SetAttribute("Name", name.c_str());
document.InsertFirstChild(element);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
Event::Event() = default; Event::Event() = default;
Event::Event(XMLElement* element, int& id) Event::Event(XMLElement* element, int& id)
@@ -155,6 +205,22 @@ namespace anm2ed::anm2
parent->InsertEndChild(element); parent->InsertEndChild(element);
} }
std::string Event::to_string(int id)
{
XMLDocument document{};
auto* element = document.NewElement("Event");
element->SetAttribute("Id", id);
element->SetAttribute("Name", name.c_str());
document.InsertFirstChild(element);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
Content::Content() = default; Content::Content() = default;
void Content::serialize(XMLDocument& document, XMLElement* parent) void Content::serialize(XMLDocument& document, XMLElement* parent)
@@ -245,6 +311,134 @@ namespace anm2ed::anm2
events[id] = Event(); events[id] = Event();
} }
bool Content::spritesheets_deserialize(const std::string& string, const std::string& directory, merge::Type type,
std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
int id{};
if (!document.FirstChildElement("Spritesheet"))
{
if (errorString) *errorString = "No valid spritesheet(s).";
return false;
}
WorkingDirectory workingDirectory(directory);
for (auto element = document.FirstChildElement("Spritesheet"); element;
element = element->NextSiblingElement("Spritesheet"))
{
auto spritesheet = Spritesheet(element, id);
if (type == merge::APPEND) id = map::next_id_get(spritesheets);
spritesheets[id] = std::move(spritesheet);
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
bool Content::layers_deserialize(const std::string& string, merge::Type type, std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
int id{};
if (!document.FirstChildElement("Layer"))
{
if (errorString) *errorString = "No valid layer(s).";
return false;
}
for (auto element = document.FirstChildElement("Layer"); element; element = element->NextSiblingElement("Layer"))
{
auto layer = Layer(element, id);
if (type == merge::APPEND) id = map::next_id_get(layers);
layers[id] = layer;
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
bool Content::nulls_deserialize(const std::string& string, merge::Type type, std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
int id{};
if (!document.FirstChildElement("Null"))
{
if (errorString) *errorString = "No valid null(s).";
return false;
}
for (auto element = document.FirstChildElement("Null"); element; element = element->NextSiblingElement("Null"))
{
auto layer = Null(element, id);
if (type == merge::APPEND) id = map::next_id_get(nulls);
nulls[id] = layer;
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
bool Content::events_deserialize(const std::string& string, merge::Type type, std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
int id{};
if (!document.FirstChildElement("Event"))
{
if (errorString) *errorString = "No valid event(s).";
return false;
}
for (auto element = document.FirstChildElement("Event"); element; element = element->NextSiblingElement("Event"))
{
auto layer = Event(element, id);
if (type == merge::APPEND) id = map::next_id_get(events);
events[id] = layer;
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
Frame::Frame() = default; Frame::Frame() = default;
Frame::Frame(XMLElement* element, Type type) Frame::Frame(XMLElement* element, Type type)
@@ -504,6 +698,25 @@ namespace anm2ed::anm2
return nullptr; return nullptr;
} }
void Animation::item_remove(Type type, int id)
{
switch (type)
{
case LAYER:
layerAnimations.erase(id);
for (auto [i, value] : std::views::enumerate(layerOrder))
if (value == id) layerOrder.erase(layerOrder.begin() + i);
break;
case NULL_:
nullAnimations.erase(id);
break;
case ROOT:
case TRIGGER:
default:
break;
}
}
void Animation::serialize(XMLDocument& document, XMLElement* parent) void Animation::serialize(XMLDocument& document, XMLElement* parent)
{ {
auto element = document.NewElement("Animation"); auto element = document.NewElement("Animation");
@@ -551,6 +764,39 @@ namespace anm2ed::anm2
return length; return length;
} }
std::string Animation::to_string()
{
XMLDocument document{};
auto* element = document.NewElement("Animation");
document.InsertFirstChild(element);
element->SetAttribute("Name", name.c_str());
element->SetAttribute("FrameNum", frameNum);
element->SetAttribute("Loop", isLoop);
rootAnimation.serialize(document, element, ROOT);
auto layerAnimationsElement = document.NewElement("LayerAnimations");
for (auto& i : layerOrder)
{
Item& layerAnimation = layerAnimations.at(i);
layerAnimation.serialize(document, layerAnimationsElement, LAYER, i);
}
element->InsertEndChild(layerAnimationsElement);
auto nullAnimationsElement = document.NewElement("NullAnimations");
for (auto& [id, nullAnimation] : nullAnimations)
nullAnimation.serialize(document, nullAnimationsElement, NULL_, id);
element->InsertEndChild(nullAnimationsElement);
triggers.serialize(document, element, TRIGGER);
XMLPrinter printer;
document.Print(&printer);
return std::string(printer.CStr());
}
Animations::Animations() = default; Animations::Animations() = default;
Animations::Animations(XMLElement* element) Animations::Animations(XMLElement* element)
@@ -660,6 +906,37 @@ namespace anm2ed::anm2
return finalIndex; return finalIndex;
} }
bool Animations::animations_deserialize(const std::string& string, int start, std::set<int>& indices,
std::string* errorString)
{
XMLDocument document{};
if (document.Parse(string.c_str()) == XML_SUCCESS)
{
if (!document.FirstChildElement("Animation"))
{
if (errorString) *errorString = "No valid animation(s).";
return false;
}
int count{};
for (auto element = document.FirstChildElement("Animation"); element;
element = element->NextSiblingElement("Animation"))
{
auto index = start + count;
items.insert(items.begin() + start + count, Animation(element));
indices.insert(index);
count++;
}
return true;
}
else if (errorString)
*errorString = document.ErrorStr();
return false;
}
Anm2::Anm2() Anm2::Anm2()
{ {
info.createdOn = time::get("%d-%B-%Y %I:%M:%S"); info.createdOn = time::get("%d-%B-%Y %I:%M:%S");
@@ -697,7 +974,7 @@ namespace anm2ed::anm2
XMLPrinter printer; XMLPrinter printer;
document.Print(&printer); document.Print(&printer);
return printer.CStr(); return std::string(printer.CStr());
} }
Anm2::Anm2(const std::string& path, std::string* errorString) Anm2::Anm2(const std::string& path, std::string* errorString)
@@ -710,8 +987,6 @@ namespace anm2ed::anm2
return; return;
} }
isValid = false;
WorkingDirectory workingDirectory(path, true); WorkingDirectory workingDirectory(path, true);
const XMLElement* element = document.RootElement(); const XMLElement* element = document.RootElement();
@@ -907,4 +1182,53 @@ namespace anm2ed::anm2
spritesheets.push_back(std::format("#{} {}", id, spritesheet.path.c_str())); spritesheets.push_back(std::format("#{} {}", id, spritesheet.path.c_str()));
return spritesheets; return spritesheets;
} }
}
void Anm2::bake(Reference reference, int interval, bool isRoundScale, bool isRoundRotation)
{
Item* item = item_get(reference);
if (!item) return;
Frame* frame = frame_get(reference);
if (!frame) return;
if (frame->delay == FRAME_DELAY_MIN) return;
Reference referenceNext = reference;
referenceNext.frameIndex = reference.frameIndex + 1;
Frame* frameNext = frame_get(referenceNext);
if (!frameNext) frameNext = frame;
Frame baseFrame = *frame;
Frame baseFrameNext = *frameNext;
int delay{};
int index = reference.frameIndex;
while (delay < baseFrame.delay)
{
float interpolation = (float)delay / baseFrame.delay;
Frame baked = baseFrame;
baked.delay = std::min(interval, baseFrame.delay - delay);
baked.isInterpolated = (index == reference.frameIndex) ? baseFrame.isInterpolated : false;
baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation);
baked.position = glm::mix(baseFrame.position, baseFrameNext.position, interpolation);
baked.scale = glm::mix(baseFrame.scale, baseFrameNext.scale, interpolation);
baked.offset = glm::mix(baseFrame.offset, baseFrameNext.offset, interpolation);
baked.tint = glm::mix(baseFrame.tint, baseFrameNext.tint, interpolation);
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
if (isRoundRotation) baked.rotation = (int)baked.rotation;
if (index == reference.frameIndex)
item->frames[index] = baked;
else
item->frames.insert(item->frames.begin() + index, baked);
index++;
delay += baked.delay;
}
}
}

View File

@@ -21,6 +21,7 @@ namespace anm2ed::anm2
constexpr auto MERGED_STRING = "(Merged)"; constexpr auto MERGED_STRING = "(Merged)";
constexpr auto NO_PATH = "(No Path)";
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})"; constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
constexpr auto NULL_FORMAT = "#{} {}"; constexpr auto NULL_FORMAT = "#{} {}";
constexpr auto SPRITESHEET_FORMAT = "#%d %s"; constexpr auto SPRITESHEET_FORMAT = "#%d %s";
@@ -43,8 +44,8 @@ namespace anm2ed::anm2
int frameIndex{-1}; int frameIndex{-1};
int frameTime{-1}; int frameTime{-1};
void previous_frame(int max = FRAME_NUM_MAX - 1); void previous_frame(int = FRAME_NUM_MAX - 1);
void next_frame(int max = FRAME_NUM_MAX - 1); void next_frame(int = FRAME_NUM_MAX - 1);
auto operator<=>(const Reference&) const = default; auto operator<=>(const Reference&) const = default;
}; };
@@ -59,8 +60,8 @@ namespace anm2ed::anm2
int version{}; int version{};
Info(); Info();
Info(tinyxml2::XMLElement* element); Info(tinyxml2::XMLElement*);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
}; };
class Spritesheet class Spritesheet
@@ -70,12 +71,13 @@ namespace anm2ed::anm2
texture::Texture texture; texture::Texture texture;
Spritesheet(); Spritesheet();
Spritesheet(tinyxml2::XMLElement* element, int& id); Spritesheet(tinyxml2::XMLElement*, int&);
Spritesheet(const std::string& directory, const std::string& path = {}); Spritesheet(const std::string&, const std::string& = {});
bool save(const std::string& directory, const std::string& path = {}); bool save(const std::string&, const std::string& = {});
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, int id); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
void reload(const std::string& directory); void reload(const std::string&);
bool is_valid(); bool is_valid();
std::string to_string(int id);
}; };
class Layer class Layer
@@ -85,8 +87,9 @@ namespace anm2ed::anm2
int spritesheetID{}; int spritesheetID{};
Layer(); Layer();
Layer(tinyxml2::XMLElement* element, int& id); Layer(tinyxml2::XMLElement*, int&);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, int id); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
std::string to_string(int);
}; };
class Null class Null
@@ -96,8 +99,9 @@ namespace anm2ed::anm2
bool isShowRect{}; bool isShowRect{};
Null(); Null();
Null(tinyxml2::XMLElement* element, int& id); Null(tinyxml2::XMLElement*, int&);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, int id); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
std::string to_string(int);
}; };
class Event class Event
@@ -106,8 +110,9 @@ namespace anm2ed::anm2
std::string name{"New Event"}; std::string name{"New Event"};
Event(); Event();
Event(tinyxml2::XMLElement* element, int& id); Event(tinyxml2::XMLElement*, int&);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, int id); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
std::string to_string(int);
}; };
struct Content struct Content
@@ -119,14 +124,18 @@ namespace anm2ed::anm2
Content(); Content();
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
Content(tinyxml2::XMLElement* element); Content(tinyxml2::XMLElement*);
bool spritesheet_add(const std::string& directory, const std::string& path, int& id); bool spritesheet_add(const std::string&, const std::string&, int&);
void spritesheet_remove(int& id); void spritesheet_remove(int&);
std::set<int> spritesheets_unused(); std::set<int> spritesheets_unused();
void layer_add(int& id); void layer_add(int&);
void null_add(int& id); void null_add(int&);
void event_add(int& id); void event_add(int&);
bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
bool layers_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
bool nulls_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
bool events_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
}; };
#define MEMBERS \ #define MEMBERS \
@@ -152,8 +161,8 @@ namespace anm2ed::anm2
#undef X #undef X
Frame(); Frame();
Frame(tinyxml2::XMLElement* element, Type type); Frame(tinyxml2::XMLElement*, Type);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, Type type); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
void shorten(); void shorten();
void extend(); void extend();
}; };
@@ -175,10 +184,10 @@ namespace anm2ed::anm2
Item(); Item();
Item(tinyxml2::XMLElement* element, Type type, int* id = nullptr); Item(tinyxml2::XMLElement*, Type, int* = nullptr);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent, Type type, int id = -1); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
int length(Type type); int length(Type);
Frame frame_generate(float time, Type type); Frame frame_generate(float, Type);
}; };
class Animation class Animation
@@ -194,10 +203,12 @@ namespace anm2ed::anm2
Item triggers; Item triggers;
Animation(); Animation();
Animation(tinyxml2::XMLElement* element); Animation(tinyxml2::XMLElement*);
Item* item_get(Type type, int id = -1); Item* item_get(Type, int = -1);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent); void item_remove(Type, int = -1);
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
int length(); int length();
std::string to_string();
}; };
struct Animations struct Animations
@@ -207,42 +218,41 @@ namespace anm2ed::anm2
Animations(); Animations();
Animations(tinyxml2::XMLElement* element); Animations(tinyxml2::XMLElement*);
void serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent); void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
int length(); int length();
int merge(int target, std::set<int>& sources, types::merge::Type type, bool isDeleteAfter = true); int merge(int, std::set<int>&, types::merge::Type = types::merge::APPEND, bool = true);
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
}; };
class Anm2 class Anm2
{ {
bool isValid{false};
public: public:
Info info{}; Info info{};
Content content{}; Content content{};
Animations animations{}; Animations animations{};
Anm2(); Anm2();
bool serialize(const std::string& path, std::string* errorString = nullptr); bool serialize(const std::string&, std::string* = nullptr);
std::string to_string(); std::string to_string();
Anm2(const std::string& path, std::string* errorString = nullptr); Anm2(const std::string&, std::string* = nullptr);
uint64_t hash(); uint64_t hash();
Animation* animation_get(Reference& reference); Animation* animation_get(Reference&);
Item* item_get(Reference& reference); Item* item_get(Reference&);
Frame* frame_get(Reference& reference); Frame* frame_get(Reference&);
bool spritesheet_add(const std::string& directory, const std::string& path, int& id); bool spritesheet_add(const std::string&, const std::string&, int&);
Spritesheet* spritesheet_get(int id); Spritesheet* spritesheet_get(int);
void spritesheet_remove(int id); void spritesheet_remove(int);
std::set<int> spritesheets_unused(); std::set<int> spritesheets_unused();
int layer_add(); int layer_add();
Reference layer_add(Reference reference = REFERENCE_DEFAULT, std::string name = {}, int spritesheetID = 0, Reference layer_add(Reference = REFERENCE_DEFAULT, std::string = {}, int = 0,
types::locale::Type locale = types::locale::GLOBAL); types::locale::Type = types::locale::GLOBAL);
Reference null_add(Reference reference = REFERENCE_DEFAULT, std::string name = {}, Reference null_add(Reference = REFERENCE_DEFAULT, std::string = {}, types::locale::Type = types::locale::GLOBAL);
types::locale::Type locale = types::locale::GLOBAL); void event_add(int&);
void event_add(int& id); std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
std::set<int> events_unused(Reference reference = REFERENCE_DEFAULT); std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
std::set<int> layers_unused(Reference reference = REFERENCE_DEFAULT); std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
std::set<int> nulls_unused(Reference reference = REFERENCE_DEFAULT);
std::vector<std::string> spritesheet_names_get(); std::vector<std::string> spritesheet_names_get();
void bake(Reference, int = 1, bool = true, bool = true);
}; };
} }

View File

@@ -32,24 +32,24 @@ namespace anm2ed::canvas
glm::vec2 size{}; glm::vec2 size{};
Canvas(); Canvas();
Canvas(glm::vec2 size); Canvas(glm::vec2);
~Canvas(); ~Canvas();
bool is_valid(); bool is_valid();
void framebuffer_set(); void framebuffer_set();
void framebuffer_resize_check(); void framebuffer_resize_check();
void size_set(glm::vec2 size); void size_set(glm::vec2);
glm::mat4 transform_get(float zoom, glm::vec2 pan); glm::mat4 transform_get(float, glm::vec2);
void axes_render(shader::Shader& shader, float zoom, glm::vec2 pan, glm::vec4 color = glm::vec4(1.0f)); void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
void grid_render(shader::Shader& shader, float zoom, glm::vec2 pan, glm::ivec2 size = glm::ivec2(32, 32), void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
glm::ivec2 offset = {}, glm::vec4 color = glm::vec4(1.0f)); glm::vec4 = glm::vec4(1.0f));
void texture_render(shader::Shader& shader, GLuint& texture, glm::mat4& transform, glm::vec4 tint = glm::vec4(1.0f), void texture_render(shader::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
glm::vec3 colorOffset = {}, float* vertices = (float*)TEXTURE_VERTICES); float* = (float*)TEXTURE_VERTICES);
void rect_render(shader::Shader& shader, glm::mat4& transform, glm::vec4 color = glm::vec4(1.0f)); void rect_render(shader::Shader&, glm::mat4&, glm::vec4 = glm::vec4(1.0f));
void viewport_set(); void viewport_set();
void clear(glm::vec4& color); void clear(glm::vec4&);
void bind(); void bind();
void unbind(); void unbind();
void zoom_set(float& zoom, glm::vec2& pan, glm::vec2& focus, float step); void zoom_set(float&, glm::vec2&, glm::vec2&, float);
glm::vec2 position_translate(float& zoom, glm::vec2& pan, glm::vec2 position); glm::vec2 position_translate(float&, glm::vec2&, glm::vec2);
}; };
} }

25
src/clipboard.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include "clipboard.h"
#include <SDL3/SDL.h>
namespace anm2ed::clipboard
{
std::string Clipboard::get()
{
auto text = SDL_GetClipboardText();
auto string = std::string(text);
SDL_free(text);
return string;
}
bool Clipboard::is_empty()
{
return get().empty();
}
void Clipboard::set(const std::string& string)
{
SDL_SetClipboardText(string.data());
}
}

14
src/clipboard.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <string>
namespace anm2ed::clipboard
{
class Clipboard
{
public:
bool is_empty();
std::string get();
void set(const std::string&);
};
}

View File

@@ -26,14 +26,14 @@ namespace anm2ed::dialog
int replaceID{-1}; int replaceID{-1};
Dialog(); Dialog();
Dialog(SDL_Window* window); Dialog(SDL_Window*);
void anm2_new(); void anm2_new();
void anm2_open(); void anm2_open();
void anm2_save(); void anm2_save();
void spritesheet_open(); void spritesheet_open();
void spritesheet_replace(); void spritesheet_replace();
void file_explorer_open(const std::string& path); void file_explorer_open(const std::string&);
void reset(); void reset();
bool is_selected_file(Type type); bool is_selected_file(Type);
}; };
} }

View File

@@ -6,7 +6,8 @@
using namespace anm2ed::animations; using namespace anm2ed::animations;
using namespace anm2ed::dialog; using namespace anm2ed::dialog;
using namespace anm2ed::document_manager; using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::documents; using namespace anm2ed::documents;
using namespace anm2ed::playback; using namespace anm2ed::playback;
using namespace anm2ed::resources; using namespace anm2ed::resources;
@@ -15,8 +16,8 @@ using namespace anm2ed::taskbar;
namespace anm2ed::dockspace namespace anm2ed::dockspace
{ {
void Dockspace::update(Taskbar& taskbar, Documents& documents, DocumentManager& manager, Settings& settings, void Dockspace::update(Taskbar& taskbar, Documents& documents, Manager& manager, Settings& settings,
Resources& resources, Dialog& dialog, Playback& playback) Resources& resources, Dialog& dialog, Clipboard& clipboard)
{ {
auto viewport = ImGui::GetMainViewport(); auto viewport = ImGui::GetMainViewport();
@@ -35,17 +36,17 @@ namespace anm2ed::dockspace
{ {
if (auto document = manager.get(); document) if (auto document = manager.get(); document)
{ {
if (settings.windowIsAnimationPreview) animationPreview.update(manager, settings, resources, playback); if (settings.windowIsAnimationPreview) animationPreview.update(manager, settings, resources);
if (settings.windowIsAnimations) animations.update(*document, manager.selected, settings, resources); if (settings.windowIsAnimations) animations.update(manager, settings, resources, clipboard);
if (settings.windowIsEvents) events.update(manager, settings, resources); if (settings.windowIsEvents) events.update(manager, settings, resources, clipboard);
if (settings.windowIsFrameProperties) frameProperties.update(manager, settings); if (settings.windowIsFrameProperties) frameProperties.update(manager, settings);
if (settings.windowIsLayers) layers.update(*document, settings, resources); if (settings.windowIsLayers) layers.update(manager, settings, resources, clipboard);
if (settings.windowIsNulls) nulls.update(*document, manager.selected, settings, resources); if (settings.windowIsNulls) nulls.update(manager, settings, resources, clipboard);
if (settings.windowIsOnionskin) onionskin.update(settings); if (settings.windowIsOnionskin) onionskin.update(settings);
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources); if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
if (settings.windowIsSpritesheets) spritesheets.update(*document, settings, resources, dialog); if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard);
if (settings.windowIsTimeline) timeline.update(manager, settings, resources, playback); if (settings.windowIsTimeline) timeline.update(manager, settings, resources);
if (settings.windowIsTools) tools.update(settings, resources); if (settings.windowIsTools) tools.update(manager, settings, resources);
} }
} }
} }

View File

@@ -31,8 +31,7 @@ namespace anm2ed::dockspace
tools::Tools tools; tools::Tools tools;
public: public:
void update(taskbar::Taskbar& taskbar, documents::Documents& documents, document_manager::DocumentManager& manager, void update(taskbar::Taskbar&, documents::Documents&, manager::Manager&, settings::Settings&, resources::Resources&,
settings::Settings& settings, resources::Resources& resources, dialog::Dialog& dialog, dialog::Dialog&, clipboard::Clipboard&);
playback::Playback& playback);
}; };
} }

View File

@@ -2,24 +2,21 @@
#include "anm2.h" #include "anm2.h"
#include "filesystem.h" #include "filesystem.h"
#include "toast.h"
#include "util.h"
#include <algorithm>
#include <ranges>
using namespace anm2ed::anm2; using namespace anm2ed::anm2;
using namespace anm2ed::filesystem; using namespace anm2ed::filesystem;
using namespace anm2ed::toast;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util;
namespace anm2ed::document namespace anm2ed::document
{ {
Document::Document()
{
for (auto& value : isJustChanged)
value = true;
}
Document::Document(const std::string& path, bool isNew, std::string* errorString) Document::Document(const std::string& path, bool isNew, std::string* errorString)
{ {
for (auto& value : isJustChanged)
value = true;
if (!path_is_exist(path)) return; if (!path_is_exist(path)) return;
if (isNew) if (isNew)
@@ -31,8 +28,8 @@ namespace anm2ed::document
} }
this->path = path; this->path = path;
on_change();
clean(); clean();
change(change::ALL);
} }
bool Document::save(const std::string& path, std::string* errorString) bool Document::save(const std::string& path, std::string* errorString)
@@ -62,7 +59,42 @@ namespace anm2ed::document
void Document::change(change::Type type) void Document::change(change::Type type)
{ {
hash_set(); hash_set();
isJustChanged[type] = true;
auto layer_set = [&]() { unusedLayerIDs = anm2.layers_unused(); };
auto null_set = [&]() { unusedNullIDs = anm2.nulls_unused(); };
auto event_set = [&]() { unusedEventIDs = anm2.events_unused(); };
auto spritesheet_set = [&]()
{
unusedSpritesheetIDs = anm2.spritesheets_unused();
spritesheetNames = anm2.spritesheet_names_get();
spritesheetNamesCstr.clear();
for (auto& name : spritesheetNames)
spritesheetNamesCstr.push_back(name.c_str());
};
switch (type)
{
case change::LAYERS:
layer_set();
break;
case change::NULLS:
null_set();
break;
case change::EVENTS:
event_set();
break;
case change::SPRITESHEETS:
spritesheet_set();
break;
case change::ALL:
layer_set();
null_set();
event_set();
spritesheet_set();
break;
default:
break;
}
} }
bool Document::is_dirty() bool Document::is_dirty()
@@ -70,11 +102,6 @@ namespace anm2ed::document
return hash != saveHash; return hash != saveHash;
} }
bool Document::is_just_changed(types::change::Type type)
{
return isJustChanged[type];
}
std::string Document::directory_get() std::string Document::directory_get()
{ {
return path.parent_path(); return path.parent_path();
@@ -85,9 +112,9 @@ namespace anm2ed::document
return path.filename().string(); return path.filename().string();
} }
anm2::Animation* Document::animation_get() bool Document::is_valid()
{ {
return anm2.animation_get(reference); return !path.empty();
} }
anm2::Frame* Document::frame_get() anm2::Frame* Document::frame_get()
@@ -95,6 +122,42 @@ namespace anm2ed::document
return anm2.frame_get(reference); return anm2.frame_get(reference);
} }
void Document::frames_bake(int interval, bool isRoundScale, bool isRoundRotation)
{
snapshot("Bake Frames");
anm2.bake(reference, interval, isRoundScale, isRoundRotation);
change(change::FRAMES);
}
void Document::frames_add(anm2::Item* item)
{
if (!item) return;
auto frame = frame_get();
if (frame)
{
item->frames.insert(item->frames.begin() + reference.frameIndex, *frame);
reference.frameIndex++;
}
else if (!item->frames.empty())
{
auto frame = item->frames.back();
item->frames.emplace_back(frame);
reference.frameIndex = item->frames.size() - 1;
}
}
void Document::frames_delete(anm2::Item* item)
{
if (!item) return;
snapshot("Delete Frames");
item->frames.erase(item->frames.begin() + reference.frameIndex);
reference.frameIndex = glm::max(-1, --reference.frameIndex);
change(change::FRAMES);
}
anm2::Item* Document::item_get() anm2::Item* Document::item_get()
{ {
return anm2.item_get(reference); return anm2.item_get(reference);
@@ -105,27 +168,317 @@ namespace anm2ed::document
return anm2.spritesheet_get(referenceSpritesheet); return anm2.spritesheet_get(referenceSpritesheet);
} }
bool Document::is_valid() void Document::spritesheet_add(const std::string& path)
{ {
return !path.empty(); int id{};
} snapshot("Add Spritesheet");
if (anm2.spritesheet_add(directory_get(), path, id))
void Document::on_change()
{
if (is_just_changed(change::SPRITESHEETS))
{ {
spritesheetNames = anm2.spritesheet_names_get(); spritesheetMultiSelect = {id};
spritesheetNamesCstr.clear(); toasts.info(std::format("Initialized spritesheet #{}: {}", id, path));
for (auto& name : spritesheetNames) change(change::SPRITESHEETS);
spritesheetNamesCstr.push_back(name.c_str());
} }
else
toasts.error(std::format("Failed to initialize spritesheet: {}", path));
} }
void Document::update() void Document::spritesheets_deserialize(const std::string& string, merge::Type type)
{ {
on_change(); snapshot("Paste Spritesheet(s)");
std::string errorString{};
for (auto& value : isJustChanged) if (anm2.content.spritesheets_deserialize(string, directory_get(), type, &errorString))
value = false; change(change::SPRITESHEETS);
else
toasts.error(std::format("Failed to deserialize spritesheet(s): {}", errorString));
} }
};
void Document::layers_deserialize(const std::string& string, merge::Type type)
{
snapshot("Paste Layer(s)");
std::string errorString{};
if (anm2.content.layers_deserialize(string, type, &errorString))
change(change::NULLS);
else
toasts.error(std::format("Failed to deserialize layer(s): {}", errorString));
}
void Document::layer_set(anm2::Layer& layer)
{
if (referenceLayer > -1)
{
snapshot("Set Layer");
anm2.content.layers[referenceLayer] = layer;
layersMultiSelect = {referenceLayer};
}
else
{
snapshot("Add Layer");
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = layer;
layersMultiSelect = {id};
}
change(change::LAYERS);
}
void Document::layers_remove_unused()
{
snapshot("Remove Unused Layers");
for (auto& id : unusedLayerIDs)
anm2.content.layers.erase(id);
change(change::LAYERS);
unusedLayerIDs.clear();
}
void Document::null_set(anm2::Null& null)
{
if (referenceNull > -1)
{
snapshot("Set Null");
anm2.content.nulls[referenceNull] = null;
nullMultiSelect = {referenceNull};
}
else
{
snapshot("Add Null");
auto id = map::next_id_get(anm2.content.nulls);
anm2.content.nulls[id] = null;
nullMultiSelect = {id};
}
change(change::NULLS);
}
void Document::null_rect_toggle(anm2::Null& null)
{
snapshot("Null Rect");
null.isShowRect = !null.isShowRect;
change(change::NULLS);
}
void Document::nulls_remove_unused()
{
snapshot("Remove Unused Nulls");
for (auto& id : unusedNullIDs)
anm2.content.nulls.erase(id);
change(change::NULLS);
unusedNullIDs.clear();
}
void Document::nulls_deserialize(const std::string& string, merge::Type type)
{
snapshot("Paste Null(s)");
std::string errorString{};
if (anm2.content.nulls_deserialize(string, type, &errorString))
change(change::NULLS);
else
toasts.error(std::format("Failed to deserialize null(s): {}", errorString));
}
void Document::event_add()
{
snapshot("Add Event");
int id{};
anm2.event_add(id);
eventMultiSelect = {id};
change(change::EVENTS);
}
void Document::events_remove_unused()
{
snapshot("Remove Unused Events");
for (auto& id : unusedEventIDs)
anm2.content.events.erase(id);
change(change::EVENTS);
unusedEventIDs.clear();
}
void Document::events_deserialize(const std::string& string, merge::Type type)
{
snapshot("Paste Event(s)");
std::string errorString{};
if (anm2.content.events_deserialize(string, type, &errorString))
change(change::EVENTS);
else
toasts.error(std::format("Failed to deserialize event(s): {}", errorString));
}
void Document::item_visible_toggle(anm2::Item* item)
{
if (!item) return;
snapshot("Item Visible");
item->isVisible = !item->isVisible;
change(change::ITEMS);
}
void Document::item_add(anm2::Type type, int id, std::string& name, locale::Type locale, int spritesheetID)
{
snapshot("Add Item");
anm2::Reference addReference;
if (type == anm2::LAYER)
addReference =
anm2.layer_add({reference.animationIndex, anm2::LAYER, id}, name, spritesheetID, (locale::Type)locale);
else if (type == anm2::NULL_)
addReference = anm2.null_add({reference.animationIndex, anm2::LAYER, id}, name, (locale::Type)locale);
reference = addReference;
change(change::ITEMS);
}
void Document::item_remove(anm2::Animation* animation)
{
snapshot("Remove Item");
if (!animation) return;
animation->item_remove(reference.itemType, reference.itemID);
reference = {reference.animationIndex};
change(change::ITEMS);
}
anm2::Animation* Document::animation_get()
{
return anm2.animation_get(reference);
}
void Document::animation_add()
{
snapshot("Add Animation");
anm2::Animation animation;
if (anm2::Animation* referenceAnimation = 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 =
animationMultiSelect.empty() ? (int)anm2.animations.items.size() - 1 : *animationMultiSelect.rbegin() + 1;
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
animationMultiSelect = {index};
reference = {index};
change(change::ANIMATIONS);
}
void Document::animation_duplicate()
{
snapshot("Duplicate Animation(s)");
auto duplicated = animationMultiSelect;
auto duplicatedEnd = std::ranges::max(duplicated);
for (auto& id : duplicated)
{
anm2.animations.items.insert(anm2.animations.items.begin() + duplicatedEnd, anm2.animations.items[id]);
animationMultiSelect.insert(++duplicatedEnd);
animationMultiSelect.erase(id);
}
change(change::ANIMATIONS);
}
void Document::animations_move(std::vector<int>& indices, int index)
{
snapshot("Move Animation(s)");
animationMultiSelect = vector::move_indices(anm2.animations.items, indices, index);
change(change::ANIMATIONS);
}
void Document::animation_remove()
{
snapshot("Remove Animation(s)");
for (auto& i : animationMultiSelect | std::views::reverse)
anm2.animations.items.erase(anm2.animations.items.begin() + i);
animationMultiSelect.clear();
change(change::ANIMATIONS);
}
void Document::animation_default()
{
snapshot("Default Animation");
anm2.animations.defaultAnimation = anm2.animations.items[*animationMultiSelect.begin()].name;
change(change::ANIMATIONS);
}
void Document::animations_deserialize(const std::string& string)
{
snapshot("Paste Animations");
auto& multiSelect = animationMultiSelect;
auto start = multiSelect.empty() ? anm2.animations.items.size() : *multiSelect.rbegin() + 1;
std::set<int> indices{};
std::string errorString{};
if (anm2.animations.animations_deserialize(string, start, indices, &errorString))
{
multiSelect = indices;
change(change::ANIMATIONS);
}
else
toasts.error(std::format("Failed to deserialize animation(s): {}", errorString));
}
void Document::animations_merge_quick()
{
snapshot("Merge Animations");
int merged{};
if (animationMultiSelect.size() > 1)
merged = anm2.animations.merge(*animationMultiSelect.begin(), animationMultiSelect);
else if (animationMultiSelect.size() == 1 && *animationMultiSelect.begin() != (int)anm2.animations.items.size() - 1)
{
auto start = *animationMultiSelect.begin();
auto next = *animationMultiSelect.begin() + 1;
std::set<int> animationSet{};
animationSet.insert(start);
animationSet.insert(next);
merged = anm2.animations.merge(start, animationSet);
}
else
return;
animationMultiSelect = {merged};
reference = {merged};
change(change::ANIMATIONS);
}
void Document::animations_merge(merge::Type type, bool isDeleteAnimationsAfter)
{
snapshot("Merge Animations");
auto merged = anm2.animations.merge(mergeTarget, animationMergeMultiSelect, type, isDeleteAnimationsAfter);
animationMultiSelect = {merged};
reference = {merged};
change(change::ANIMATIONS);
}
void Document::snapshot(const std::string& message)
{
snapshots.push(anm2, reference, message);
}
void Document::undo()
{
snapshots.undo(anm2, reference, message);
toasts.info(std::format("Undo: {}", message));
change(change::ALL);
}
void Document::redo()
{
toasts.info(std::format("Redo: {}", message));
snapshots.redo(anm2, reference, message);
change(change::ALL);
}
bool Document::is_undo()
{
return !snapshots.undoStack.is_empty();
}
bool Document::is_redo()
{
return !snapshots.redoStack.is_empty();
}
}

View File

@@ -4,6 +4,9 @@
#include <set> #include <set>
#include "anm2.h" #include "anm2.h"
#include "imgui.h"
#include "playback.h"
#include "snapshots.h"
#include "types.h" #include "types.h"
#include <glm/glm.hpp> #include <glm/glm.hpp>
@@ -15,7 +18,9 @@ namespace anm2ed::document
public: public:
std::filesystem::path path{}; std::filesystem::path path{};
anm2::Anm2 anm2{}; anm2::Anm2 anm2{};
anm2::Reference reference{}; std::string message{};
playback::Playback playback{};
snapshots::Snapshots snapshots{};
float previewZoom{200}; float previewZoom{200};
glm::vec2 previewPan{}; glm::vec2 previewPan{};
@@ -23,40 +28,92 @@ namespace anm2ed::document
float editorZoom{200}; float editorZoom{200};
int overlayIndex{}; int overlayIndex{};
anm2::Reference reference{};
int hoveredAnimation{-1};
int mergeTarget{-1};
imgui::MultiSelectStorage animationMultiSelect;
imgui::MultiSelectStorage animationMergeMultiSelect;
int referenceSpritesheet{-1}; int referenceSpritesheet{-1};
int referenceLayer{-1}; int hoveredSpritesheet{-1};
std::set<int> unusedSpritesheetIDs{};
std::set<int> selectedEvents{};
std::set<int> selectedLayers{};
std::set<int> selectedNulls{};
std::set<int> selectedAnimations{};
std::set<int> selectedSpritesheets{};
std::vector<std::string> spritesheetNames{}; std::vector<std::string> spritesheetNames{};
std::vector<const char*> spritesheetNamesCstr{}; std::vector<const char*> spritesheetNamesCstr{};
imgui::MultiSelectStorage spritesheetMultiSelect;
int referenceLayer{-1};
int hoveredLayer{-1};
std::set<int> unusedLayerIDs{};
imgui::MultiSelectStorage layersMultiSelect;
int referenceNull{-1};
int hoveredNull{-1};
std::set<int> unusedNullIDs{};
imgui::MultiSelectStorage nullMultiSelect;
int referenceEvent{-1};
int hoveredEvent{-1};
std::set<int> unusedEventIDs{};
imgui::MultiSelectStorage eventMultiSelect;
uint64_t hash{}; uint64_t hash{};
uint64_t saveHash{}; uint64_t saveHash{};
bool isJustChanged[types::change::COUNT]{};
bool isOpen{true}; bool isOpen{true};
Document(); Document(const std::string&, bool = false, std::string* = nullptr);
bool save(const std::string& = {}, std::string* = nullptr);
Document(const std::string& path, bool isNew = false, std::string* errorString = nullptr);
bool save(const std::string& path = {}, std::string* errorString = nullptr);
void hash_set(); void hash_set();
void clean(); void clean();
void on_change(); void on_change();
void change(types::change::Type type); void change(types::change::Type);
bool is_just_changed(types::change::Type type);
bool is_dirty(); bool is_dirty();
void update();
std::string directory_get(); std::string directory_get();
std::string filename_get(); std::string filename_get();
anm2::Animation* animation_get();
anm2::Frame* frame_get();
anm2::Item* item_get();
anm2::Spritesheet* spritesheet_get();
bool is_valid(); bool is_valid();
anm2::Frame* frame_get();
void frames_add(anm2::Item* item);
void frames_delete(anm2::Item* item);
void frames_bake(int, bool, bool);
anm2::Item* item_get();
void item_add(anm2::Type, int, std::string&, types::locale::Type, int);
void item_remove(anm2::Animation* animation);
anm2::Spritesheet* spritesheet_get();
void spritesheet_add(const std::string&);
void spritesheets_deserialize(const std::string&, types::merge::Type);
void layer_set(anm2::Layer& layer);
void layers_remove_unused();
void layers_deserialize(const std::string&, types::merge::Type);
void null_set(anm2::Null& null);
void null_rect_toggle(anm2::Null& null);
void nulls_remove_unused();
void nulls_deserialize(const std::string&, types::merge::Type);
void event_add();
void events_remove_unused();
void events_deserialize(const std::string&, types::merge::Type);
void item_visible_toggle(anm2::Item*);
void animation_add();
void animation_duplicate();
void animation_default();
void animation_remove();
void animations_move(std::vector<int>&, int);
void animations_merge(types::merge::Type, bool);
void animations_merge_quick();
anm2::Animation* animation_get();
void animations_deserialize(const std::string& string);
void snapshot(const std::string& message);
void undo();
void redo();
bool is_undo();
bool is_redo();
}; };
}; }

View File

@@ -1,58 +0,0 @@
#include "document_manager.h"
#include "util.h"
using namespace anm2ed::util;
namespace anm2ed::document_manager
{
Document* DocumentManager::get()
{
return vector::find(documents, selected);
}
Document* DocumentManager::get(int index)
{
return vector::find(documents, index);
}
bool DocumentManager::open(const std::string& path, bool isNew)
{
std::string errorString{};
Document document = Document(path, isNew, &errorString);
if (document.is_valid())
{
documents.emplace_back(std::move(document));
selected = documents.size() - 1;
pendingSelected = selected;
return true;
}
return false;
}
bool DocumentManager::new_(const std::string& path)
{
return open(path, true);
}
void DocumentManager::save(int index, const std::string& path)
{
auto document = get(index);
if (!document) return;
std::string errorString{};
document->path = !path.empty() ? path : document->path.string();
document->save(document->path, &errorString);
}
void DocumentManager::save(const std::string& path)
{
save(selected, path);
}
void DocumentManager::close(int index)
{
documents.erase(documents.begin() + index);
}
}

View File

@@ -1,27 +0,0 @@
#pragma once
#include <vector>
#include "document.h"
using namespace anm2ed::document;
namespace anm2ed::document_manager
{
class DocumentManager
{
public:
std::vector<Document> documents{};
int selected{};
int pendingSelected{};
Document* get();
Document* get(int index);
bool open(const std::string& path, bool isNew = false);
bool new_(const std::string& path);
void save(int index, const std::string& path = {});
void save(const std::string& path = {});
void close(int index);
void spritesheet_add(const std::string& path);
};
}

View File

@@ -5,12 +5,12 @@
#include "imgui.h" #include "imgui.h"
using namespace anm2ed::taskbar; using namespace anm2ed::taskbar;
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::resources; using namespace anm2ed::resources;
namespace anm2ed::documents namespace anm2ed::documents
{ {
void Documents::update(Taskbar& taskbar, DocumentManager& manager, Resources& resources) void Documents::update(Taskbar& taskbar, Manager& manager, Resources& resources)
{ {
auto viewport = ImGui::GetMainViewport(); auto viewport = ImGui::GetMainViewport();
auto windowHeight = ImGui::GetFrameHeightWithSpacing(); auto windowHeight = ImGui::GetFrameHeightWithSpacing();
@@ -32,7 +32,6 @@ namespace anm2ed::documents
for (auto [i, document] : std::views::enumerate(manager.documents)) for (auto [i, document] : std::views::enumerate(manager.documents))
{ {
auto isDirty = document.is_dirty(); auto isDirty = document.is_dirty();
auto isSelected = i == manager.selected;
auto isRequested = i == manager.pendingSelected; auto isRequested = i == manager.pendingSelected;
auto font = isDirty ? font::ITALICS : font::REGULAR; auto font = isDirty ? font::ITALICS : font::REGULAR;
@@ -65,8 +64,6 @@ namespace anm2ed::documents
else else
manager.close(i); manager.close(i);
} }
if (isSelected) document.update();
} }
ImGui::EndTabBar(); ImGui::EndTabBar();

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "document_manager.h"
#include "imgui.h" #include "imgui.h"
#include "manager.h"
#include "resources.h" #include "resources.h"
#include "taskbar.h" #include "taskbar.h"
@@ -17,6 +17,6 @@ namespace anm2ed::documents
public: public:
float height{}; float height{};
void update(taskbar::Taskbar& taskbar, document_manager::DocumentManager& manager, resources::Resources& resources); void update(taskbar::Taskbar&, manager::Manager&, resources::Resources&);
}; };
} }

View File

@@ -2,22 +2,21 @@
#include <ranges> #include <ranges>
using namespace anm2ed::document_manager; using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::types; using namespace anm2ed::types;
namespace anm2ed::events namespace anm2ed::events
{ {
void Events::update(DocumentManager& manager, Settings& settings, Resources& resources) void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& selection = document.selectedEvents; auto& unused = document.unusedEventIDs;
auto& hovered = document.hoveredEvent;
if (document.is_just_changed(change::EVENTS)) unusedEventIDs = anm2.events_unused(); auto& multiSelect = document.eventMultiSelect;
storage.user_data_set(&selection);
if (ImGui::Begin("Events", &settings.windowIsEvents)) if (ImGui::Begin("Events", &settings.windowIsEvents))
{ {
@@ -26,17 +25,16 @@ namespace anm2ed::events
if (ImGui::BeginChild("##Events Child", childSize, true)) if (ImGui::BeginChild("##Events Child", childSize, true))
{ {
storage.begin(anm2.content.events.size()); multiSelect.start(anm2.content.events.size());
for (auto& [id, event] : anm2.content.events) for (auto& [id, event] : anm2.content.events)
{ {
auto isSelected = selection.contains(id);
ImGui::PushID(id); ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id); ImGui::SetNextItemSelectionUserData(id);
if (imgui::selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id), if (imgui::selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id),
event.name, isSelected, 0, &isRenamed)) event.name, multiSelect.contains(id), 0, &isRenamed))
if (isRenamed) document.change(change::EVENTS); if (ImGui::IsItemHovered()) hovered = id;
if (isRenamed) document.change(change::EVENTS);
if (ImGui::BeginItemTooltip()) if (ImGui::BeginItemTooltip())
{ {
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
@@ -47,34 +45,67 @@ namespace anm2ed::events
ImGui::PopID(); ImGui::PopID();
} }
storage.end(); 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 (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(); ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutAdd); imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) if (ImGui::Button("Add", widgetSize)) document.event_add();
{
int id{};
anm2.event_add(id);
selection = {id};
document.change(change::EVENTS);
}
imgui::set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd); imgui::set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unusedEventIDs.empty()); ImGui::BeginDisabled(unused.empty());
{ if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
if (ImGui::Button("Remove Unused", widgetSize))
{
for (auto& id : unusedEventIDs)
anm2.content.events.erase(id);
document.change(change::EVENTS);
unusedEventIDs.clear();
}
}
ImGui::EndDisabled(); ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)", imgui::set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
settings.shortcutRemove); settings.shortcutRemove);

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "document_manager.h" #include "clipboard.h"
#include "imgui.h" #include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -9,11 +9,8 @@ namespace anm2ed::events
{ {
class Events class Events
{ {
imgui::MultiSelectStorage storage{};
std::set<int> unusedEventIDs{};
public: public:
void update(document_manager::DocumentManager& manager, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
resources::Resources& resources);
}; };
} }

View File

@@ -6,15 +6,15 @@
namespace anm2ed::filesystem namespace anm2ed::filesystem
{ {
std::string path_preferences_get(); std::string path_preferences_get();
bool path_is_exist(const std::string& path); bool path_is_exist(const std::string&);
bool path_is_extension(const std::string& path, const std::string& extension); bool path_is_extension(const std::string&, const std::string&);
class WorkingDirectory class WorkingDirectory
{ {
public: public:
std::filesystem::path previous; std::filesystem::path previous;
WorkingDirectory(const std::string& path, bool isFile = false); WorkingDirectory(const std::string&, bool = false);
~WorkingDirectory(); ~WorkingDirectory();
}; };
} }

View File

@@ -5230,9 +5230,9 @@ namespace anm2ed::font
public: public:
Font(); Font();
Font(void* data, size_t length, int size); Font(void*, size_t, int);
~Font(); ~Font();
ImFont* get(); ImFont* get();
Font& operator=(Font&& other) noexcept; Font& operator=(Font&&) noexcept;
}; };
}; };

View File

@@ -9,7 +9,7 @@
#include "types.h" #include "types.h"
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::math; using namespace anm2ed::math;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace glm; using namespace glm;
@@ -17,7 +17,7 @@ using namespace glm;
namespace anm2ed::frame_properties namespace anm2ed::frame_properties
{ {
void FrameProperties::update(DocumentManager& manager, Settings& settings) void FrameProperties::update(Manager& manager, Settings& settings)
{ {
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties)) if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
{ {

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "document_manager.h" #include "manager.h"
#include "settings.h" #include "settings.h"
namespace anm2ed::frame_properties namespace anm2ed::frame_properties
@@ -8,6 +8,6 @@ namespace anm2ed::frame_properties
class FrameProperties class FrameProperties
{ {
public: public:
void update(document_manager::DocumentManager& manager, settings::Settings& settings); void update(manager::Manager&, settings::Settings&);
}; };
} }

View File

@@ -118,7 +118,7 @@ namespace icon
)"; )";
constexpr auto TARGET_DATA = R"( constexpr auto TARGET_DATA = R"(
<svg viewBox="0 0 24 24" fill="none" stroke="#FFF" stroke-width="1" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="3.5"/> <line x1="12" y1="-2" x2="12" y2="26"/> <line x1="-2" y1="12" x2="26" y2="12"/> </svg> <svg viewBox="0 0 24 24" fill="none" stroke="#FFF" stroke-width="0.75" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="2.8"/> <line x1="12" y1="5" x2="12" y2="19"/> <line x1="5" y1="12" x2="19" y2="12"/> </svg>
)"; )";
constexpr auto INTERPOLATED_DATA = R"( constexpr auto INTERPOLATED_DATA = R"(
@@ -142,7 +142,7 @@ namespace icon
)"; )";
#define LIST \ #define LIST \
X(NONE, NONE_DATA, SIZE_NORMAL) \ X(NONE, NONE_DATA, SIZE_SMALL) \
X(FILE, FILE_DATA, SIZE_NORMAL) \ X(FILE, FILE_DATA, SIZE_NORMAL) \
X(FOLDER, FOLDER_DATA, SIZE_NORMAL) \ X(FOLDER, FOLDER_DATA, SIZE_NORMAL) \
X(CLOSE, CLOSE_DATA, SIZE_NORMAL) \ X(CLOSE, CLOSE_DATA, SIZE_NORMAL) \

View File

@@ -252,19 +252,15 @@ namespace anm2ed::imgui
internal.AdapterSetItemSelected = external_storage_set; internal.AdapterSetItemSelected = external_storage_set;
} }
void MultiSelectStorage::user_data_set(std::set<int>* userData) void MultiSelectStorage::start(size_t size)
{ {
internal.UserData = userData; internal.UserData = this;
this->userData = userData;
}
void MultiSelectStorage::begin(size_t size) auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape, this->size(), size);
{
auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape, userData ? userData->size() : 0, size);
internal.ApplyRequests(io); internal.ApplyRequests(io);
} }
void MultiSelectStorage::end() void MultiSelectStorage::finish()
{ {
auto io = ImGui::EndMultiSelect(); auto io = ImGui::EndMultiSelect();
internal.ApplyRequests(io); internal.ApplyRequests(io);
@@ -281,6 +277,7 @@ namespace anm2ed::imgui
{ {
isOpen = true; isOpen = true;
isTriggered = true; isTriggered = true;
isJustOpened = true;
} }
void PopupHelper::trigger() void PopupHelper::trigger()
@@ -296,6 +293,11 @@ namespace anm2ed::imgui
ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * percent)); ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * percent));
} }
void PopupHelper::end()
{
isJustOpened = false;
}
void PopupHelper::close() void PopupHelper::close()
{ {
isOpen = false; isOpen = false;

View File

@@ -124,37 +124,43 @@ namespace anm2ed::imgui
{"Super", ImGuiMod_Super}, {"Super", ImGuiMod_Super},
}; };
std::string chord_to_string(ImGuiKeyChord chord); std::string chord_to_string(ImGuiKeyChord);
ImGuiKeyChord string_to_chord(const std::string& string); ImGuiKeyChord string_to_chord(const std::string&);
float row_widget_width_get(int count, float width = ImGui::GetContentRegionAvail().x); float row_widget_width_get(int, float = ImGui::GetContentRegionAvail().x);
ImVec2 widget_size_with_row_get(int count, float width = ImGui::GetContentRegionAvail().x); ImVec2 widget_size_with_row_get(int, float = ImGui::GetContentRegionAvail().x);
float footer_height_get(int itemCount = 1); float footer_height_get(int = 1);
ImVec2 footer_size_get(int itemCount = 1); ImVec2 footer_size_get(int = 1);
ImVec2 size_without_footer_get(int rowCount = 1); ImVec2 size_without_footer_get(int = 1);
ImVec2 child_size_get(int rowCount = 1); ImVec2 child_size_get(int = 1);
int input_text_callback(ImGuiInputTextCallbackData* data); int input_text_callback(ImGuiInputTextCallbackData*);
bool input_text_string(const char* label, std::string* string, ImGuiInputTextFlags flags = 0); bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
void combo_strings(const std::string& label, int* index, std::vector<std::string>& strings); void combo_strings(const std::string&, int*, std::vector<std::string>&);
void combo_strings(const std::string& label, int* index, std::vector<const char*>& strings); void combo_strings(const std::string&, int*, std::vector<const char*>&);
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool selectable_input_text(const std::string&, const std::string&, std::string&, bool = false,
bool isSelected = false, ImGuiSelectableFlags flags = 0, bool* isRenamed = nullptr); ImGuiSelectableFlags = 0, bool* = nullptr);
void set_item_tooltip_shortcut(const char* tooltip, const std::string& shortcut = {}); void set_item_tooltip_shortcut(const char*, const std::string& = {});
void external_storage_set(ImGuiSelectionExternalStorage* self, int id, bool isSelected); void external_storage_set(ImGuiSelectionExternalStorage*, int, bool);
ImVec2 icon_size_get(); ImVec2 icon_size_get();
bool chord_held(ImGuiKeyChord chord); bool chord_held(ImGuiKeyChord);
bool chord_repeating(ImGuiKeyChord chord, float delay = 0.125f, float rate = 0.025f); bool chord_repeating(ImGuiKeyChord, float = 0.125f, float = 0.025f);
bool shortcut(std::string string, types::shortcut::Type type = types::shortcut::FOCUSED_SET); bool shortcut(std::string, types::shortcut::Type = types::shortcut::FOCUSED_SET);
class MultiSelectStorage class MultiSelectStorage : public std::set<int>
{ {
public: public:
ImGuiSelectionExternalStorage internal{}; ImGuiSelectionExternalStorage internal{};
std::set<int>* userData{}; using std::set<int>::set;
using std::set<int>::operator=;
using std::set<int>::begin;
using std::set<int>::rbegin;
using std::set<int>::end;
using std::set<int>::size;
using std::set<int>::insert;
using std::set<int>::erase;
MultiSelectStorage(); MultiSelectStorage();
void user_data_set(std::set<int>* userData); void start(size_t);
void begin(size_t size); void finish();
void end();
}; };
class PopupHelper class PopupHelper
@@ -163,12 +169,14 @@ namespace anm2ed::imgui
const char* label{}; const char* label{};
bool isOpen{}; bool isOpen{};
bool isTriggered{}; bool isTriggered{};
bool isJustOpened{};
bool isNoHeight{}; bool isNoHeight{};
float percent{}; float percent{};
PopupHelper(const char* label, float percent = POPUP_NORMAL, bool isNoHeight = false); PopupHelper(const char*, float = POPUP_NORMAL, bool = false);
void open(); void open();
void trigger(); void trigger();
void end();
void close(); void close();
}; };
} }

View File

@@ -2,38 +2,24 @@
#include <ranges> #include <ranges>
#include "util.h"
using namespace anm2ed::document; using namespace anm2ed::document;
using namespace anm2ed::settings; using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::settings;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::util;
namespace anm2ed::layers namespace anm2ed::layers
{ {
void Layers::update(Document& document, Settings& settings, Resources& resources) void Layers::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{ {
auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& selection = document.selectedLayers; auto& reference = document.referenceLayer;
auto& referenceLayer = document.referenceLayer; auto& unused = document.unusedLayerIDs;
auto& hovered = document.hoveredLayer;
if (document.is_just_changed(change::LAYERS)) unusedLayerIDs = anm2.layers_unused(); auto& multiSelect = document.layersMultiSelect;
auto& propertiesPopup = manager.layerPropertiesPopup;
storage.user_data_set(&selection);
auto properties_popup_open = [&](int id = -1)
{
if (id == -1)
{
isAdd = true;
editLayer = anm2::Layer();
}
else
editLayer = anm2.content.layers.at(id);
propertiesPopup.open();
};
if (ImGui::Begin("Layers", &settings.windowIsLayers)) if (ImGui::Begin("Layers", &settings.windowIsLayers))
{ {
@@ -41,26 +27,24 @@ namespace anm2ed::layers
if (ImGui::BeginChild("##Layers Child", childSize, true)) if (ImGui::BeginChild("##Layers Child", childSize, true))
{ {
storage.begin(anm2.content.layers.size()); multiSelect.start(anm2.content.layers.size());
for (auto& [id, layer] : anm2.content.layers) for (auto& [id, layer] : anm2.content.layers)
{ {
auto isSelected = selection.contains(id); auto isSelected = multiSelect.contains(id);
auto isReferenced = referenceLayer == id;
ImGui::PushID(id); ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id); ImGui::SetNextItemSelectionUserData(id);
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE); ImGui::Selectable(std::format(anm2::LAYER_FORMAT, id, layer.name, layer.spritesheetID).c_str(), isSelected);
ImGui::Selectable(std::format("#{} {} (Spritesheet: #{})", id, layer.name, layer.spritesheetID).c_str(), if (ImGui::IsItemHovered())
isSelected);
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{ {
referenceLayer = id; hovered = id;
properties_popup_open(id); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.layer_properties_open(id);
} }
else
hovered = -1;
if (isReferenced) ImGui::PopFont();
if (ImGui::BeginItemTooltip()) if (ImGui::BeginItemTooltip())
{ {
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
@@ -73,50 +57,83 @@ namespace anm2ed::layers
ImGui::PopID(); ImGui::PopID();
} }
storage.end(); 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 (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(); ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutAdd); imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) properties_popup_open(); if (ImGui::Button("Add", widgetSize)) manager.layer_properties_open();
imgui::set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd); imgui::set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unusedLayerIDs.empty()); ImGui::BeginDisabled(unused.empty());
{ if (ImGui::Button("Remove Unused", widgetSize)) document.layers_remove_unused();
if (ImGui::Button("Remove Unused", widgetSize))
{
for (auto& id : unusedLayerIDs)
anm2.content.layers.erase(id);
document.change(change::LAYERS);
unusedLayerIDs.clear();
}
}
ImGui::EndDisabled(); ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)", imgui::set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
settings.shortcutRemove); settings.shortcutRemove);
} }
ImGui::End(); ImGui::End();
propertiesPopup.trigger(); manager.layer_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize)) if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{ {
auto childSize = imgui::child_size_get(2); auto childSize = imgui::child_size_get(2);
auto& layer = editLayer; auto& layer = manager.editLayer;
auto close = [&]()
{
isAdd = false;
editLayer = anm2::Layer();
propertiesPopup.close();
};
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders)) if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
{ {
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
imgui::input_text_string("Name", &layer.name); imgui::input_text_string("Name", &layer.name);
ImGui::SetItemTooltip("Set the item's name."); ImGui::SetItemTooltip("Set the item's name.");
imgui::combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames); imgui::combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
@@ -126,27 +143,18 @@ namespace anm2ed::layers
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button(isAdd ? "Add" : "Confirm", widgetSize)) if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
{ {
if (isAdd) document.layer_set(layer);
{ manager.layer_properties_close();
auto id = map::next_id_get(anm2.content.layers);
anm2.content.layers[id] = editLayer;
referenceLayer = id;
}
else
anm2.content.layers[referenceLayer] = editLayer;
document.change(change::LAYERS);
close();
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) close(); if (ImGui::Button("Cancel", widgetSize)) manager.layer_properties_close();
manager.layer_properties_end();
ImGui::EndPopup(); ImGui::EndPopup();
} }
referenceLayer = propertiesPopup.isOpen ? referenceLayer : -1;
} }
} }

View File

@@ -1,22 +1,15 @@
#pragma once #pragma once
#include "document.h" #include "clipboard.h"
#include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
#include "imgui.h"
namespace anm2ed::layers namespace anm2ed::layers
{ {
class Layers class Layers
{ {
bool isAdd{};
imgui::PopupHelper propertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)};
imgui::MultiSelectStorage storage;
anm2::Layer editLayer{};
std::set<int> unusedLayerIDs{};
public: public:
void update(document::Document& document, settings::Settings& settings, resources::Resources& resources); void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
}; };
} }

View File

@@ -18,7 +18,7 @@ namespace anm2ed::loader
std::vector<std::string> arguments; std::vector<std::string> arguments;
bool isError{}; bool isError{};
Loader(int argc, const char** argv); Loader(int, const char**);
~Loader(); ~Loader();
}; };
} }

View File

@@ -30,15 +30,15 @@ namespace anm2ed::log
std::ofstream file{}; std::ofstream file{};
public: public:
void write(const Level level, const std::string& message); void write(const Level, const std::string&);
void info(const std::string& message); void info(const std::string&);
void warning(const std::string& message); void warning(const std::string&);
void error(const std::string& message); void error(const std::string&);
void fatal(const std::string& message); void fatal(const std::string&);
void open(const std::filesystem::path& path); void open(const std::filesystem::path&);
Logger(); Logger();
~Logger(); ~Logger();
}; };
extern Logger logger; extern Logger logger;
} }

120
src/manager.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "manager.h"
#include "toast.h"
#include "util.h"
using namespace anm2ed::toast;
using namespace anm2ed::types;
using namespace anm2ed::util;
namespace anm2ed::manager
{
Document* Manager::get(int index)
{
return vector::find(documents, index > -1 ? index : selected);
}
void Manager::open(const std::string& path, bool isNew)
{
std::string errorString{};
Document document = Document(path, isNew, &errorString);
if (document.is_valid())
{
documents.emplace_back(std::move(document));
selected = documents.size() - 1;
pendingSelected = selected;
toasts.info(std::format("Initialized document: {}", path));
}
else
toasts.error(std::format("Failed to initialize document: {} ({})", path, errorString));
}
void Manager::new_(const std::string& path)
{
open(path, true);
}
void Manager::save(int index, const std::string& path)
{
if (auto document = get(index); document)
{
std::string errorString{};
document->path = !path.empty() ? path : document->path.string();
document->save(document->path, &errorString);
}
}
void Manager::save(const std::string& path)
{
save(selected, path);
}
void Manager::close(int index)
{
documents.erase(documents.begin() + index);
}
void Manager::layer_properties_open(int id)
{
if (auto document = get(); document)
{
if (id == -1)
editLayer = anm2::Layer();
else
editLayer = document->anm2.content.layers.at(id);
document->referenceLayer = id;
layerPropertiesPopup.open();
}
}
void Manager::layer_properties_trigger()
{
layerPropertiesPopup.trigger();
}
void Manager::layer_properties_end()
{
layerPropertiesPopup.end();
}
void Manager::layer_properties_close()
{
editLayer = anm2::Layer();
layerPropertiesPopup.close();
}
void Manager::null_properties_open(int id)
{
if (auto document = get(); document)
{
if (id == -1)
editNull = anm2::Null();
else
editNull = document->anm2.content.nulls.at(id);
document->referenceNull = id;
nullPropertiesPopup.open();
}
}
void Manager::null_properties_trigger()
{
nullPropertiesPopup.trigger();
}
void Manager::null_properties_end()
{
nullPropertiesPopup.end();
}
void Manager::null_properties_close()
{
editNull = anm2::Null();
nullPropertiesPopup.close();
}
}

40
src/manager.h Normal file
View File

@@ -0,0 +1,40 @@
#pragma once
#include <vector>
#include "document.h"
#include "imgui.h"
using namespace anm2ed::document;
namespace anm2ed::manager
{
class Manager
{
public:
std::vector<Document> documents{};
int selected{};
int pendingSelected{};
anm2::Layer editLayer{};
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)};
anm2::Null editNull{};
imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL, true)};
Document* get(int = -1);
void open(const std::string&, bool = false);
void new_(const std::string&);
void save(int, const std::string& = {});
void save(const std::string& = {});
void close(int);
void layer_properties_open(int = -1);
void layer_properties_trigger();
void layer_properties_end();
void layer_properties_close();
void null_properties_open(int = -1);
void null_properties_trigger();
void null_properties_end();
void null_properties_close();
};
}

View File

@@ -31,16 +31,15 @@ namespace anm2ed::math
1.0f, 1.0f, uvMax.x, uvMax.y, 0.0f, 1.0f, uvMin.x, uvMax.y}; 1.0f, 1.0f, uvMax.x, uvMax.y, 0.0f, 1.0f, uvMin.x, uvMax.y};
} }
float round_nearest_multiple(float value, float multiple); float round_nearest_multiple(float, float);
int float_decimals_needed(float value); int float_decimals_needed(float);
const char* float_format_get(float value); const char* float_format_get(float);
const char* vec2_format_get(glm::vec2& value); const char* vec2_format_get(glm::vec2&);
glm::mat4 quad_model_get(glm::vec2 size = {}, glm::vec2 position = {}, glm::vec2 pivot = {}, glm::mat4 quad_model_get(glm::vec2 = {}, glm::vec2 = {}, glm::vec2 = {},
glm::vec2 scale = glm::vec2(1.0f), float rotation = {}); glm::vec2 = glm::vec2(1.0f), float = {});
glm::mat4 quad_model_parent_get(glm::vec2 position = {}, glm::vec2 pivot = {}, glm::vec2 scale = glm::vec2(1.0f), glm::mat4 quad_model_parent_get(glm::vec2 = {}, glm::vec2 = {}, glm::vec2 = glm::vec2(1.0f), float = {});
float rotation = {}); }
}

View File

@@ -2,21 +2,23 @@
#include <ranges> #include <ranges>
using namespace anm2ed::document; using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::types; using namespace anm2ed::types;
namespace anm2ed::nulls namespace anm2ed::nulls
{ {
void Nulls::update(Document& document, int& documentIndex, Settings& settings, Resources& resources) void Nulls::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
{ {
auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& selection = document.selectedNulls; auto& reference = document.referenceNull;
auto& unused = document.unusedNullIDs;
if (document.is_just_changed(change::NULLS)) unusedNullsIDs = anm2.nulls_unused(); auto& hovered = document.hoveredNull;
auto& multiSelect = document.nullMultiSelect;
storage.user_data_set(&selection); auto& propertiesPopup = manager.nullPropertiesPopup;
if (ImGui::Begin("Nulls", &settings.windowIsNulls)) if (ImGui::Begin("Nulls", &settings.windowIsNulls))
{ {
@@ -24,17 +26,25 @@ namespace anm2ed::nulls
if (ImGui::BeginChild("##Nulls Child", childSize, true)) if (ImGui::BeginChild("##Nulls Child", childSize, true))
{ {
storage.begin(anm2.content.nulls.size()); multiSelect.start(anm2.content.nulls.size());
for (auto& [id, null] : anm2.content.nulls) for (auto& [id, null] : anm2.content.nulls)
{ {
auto isSelected = selection.contains(id); auto isSelected = multiSelect.contains(id);
auto isReferenced = reference == id;
ImGui::PushID(id); ImGui::PushID(id);
ImGui::SetNextItemSelectionUserData(id); ImGui::SetNextItemSelectionUserData(id);
imgui::selectable_input_text(std::format("#{} {}", id, null.name), if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
std::format("###Document #{} Null #{}", documentIndex, id), null.name, ImGui::Selectable(std::format(anm2::NULL_FORMAT, id, null.name).c_str(), isSelected);
isSelected); if (ImGui::IsItemHovered())
{
hovered = id;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) manager.null_properties_open(id);
}
if (isReferenced) ImGui::PopFont();
if (ImGui::BeginItemTooltip()) if (ImGui::BeginItemTooltip())
{ {
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
@@ -46,29 +56,106 @@ namespace anm2ed::nulls
ImGui::PopID(); ImGui::PopID();
} }
storage.end(); 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 (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(); ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2); auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutAdd); imgui::shortcut(settings.shortcutAdd);
ImGui::Button("Add", widgetSize); if (ImGui::Button("Add", widgetSize)) manager.null_properties_open();
imgui::set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd); imgui::set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
ImGui::BeginDisabled(unusedNullsIDs.empty()); ImGui::BeginDisabled(unused.empty());
{ if (ImGui::Button("Remove Unused", widgetSize)) document.nulls_remove_unused();
if (ImGui::Button("Remove Unused", widgetSize))
for (auto& id : unusedNullsIDs)
anm2.content.nulls.erase(id);
document.change(change::NULLS);
}
ImGui::EndDisabled(); ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)", imgui::set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)",
settings.shortcutRemove); settings.shortcutRemove);
} }
ImGui::End(); ImGui::End();
manager.null_properties_trigger();
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto childSize = imgui::child_size_get(2);
auto& null = manager.editNull;
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
{
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
imgui::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 = imgui::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();
} }
} }

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "document.h" #include "clipboard.h"
#include "imgui.h" #include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -9,11 +9,7 @@ namespace anm2ed::nulls
{ {
class Nulls class Nulls
{ {
imgui::MultiSelectStorage storage{};
std::set<int> unusedNullsIDs{};
public: public:
void update(document::Document& document, int& documentIndex, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
resources::Resources& resources);
}; };
} }

View File

@@ -3,6 +3,7 @@
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#include "imgui.h" #include "imgui.h"
#include "types.h"
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::types; using namespace anm2ed::types;
@@ -24,7 +25,6 @@ namespace anm2ed::onionskin
ImGui::PopID(); ImGui::PopID();
}; };
imgui::shortcut(settings.shortcutOnionskin);
ImGui::Checkbox("Enabled", &settings.onionskinIsEnabled); ImGui::Checkbox("Enabled", &settings.onionskinIsEnabled);
order_configure("Before", settings.onionskinBeforeCount, settings.onionskinBeforeColor); order_configure("Before", settings.onionskinBeforeCount, settings.onionskinBeforeColor);
@@ -32,14 +32,14 @@ namespace anm2ed::onionskin
ImGui::Text("Order"); ImGui::Text("Order");
ImGui::SameLine(); ImGui::SameLine();
ImGui::RadioButton("Before", &settings.onionskinDrawOrder, BELOW); ImGui::RadioButton("Before", &settings.onionskinDrawOrder, draw_order::BELOW);
ImGui::SameLine(); ImGui::SameLine();
ImGui::RadioButton("After", &settings.onionskinDrawOrder, ABOVE); ImGui::RadioButton("After", &settings.onionskinDrawOrder, draw_order::ABOVE);
} }
if (imgui::shortcut(settings.shortcutOnionskin), shortcut::GLOBAL)
settings.onionskinIsEnabled = !settings.onionskinIsEnabled;
ImGui::End(); ImGui::End();
if (imgui::shortcut(settings.shortcutOnionskin, shortcut::GLOBAL))
settings.onionskinIsEnabled = !settings.onionskinIsEnabled;
} }
} }

View File

@@ -4,15 +4,9 @@
namespace anm2ed::onionskin namespace anm2ed::onionskin
{ {
enum Type
{
BELOW,
ABOVE
};
class Onionskin class Onionskin
{ {
public: public:
void update(settings::Settings& settings); void update(settings::Settings&);
}; };
} }

View File

@@ -10,9 +10,9 @@ namespace anm2ed::playback
bool isFinished{}; bool isFinished{};
void toggle(); void toggle();
void clamp(int length); void clamp(int);
void tick(int fps, int length, bool isLoop); void tick(int, int, bool);
void decrement(int length); void decrement(int);
void increment(int length); void increment(int);
}; };
} }

View File

@@ -206,8 +206,8 @@ namespace anm2ed::settings
Settings(); Settings();
Settings(const std::string& path); Settings(const std::string&);
void save(const std::string& path, const std::string& imguiData); void save(const std::string&, const std::string&);
}; };
enum ShortcutType enum ShortcutType

View File

@@ -39,19 +39,6 @@ namespace anm2ed::shader
} }
)"; )";
constexpr auto GRID_VERTEX = R"(
#version 330 core
layout (location = 0) in vec2 i_position;
layout (location = 1) in vec2 i_uv;
out vec2 i_uv_out;
void main() {
i_uv_out = i_position;
gl_Position = vec4(i_position, 0.0, 1.0);
}
)";
constexpr auto FRAGMENT = R"( constexpr auto FRAGMENT = R"(
#version 330 core #version 330 core
out vec4 o_fragColor; out vec4 o_fragColor;
@@ -78,6 +65,19 @@ void main() {
} }
)"; )";
constexpr auto GRID_VERTEX = R"(
#version 330 core
layout (location = 0) in vec2 i_position;
layout (location = 1) in vec2 i_uv;
out vec2 i_uv_out;
void main() {
i_uv_out = i_position;
gl_Position = vec4(i_position, 0.0, 1.0);
}
)";
constexpr auto GRID_FRAGMENT = R"( constexpr auto GRID_FRAGMENT = R"(
#version 330 core #version 330 core
in vec2 i_uv_out; in vec2 i_uv_out;
@@ -98,6 +98,7 @@ void main() {
vec2 pan = u_pan; vec2 pan = u_pan;
vec2 world = (i_uv_out - (2.0 * pan / viewSize)) * (viewSize / (2.0 * zoom)); vec2 world = (i_uv_out - (2.0 * pan / viewSize)) * (viewSize / (2.0 * zoom));
world += vec2(0.5); // Half pixel nudge
vec2 cell = max(u_size, vec2(1.0)); vec2 cell = max(u_size, vec2(1.0));
vec2 grid = (world - u_offset) / cell; vec2 grid = (world - u_offset) / cell;
@@ -148,8 +149,8 @@ void main() {
GLuint id{}; GLuint id{};
Shader(); Shader();
Shader(const char* vertex, const char* fragment); Shader(const char*, const char*);
Shader& operator=(Shader&& other) noexcept; Shader& operator=(Shader&&) noexcept;
~Shader(); ~Shader();
bool is_valid() const; bool is_valid() const;
}; };

68
src/snapshots.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "snapshots.h"
namespace anm2ed::snapshots
{
bool SnapshotStack::is_empty()
{
return top == 0;
}
void SnapshotStack::push(Snapshot& snapshot)
{
if (top >= MAX)
{
for (int i = 0; i < MAX - 1; i++)
snapshots[i] = snapshots[i + 1];
top = MAX - 1;
}
snapshots[top++] = snapshot;
}
Snapshot* SnapshotStack::pop()
{
if (is_empty()) return nullptr;
return &snapshots[--top];
}
void SnapshotStack::clear()
{
top = 0;
}
void Snapshots::push(const anm2::Anm2& anm2, anm2::Reference reference, const std::string& message)
{
Snapshot snapshot = {anm2, reference, message};
undoStack.push(snapshot);
redoStack.clear();
}
void Snapshots::undo(anm2::Anm2& anm2, anm2::Reference& reference, std::string& message)
{
if (auto current = undoStack.pop())
{
Snapshot snapshot = {anm2, reference, message};
redoStack.push(snapshot);
anm2 = current->anm2;
reference = current->reference;
message = current->message;
}
}
void Snapshots::redo(anm2::Anm2& anm2, anm2::Reference& reference, std::string& message)
{
if (auto current = redoStack.pop())
{
Snapshot snapshot = {anm2, reference, message};
undoStack.push(snapshot);
anm2 = current->anm2;
reference = current->reference;
message = current->message;
}
}
void Snapshots::reset()
{
undoStack.clear();
redoStack.clear();
}
};

42
src/snapshots.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include "anm2.h"
namespace anm2ed::snapshots
{
constexpr auto ACTION = "Action";
constexpr auto MAX = 100;
class Snapshot
{
public:
anm2::Anm2 anm2{};
anm2::Reference reference{};
std::string message = ACTION;
};
class SnapshotStack
{
public:
Snapshot snapshots[MAX];
int top{};
bool is_empty();
void push(Snapshot& snapshot);
Snapshot* pop();
void clear();
};
class Snapshots
{
public:
SnapshotStack undoStack{};
SnapshotStack redoStack{};
Snapshot* get();
void push(const anm2::Anm2&, anm2::Reference, const std::string&);
void undo(anm2::Anm2& anm2, anm2::Reference& reference, std::string&);
void redo(anm2::Anm2& anm2, anm2::Reference& reference, std::string&);
void reset();
};
}

View File

@@ -5,7 +5,7 @@
#include "tool.h" #include "tool.h"
#include "types.h" #include "types.h"
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::canvas; using namespace anm2ed::canvas;
using namespace anm2ed::resources; using namespace anm2ed::resources;
@@ -18,7 +18,7 @@ namespace anm2ed::spritesheet_editor
{ {
} }
void SpritesheetEditor::update(DocumentManager& manager, Settings& settings, Resources& resources) void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& pan = document.editorPan; auto& pan = document.editorPan;

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "canvas.h" #include "canvas.h"
#include "document_manager.h" #include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -13,7 +13,6 @@ namespace anm2ed::spritesheet_editor
public: public:
SpritesheetEditor(); SpritesheetEditor();
void update(document_manager::DocumentManager& manager, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&);
resources::Resources& resources);
}; };
} }

View File

@@ -1,10 +1,13 @@
#include "spritesheets.h" #include "spritesheets.h"
#include <ranges>
#include "imgui.h" #include "imgui.h"
#include "toast.h" #include "toast.h"
#include <ranges>
using namespace anm2ed::anm2; using namespace anm2ed::anm2;
using namespace anm2ed::clipboard;
using namespace anm2ed::manager;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::dialog; using namespace anm2ed::dialog;
@@ -15,17 +18,72 @@ using namespace glm;
namespace anm2ed::spritesheets namespace anm2ed::spritesheets
{ {
void Spritesheets::update(Document& document, Settings& settings, Resources& resources, Dialog& dialog) void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
Clipboard& clipboard)
{ {
auto& document = *manager.get();
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& selection = document.selectedSpritesheets; auto& multiSelect = document.spritesheetMultiSelect;
auto& unused = document.unusedSpritesheetIDs;
if (document.is_just_changed(change::SPRITESHEETS)) unusedSpritesheetIDs = anm2.spritesheets_unused(); auto& hovered = document.hoveredSpritesheet;
auto& reference = document.referenceSpritesheet;
if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets)) if (ImGui::Begin("Spritesheets", &settings.windowIsSpritesheets))
{ {
auto style = ImGui::GetStyle(); auto style = ImGui::GetStyle();
storage.user_data_set(&selection);
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); auto childSize = imgui::size_without_footer_get(2);
@@ -37,7 +95,7 @@ namespace anm2ed::spritesheets
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2()); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
storage.begin(anm2.content.spritesheets.size()); multiSelect.start(anm2.content.spritesheets.size());
for (auto& [id, spritesheet] : anm2.content.spritesheets) for (auto& [id, spritesheet] : anm2.content.spritesheets)
{ {
@@ -45,15 +103,16 @@ namespace anm2ed::spritesheets
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders)) if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
{ {
auto isSelected = selection.contains(id); auto isSelected = multiSelect.contains(id);
auto isReferenced = id == document.referenceSpritesheet; auto isReferenced = id == reference;
auto cursorPos = ImGui::GetCursorPos(); auto cursorPos = ImGui::GetCursorPos();
auto& texture = spritesheet.texture; 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::SetNextItemSelectionUserData(id);
ImGui::SetNextItemStorageID(id); ImGui::SetNextItemStorageID(id);
if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) if (ImGui::Selectable("##Spritesheet Selectable", isSelected, 0, spritesheetChildSize)) reference = id;
document.referenceSpritesheet = id; if (ImGui::IsItemHovered()) hovered = id;
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
@@ -63,32 +122,31 @@ namespace anm2ed::spritesheets
auto viewport = ImGui::GetMainViewport(); auto viewport = ImGui::GetMainViewport();
auto size = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f auto textureSize = texture.size.x * texture.size.y > (viewport->Size.x * viewport->Size.y) * 0.5f
? to_vec2(viewport->Size) * 0.5f ? to_vec2(viewport->Size) * 0.5f
: vec2(texture.size); : vec2(texture.size);
auto aspectRatio = (float)texture.size.x / texture.size.y; auto aspectRatio = (float)texture.size.x / texture.size.y;
if (size.x / size.y > aspectRatio) if (textureSize.x / textureSize.y > aspectRatio)
size.x = size.y * aspectRatio; textureSize.x = textureSize.y * aspectRatio;
else else
size.y = size.x / aspectRatio; textureSize.y = textureSize.x / aspectRatio;
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(size), ImGuiChildFlags_Borders)) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
if (ImGui::BeginChild("##Spritesheet Tooltip Image Child", to_imvec2(textureSize),
ImGuiChildFlags_Borders))
ImGui::Image(texture.id, ImGui::GetContentRegionAvail()); ImGui::Image(texture.id, ImGui::GetContentRegionAvail());
ImGui::PopStyleVar();
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::BeginChild( if (ImGui::BeginChild("##Spritesheet Info Tooltip Child"))
"##Spritesheet Info Tooltip Child",
ImVec2(ImGui::CalcTextSize(spritesheet.path.c_str()).x + ImGui::GetTextLineHeightWithSpacing(),
0)))
{ {
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE); ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
ImGui::TextUnformatted(spritesheet.path.c_str()); ImGui::TextUnformatted(path);
ImGui::PopFont(); ImGui::PopFont();
ImGui::Text("ID: %d", id); ImGui::Text("ID: %d", id);
ImGui::Text("Size: %d x %d", texture.size.x, texture.size.y); ImGui::Text("Size: %d x %d", texture.size.x, texture.size.y);
@@ -97,7 +155,6 @@ namespace anm2ed::spritesheets
ImGui::EndTooltip(); ImGui::EndTooltip();
} }
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
auto imageSize = to_imvec2(vec2(spritesheetChildSize.y)); auto imageSize = to_imvec2(vec2(spritesheetChildSize.y));
@@ -116,22 +173,24 @@ namespace anm2ed::spritesheets
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2)); spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE); if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
ImGui::Text(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()); ImGui::Text(SPRITESHEET_FORMAT, id, path);
if (isReferenced) ImGui::PopFont(); if (isReferenced) ImGui::PopFont();
context_menu();
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopID(); ImGui::PopID();
} }
storage.end(); multiSelect.finish();
ImGui::PopStyleVar(); ImGui::PopStyleVar(2);
context_menu();
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleVar();
auto rowOneWidgetSize = imgui::widget_size_with_row_get(4); auto rowOneWidgetSize = imgui::widget_size_with_row_get(4);
imgui::shortcut(settings.shortcutAdd); imgui::shortcut(settings.shortcutAdd);
@@ -140,24 +199,21 @@ namespace anm2ed::spritesheets
if (dialog.is_selected_file(dialog::SPRITESHEET_OPEN)) if (dialog.is_selected_file(dialog::SPRITESHEET_OPEN))
{ {
int id{}; document.spritesheet_add(dialog.path);
anm2.spritesheet_add(document.directory_get(), dialog.path, id);
selection = {id};
document.change(change::SPRITESHEETS);
dialog.reset(); dialog.reset();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.empty()); ImGui::BeginDisabled(multiSelect.empty());
{ {
if (ImGui::Button("Reload", rowOneWidgetSize)) if (ImGui::Button("Reload", rowOneWidgetSize))
{ {
for (auto& id : selection) for (auto& id : multiSelect)
{ {
Spritesheet& spritesheet = anm2.content.spritesheets[id]; Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet.reload(document.directory_get()); spritesheet.reload(document.directory_get());
toasts.add(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string())); toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
} }
} }
ImGui::SetItemTooltip("Reloads the selected spritesheets."); ImGui::SetItemTooltip("Reloads the selected spritesheets.");
@@ -166,7 +222,7 @@ namespace anm2ed::spritesheets
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.size() != 1); ImGui::BeginDisabled(multiSelect.size() != 1);
{ {
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.spritesheet_replace(); if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.spritesheet_replace();
ImGui::SetItemTooltip("Replace the selected spritesheet with a new one."); ImGui::SetItemTooltip("Replace the selected spritesheet with a new one.");
@@ -175,27 +231,27 @@ namespace anm2ed::spritesheets
if (dialog.is_selected_file(dialog::SPRITESHEET_REPLACE)) if (dialog.is_selected_file(dialog::SPRITESHEET_REPLACE))
{ {
auto& id = *selection.begin(); auto& id = *multiSelect.begin();
Spritesheet& spritesheet = anm2.content.spritesheets[id]; Spritesheet& spritesheet = anm2.content.spritesheets[id];
spritesheet = Spritesheet(document.directory_get(), dialog.path); spritesheet = Spritesheet(document.directory_get(), dialog.path);
toasts.add(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string())); toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
dialog.reset(); dialog.reset();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(unusedSpritesheetIDs.empty()); ImGui::BeginDisabled(unused.empty());
{ {
imgui::shortcut(settings.shortcutRemove); imgui::shortcut(settings.shortcutRemove);
if (ImGui::Button("Remove Unused", rowOneWidgetSize)) if (ImGui::Button("Remove Unused", rowOneWidgetSize))
{ {
for (auto& id : unusedSpritesheetIDs) for (auto& id : unused)
{ {
Spritesheet& spritesheet = anm2.content.spritesheets[id]; Spritesheet& spritesheet = anm2.content.spritesheets[id];
toasts.add(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string())); toasts.info(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string()));
anm2.spritesheet_remove(id); anm2.spritesheet_remove(id);
} }
unusedSpritesheetIDs.clear(); unused.clear();
document.change(change::SPRITESHEETS); document.change(change::SPRITESHEETS);
} }
imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).", imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
@@ -206,11 +262,11 @@ namespace anm2ed::spritesheets
auto rowTwoWidgetSize = imgui::widget_size_with_row_get(3); auto rowTwoWidgetSize = imgui::widget_size_with_row_get(3);
imgui::shortcut(settings.shortcutSelectAll); imgui::shortcut(settings.shortcutSelectAll);
ImGui::BeginDisabled(selection.size() == anm2.content.spritesheets.size()); ImGui::BeginDisabled(multiSelect.size() == anm2.content.spritesheets.size());
{ {
if (ImGui::Button("Select All", rowTwoWidgetSize)) if (ImGui::Button("Select All", rowTwoWidgetSize))
for (auto& id : anm2.content.spritesheets | std::views::keys) for (auto& id : anm2.content.spritesheets | std::views::keys)
selection.insert(id); multiSelect.insert(id);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
imgui::set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll); imgui::set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll);
@@ -218,24 +274,24 @@ namespace anm2ed::spritesheets
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutSelectNone); imgui::shortcut(settings.shortcutSelectNone);
ImGui::BeginDisabled(selection.empty()); ImGui::BeginDisabled(multiSelect.empty());
if (ImGui::Button("Select None", rowTwoWidgetSize)) selection.clear(); if (ImGui::Button("Select None", rowTwoWidgetSize)) multiSelect.clear();
imgui::set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone); imgui::set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone);
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(selection.empty()); ImGui::BeginDisabled(multiSelect.empty());
{ {
if (ImGui::Button("Save", rowTwoWidgetSize)) if (ImGui::Button("Save", rowTwoWidgetSize))
{ {
for (auto& id : selection) for (auto& id : multiSelect)
{ {
Spritesheet& spritesheet = anm2.content.spritesheets[id]; Spritesheet& spritesheet = anm2.content.spritesheets[id];
if (spritesheet.save(document.directory_get())) if (spritesheet.save(document.directory_get()))
toasts.add(std::format("Saved spritesheet #{}: {}", id, spritesheet.path.string())); toasts.info(std::format("Saved spritesheet #{}: {}", id, spritesheet.path.string()));
else else
toasts.add(std::format("Unable to save spritesheet #{}: {}", id, spritesheet.path.string())); toasts.info(std::format("Unable to save spritesheet #{}: {}", id, spritesheet.path.string()));
} }
} }
} }
@@ -244,4 +300,4 @@ namespace anm2ed::spritesheets
} }
ImGui::End(); ImGui::End();
} }
} }

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "clipboard.h"
#include "dialog.h" #include "dialog.h"
#include "document.h" #include "manager.h"
#include "imgui.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -10,11 +10,8 @@ namespace anm2ed::spritesheets
{ {
class Spritesheets class Spritesheets
{ {
imgui::MultiSelectStorage storage{};
std::set<int> unusedSpritesheetIDs{};
public: public:
void update(document::Document& document, settings::Settings& settings, resources::Resources& resources, void update(manager::Manager&, settings::Settings&, resources::Resources&, dialog::Dialog&,
dialog::Dialog& dialog); clipboard::Clipboard& clipboard);
}; };
} }

View File

@@ -30,8 +30,9 @@ namespace anm2ed::state
{ {
if (auto document = manager.get()) if (auto document = manager.get())
if (auto animation = document->animation_get()) if (auto animation = document->animation_get())
if (playback.isPlaying) if (document->playback.isPlaying)
playback.tick(document->anm2.info.fps, animation->frameNum, animation->isLoop || settings.playbackIsLoop); document->playback.tick(document->anm2.info.fps, animation->frameNum,
animation->isLoop || settings.playbackIsLoop);
} }
void State::update(SDL_Window*& window, Settings& settings) void State::update(SDL_Window*& window, Settings& settings)
@@ -44,9 +45,21 @@ namespace anm2ed::state
switch (event.type) switch (event.type)
{ {
case SDL_EVENT_DROP_FILE: case SDL_EVENT_DROP_FILE:
if (auto droppedFile = event.drop.data; filesystem::path_is_extension(droppedFile, "anm2")) {
auto droppedFile = event.drop.data;
if (filesystem::path_is_extension(droppedFile, "anm2"))
manager.open(std::string(droppedFile)); manager.open(std::string(droppedFile));
else if (filesystem::path_is_extension(droppedFile, "png"))
{
if (auto document = manager.get())
document->spritesheet_add(droppedFile);
else
toasts.warning(std::format("Could not open spritesheet: (open a document first!)", droppedFile));
}
else
toasts.warning(std::format("Could not parse file: {} (must be .anm2 or .png)", droppedFile));
break; break;
}
case SDL_EVENT_QUIT: case SDL_EVENT_QUIT:
isQuit = true; isQuit = true;
break; break;
@@ -59,8 +72,8 @@ namespace anm2ed::state
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
taskbar.update(settings, dialog, manager, isQuit); taskbar.update(manager, settings, dialog, isQuit);
dockspace.update(taskbar, documents, manager, settings, resources, dialog, playback); dockspace.update(taskbar, documents, manager, settings, resources, dialog, clipboard);
toasts.update(); toasts.update();
documents.update(taskbar, manager, resources); documents.update(taskbar, manager, resources);

View File

@@ -8,16 +8,16 @@ namespace anm2ed::state
{ {
class State class State
{ {
void tick(settings::Settings& settings); void tick(settings::Settings&);
void update(SDL_Window*& window, settings::Settings& settings); void update(SDL_Window*&, settings::Settings&);
void render(SDL_Window*& window, settings::Settings& settings); void render(SDL_Window*&, settings::Settings&);
public: public:
bool isQuit{}; bool isQuit{};
dialog::Dialog dialog; dialog::Dialog dialog;
resources::Resources resources; resources::Resources resources;
playback::Playback playback; manager::Manager manager;
document_manager::DocumentManager manager; clipboard::Clipboard clipboard;
taskbar::Taskbar taskbar; taskbar::Taskbar taskbar;
documents::Documents documents; documents::Documents documents;
@@ -26,8 +26,8 @@ namespace anm2ed::state
uint64_t previousTick{}; uint64_t previousTick{};
uint64_t previousUpdate{}; uint64_t previousUpdate{};
State(SDL_Window*& window, std::vector<std::string>& arguments); State(SDL_Window*&, std::vector<std::string>&);
void loop(SDL_Window*& window, settings::Settings& settings); void loop(SDL_Window*&, settings::Settings&);
}; };
}; };

View File

@@ -7,12 +7,12 @@
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::dialog; using namespace anm2ed::dialog;
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::types; using namespace anm2ed::types;
namespace anm2ed::taskbar namespace anm2ed::taskbar
{ {
void Taskbar::update(Settings& settings, Dialog& dialog, DocumentManager& manager, bool& isQuit) void Taskbar::update(Manager& manager, Settings& settings, Dialog& dialog, bool& isQuit)
{ {
auto document = manager.get(); auto document = manager.get();
auto animation = document ? document->animation_get() : nullptr; auto animation = document ? document->animation_get() : nullptr;
@@ -89,7 +89,11 @@ namespace anm2ed::taskbar
if (ImGui::BeginMenu("Settings")) if (ImGui::BeginMenu("Settings"))
{ {
if (ImGui::MenuItem("Configure")) configurePopup.open(); if (ImGui::MenuItem("Configure"))
{
editSettings = settings;
configurePopup.open();
}
ImGui::EndMenu(); ImGui::EndMenu();
} }

View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "dialog.h" #include "dialog.h"
#include "document_manager.h"
#include "imgui.h" #include "imgui.h"
#include "manager.h"
#include "settings.h" #include "settings.h"
namespace anm2ed::taskbar namespace anm2ed::taskbar
@@ -12,12 +12,11 @@ namespace anm2ed::taskbar
imgui::PopupHelper configurePopup{imgui::PopupHelper("Configure")}; imgui::PopupHelper configurePopup{imgui::PopupHelper("Configure")};
imgui::PopupHelper aboutPopup{imgui::PopupHelper("About")}; imgui::PopupHelper aboutPopup{imgui::PopupHelper("About")};
settings::Settings editSettings{}; settings::Settings editSettings{};
int selectedShortcut{}; int selectedShortcut{-1};
public: public:
float height{}; float height{};
void update(settings::Settings& settings, dialog::Dialog& dialog, document_manager::DocumentManager& manager, void update(manager::Manager&, settings::Settings&, dialog::Dialog&, bool&);
bool& isQuit);
}; };
}; };

View File

@@ -29,7 +29,7 @@ namespace anm2ed::texture
return id != 0; return id != 0;
} }
void Texture::download(std::vector<uint8_t>& pixels) void Texture::download()
{ {
pixels.resize(size.x * size.y * CHANNELS); pixels.resize(size.x * size.y * CHANNELS);
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
@@ -37,9 +37,20 @@ namespace anm2ed::texture
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
void Texture::init(const uint8_t* data, bool isDownload) void Texture::upload(const uint8_t* data)
{ {
glGenTextures(1, &id); if (!data || size.x <= 0 || size.y <= 0) return;
const size_t pixelCount = static_cast<size_t>(size.x) * static_cast<size_t>(size.y) * CHANNELS;
pixels.assign(data, data + pixelCount);
upload();
}
void Texture::upload()
{
if (pixels.empty() || size.x <= 0 || size.y <= 0) return;
if (!is_valid()) glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
@@ -48,12 +59,10 @@ namespace anm2ed::texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
if (isDownload) download(pixels);
} }
Texture::Texture() = default; Texture::Texture() = default;
@@ -63,11 +72,31 @@ namespace anm2ed::texture
if (is_valid()) glDeleteTextures(1, &id); if (is_valid()) glDeleteTextures(1, &id);
} }
Texture::Texture(const Texture& other)
{
*this = other;
}
Texture::Texture(Texture&& other) Texture::Texture(Texture&& other)
{ {
*this = std::move(other); *this = std::move(other);
} }
Texture& Texture::operator=(const Texture& other)
{
if (this != &other)
{
if (is_valid()) glDeleteTextures(1, &id);
id = 0;
size = other.size;
filter = other.filter;
channels = other.channels;
pixels = other.pixels;
if (!pixels.empty()) upload();
}
return *this;
}
Texture& Texture::operator=(Texture&& other) Texture& Texture::operator=(Texture&& other)
{ {
if (this != &other) if (this != &other)
@@ -94,24 +123,21 @@ namespace anm2ed::texture
size = svgSize; size = svgSize;
filter = GL_LINEAR; filter = GL_LINEAR;
init(bitmap.data()); upload(bitmap.data());
} }
Texture::Texture(const std::string& pngPath, bool isDownload) Texture::Texture(const std::string& pngPath)
{ {
if (const uint8* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data) if (const uint8* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
{ {
init(data, isDownload); upload(data);
stbi_image_free((void*)data); stbi_image_free((void*)data);
} }
} }
bool Texture::write_png(const std::string& path) bool Texture::write_png(const std::string& path)
{ {
std::vector<uint8_t> pixels; return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS);
download(pixels);
const bool isSuccess = stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, pixels.data(), size.x * CHANNELS);
return isSuccess;
} }
void Texture::bind(GLuint unit) void Texture::bind(GLuint unit)
@@ -125,4 +151,4 @@ namespace anm2ed::texture
glActiveTexture(GL_TEXTURE0 + unit); glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_2D, 0);
} }
} }

View File

@@ -20,17 +20,20 @@ namespace anm2ed::texture
std::vector<uint8_t> pixels{}; std::vector<uint8_t> pixels{};
bool is_valid(); bool is_valid();
void download(std::vector<uint8_t>& pixels); void download();
void init(const uint8_t* data, bool isDownload = false); void upload(const uint8_t*);
void upload();
Texture(); Texture();
~Texture(); ~Texture();
Texture(Texture&& other); Texture(const Texture&);
Texture& operator=(Texture&& other); Texture(Texture&&);
Texture(const char* svgData, size_t svgDataLength, glm::ivec2 svgSize); Texture& operator=(const Texture&);
Texture(const std::string& pngPath, bool isDownload = false); Texture& operator=(Texture&&);
bool write_png(const std::string& path); Texture(const char*, size_t, glm::ivec2);
void bind(GLuint unit = 0); Texture(const std::string&);
void unbind(GLuint unit = 0); bool write_png(const std::string&);
void bind(GLuint = 0);
void unbind(GLuint = 0);
}; };
} }

View File

@@ -7,7 +7,7 @@
#include "imgui.h" #include "imgui.h"
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace anm2ed::document_manager; using namespace anm2ed::manager;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::playback; using namespace anm2ed::playback;
@@ -50,9 +50,12 @@ namespace anm2ed::timeline
- Press {} to shorten the selected frame, by one frame. - Press {} to shorten the selected frame, by one frame.
- Hold Alt while clicking a non-trigger frame to toggle interpolation.)"; - Hold Alt while clicking a non-trigger frame to toggle interpolation.)";
void Timeline::item_child(anm2::Anm2& anm2, anm2::Reference& reference, anm2::Animation* animation, void Timeline::item_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings,
Settings& settings, Resources& resources, anm2::Type type, int id, int& index) Resources& resources, anm2::Type type, int id, int& index)
{ {
auto& anm2 = document.anm2;
auto& reference = document.reference;
auto item = animation ? animation->item_get(type, id) : nullptr; auto item = animation ? animation->item_get(type, id) : nullptr;
auto isVisible = item ? item->isVisible : false; auto isVisible = item ? item->isVisible : false;
auto isActive = reference.itemType == type && reference.itemID == id; auto isActive = reference.itemType == type && reference.itemID == id;
@@ -102,7 +105,24 @@ namespace anm2ed::timeline
{ {
anm2::Reference itemReference = {reference.animationIndex, type, id}; anm2::Reference itemReference = {reference.animationIndex, type, id};
if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) reference = itemReference; if (ImGui::IsWindowHovered())
{
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
{
switch (type)
{
case anm2::LAYER:
manager.layer_properties_open(id); // Handled in layers.cpp
break;
case anm2::NULL_:
manager.null_properties_open(id); // Handled in layers.cpp
default:
break;
}
}
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) reference = itemReference;
}
ImGui::Image(resources.icons[icon].id, imgui::icon_size_get()); ImGui::Image(resources.icons[icon].id, imgui::icon_size_get());
ImGui::SameLine(); ImGui::SameLine();
@@ -121,7 +141,7 @@ namespace anm2ed::timeline
int visibleIcon = isVisible ? icon::VISIBLE : icon::INVISIBLE; int visibleIcon = isVisible ? icon::VISIBLE : icon::INVISIBLE;
if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get())) if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get()))
isVisible = !isVisible; document.item_visible_toggle(item);
ImGui::SetItemTooltip(isVisible ? "The item is shown. Press to hide." : "The item is hidden. Press to show."); ImGui::SetItemTooltip(isVisible ? "The item is shown. Press to hide." : "The item is hidden. Press to show.");
if (type == anm2::NULL_) if (type == anm2::NULL_)
@@ -134,7 +154,7 @@ namespace anm2ed::timeline
ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x, ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2)); (itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
if (ImGui::ImageButton("##Rect Toggle", resources.icons[rectIcon].id, imgui::icon_size_get())) if (ImGui::ImageButton("##Rect Toggle", resources.icons[rectIcon].id, imgui::icon_size_get()))
isShowRect = !isShowRect; document.null_rect_toggle(null);
ImGui::SetItemTooltip(isShowRect ? "The null's rect is shown. Press to hide." ImGui::SetItemTooltip(isShowRect ? "The null's rect is shown. Press to hide."
: "The null's rect is hidden. Press to show."); : "The null's rect is hidden. Press to show.");
} }
@@ -180,9 +200,9 @@ namespace anm2ed::timeline
index++; index++;
} }
void Timeline::items_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources) void Timeline::items_child(Manager& manager, Document& document, anm2::Animation* animation, Settings& settings,
Resources& resources)
{ {
auto& anm2 = document.anm2;
auto& reference = document.reference; auto& reference = document.reference;
auto itemsChildSize = ImVec2(ImGui::GetTextLineHeightWithSpacing() * 15, ImGui::GetContentRegionAvail().y); auto itemsChildSize = ImVec2(ImGui::GetTextLineHeightWithSpacing() * 15, ImGui::GetContentRegionAvail().y);
@@ -212,7 +232,7 @@ namespace anm2ed::timeline
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
item_child(anm2, reference, animation, settings, resources, type, id, index); item_child(manager, document, animation, settings, resources, type, id, index);
}; };
item_child_row(anm2::NONE); item_child_row(anm2::NONE);
@@ -253,20 +273,24 @@ namespace anm2ed::timeline
} }
ImGui::EndChild(); ImGui::EndChild();
auto widgetSize = imgui::widget_size_with_row_get(2);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + style.WindowPadding.x, ImGui::GetCursorPosY()));
auto widgetSize = imgui::widget_size_with_row_get(2, ImGui::GetContentRegionAvail().x - style.WindowPadding.x);
ImGui::BeginDisabled(!animation); ImGui::BeginDisabled(!animation);
{ {
imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Add", widgetSize)) propertiesPopup.open(); if (ImGui::Button("Add", widgetSize)) propertiesPopup.open();
ImGui::SetItemTooltip("%s", "Add a new item to the animation."); imgui::set_item_tooltip_shortcut("Add a new item to the animation.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(document.item_get()); ImGui::BeginDisabled(!document.item_get() && reference.itemType != anm2::LAYER &&
reference.itemType != anm2::NULL_);
{ {
ImGui::Button("Remove", widgetSize); imgui::shortcut(settings.shortcutRemove);
ImGui::SetItemTooltip("%s", "Remove the selected items from the animation."); if (ImGui::Button("Remove", widgetSize)) document.item_remove(animation);
imgui::set_item_tooltip_shortcut("Remove the selected items from the animation.", settings.shortcutRemove);
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
@@ -278,9 +302,10 @@ namespace anm2ed::timeline
} }
void Timeline::frame_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources, void Timeline::frame_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources,
Playback& playback, anm2::Type type, int id, int& index, float width) anm2::Type type, int id, int& index, float width)
{ {
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference; auto& reference = document.reference;
auto item = animation ? animation->item_get(type, id) : nullptr; auto item = animation ? animation->item_get(type, id) : nullptr;
auto isVisible = item ? item->isVisible : false; auto isVisible = item ? item->isVisible : false;
@@ -455,7 +480,11 @@ namespace anm2ed::timeline
if (ImGui::Button("##Frame Button", size)) if (ImGui::Button("##Frame Button", size))
{ {
if (type != anm2::TRIGGER && ImGui::IsKeyDown(ImGuiMod_Alt)) frame.isInterpolated = !frame.isInterpolated; if (type != anm2::TRIGGER && ImGui::IsKeyDown(ImGuiMod_Alt)) frame.isInterpolated = !frame.isInterpolated;
if (type == anm2::LAYER) document.referenceSpritesheet = anm2.content.layers[id].spritesheetID; if (type == anm2::LAYER)
{
document.referenceSpritesheet = anm2.content.layers[id].spritesheetID;
document.layersMultiSelect = {id};
}
reference = frameReference; reference = frameReference;
reference.frameTime = frameTime; reference.frameTime = frameTime;
} }
@@ -481,10 +510,10 @@ namespace anm2ed::timeline
ImGui::PopID(); ImGui::PopID();
} }
void Timeline::frames_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources, void Timeline::frames_child(Document& document, anm2::Animation* animation, Settings& settings, Resources& resources)
Playback& playback)
{ {
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& playback = document.playback;
auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15; auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15;
@@ -541,7 +570,7 @@ namespace anm2ed::timeline
{ {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
frame_child(document, animation, settings, resources, playback, type, id, index, childWidth); frame_child(document, animation, settings, resources, type, id, index, childWidth);
}; };
frames_child_row(anm2::NONE); frames_child_row(anm2::NONE);
@@ -614,25 +643,35 @@ namespace anm2ed::timeline
ImGui::SameLine(); ImGui::SameLine();
imgui::shortcut(settings.shortcutAdd); auto item = document.item_get();
ImGui::Button("Insert Frame", widgetSize);
imgui::set_item_tooltip_shortcut("Insert a frame, based on the current selection.", settings.shortcutAdd);
ImGui::SameLine(); ImGui::BeginDisabled(!item);
{
imgui::shortcut(settings.shortcutAdd);
if (ImGui::Button("Insert Frame", widgetSize)) document.frames_add(item);
imgui::set_item_tooltip_shortcut("Insert a frame, based on the current selection.", settings.shortcutAdd);
imgui::shortcut(settings.shortcutRemove); ImGui::SameLine();
ImGui::Button("Delete Frame", widgetSize);
imgui::set_item_tooltip_shortcut("Delete the selected frames.", settings.shortcutRemove);
ImGui::SameLine(); ImGui::BeginDisabled(!document.frame_get());
{
imgui::shortcut(settings.shortcutRemove);
if (ImGui::Button("Delete Frame", widgetSize)) document.frames_delete(item);
imgui::set_item_tooltip_shortcut("Delete the selected frames.", settings.shortcutRemove);
ImGui::Button("Bake", widgetSize); ImGui::SameLine();
ImGui::SetItemTooltip("%s", "Turn interpolated frames into uninterpolated ones.");
if (ImGui::Button("Bake", widgetSize)) bakePopup.open();
ImGui::SetItemTooltip("Turn interpolated frames into uninterpolated ones.");
}
ImGui::EndDisabled();
}
ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Fit Animation Length", widgetSize)) animation->frameNum = animation->length(); if (ImGui::Button("Fit Animation Length", widgetSize)) animation->frameNum = animation->length();
ImGui::SetItemTooltip("%s", "The animation length will be set to the effective length of the animation."); ImGui::SetItemTooltip("The animation length will be set to the effective length of the animation.");
ImGui::SameLine(); ImGui::SameLine();
@@ -640,13 +679,13 @@ namespace anm2ed::timeline
ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), step::NORMAL, ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), step::NORMAL,
step::FAST, !animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0); step::FAST, !animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0);
if (animation) animation->frameNum = clamp(animation->frameNum, anm2::FRAME_NUM_MIN, anm2::FRAME_NUM_MAX); if (animation) animation->frameNum = clamp(animation->frameNum, anm2::FRAME_NUM_MIN, anm2::FRAME_NUM_MAX);
ImGui::SetItemTooltip("%s", "Set the animation's length."); ImGui::SetItemTooltip("Set the animation's length.");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(widgetSize.x); ImGui::SetNextItemWidth(widgetSize.x);
ImGui::Checkbox("Loop", animation ? &animation->isLoop : &dummy_value<bool>()); ImGui::Checkbox("Loop", animation ? &animation->isLoop : &dummy_value<bool>());
ImGui::SetItemTooltip("%s", "Toggle the animation looping."); ImGui::SetItemTooltip("Toggle the animation looping.");
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
@@ -655,13 +694,13 @@ namespace anm2ed::timeline
ImGui::SetNextItemWidth(widgetSize.x); ImGui::SetNextItemWidth(widgetSize.x);
ImGui::InputInt("FPS", &anm2.info.fps, 1, 5); ImGui::InputInt("FPS", &anm2.info.fps, 1, 5);
anm2.info.fps = clamp(anm2.info.fps, anm2::FPS_MIN, anm2::FPS_MAX); anm2.info.fps = clamp(anm2.info.fps, anm2::FPS_MIN, anm2::FPS_MAX);
ImGui::SetItemTooltip("%s", "Set the FPS of all animations."); ImGui::SetItemTooltip("Set the FPS of all animations.");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(widgetSize.x); ImGui::SetNextItemWidth(widgetSize.x);
imgui::input_text_string("Author", &anm2.info.createdBy); imgui::input_text_string("Author", &anm2.info.createdBy);
ImGui::SetItemTooltip("%s", "Set the author of the document."); ImGui::SetItemTooltip("Set the author of the document.");
ImGui::PopStyleVar(); ImGui::PopStyleVar();
} }
@@ -710,7 +749,6 @@ namespace anm2ed::timeline
auto optionsSize = imgui::child_size_get(11); auto optionsSize = imgui::child_size_get(11);
auto itemsSize = ImVec2(0, ImGui::GetContentRegionAvail().y - auto itemsSize = ImVec2(0, ImGui::GetContentRegionAvail().y -
(optionsSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 4)); (optionsSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 4));
if (ImGui::BeginChild("Options", optionsSize, ImGuiChildFlags_Borders)) if (ImGui::BeginChild("Options", optionsSize, ImGuiChildFlags_Borders))
{ {
ImGui::SeparatorText("Type"); ImGui::SeparatorText("Type");
@@ -827,17 +865,7 @@ namespace anm2ed::timeline
if (ImGui::Button("Add", widgetSize)) if (ImGui::Button("Add", widgetSize))
{ {
anm2::Reference addReference; document.item_add((anm2::Type)type, addItemID, addItemName, (locale::Type)locale, addItemSpritesheetID);
if (type == anm2::LAYER)
addReference = anm2.layer_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName,
addItemSpritesheetID, (locale::Type)locale);
else if (type == anm2::NULL_)
addReference =
anm2.null_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName, (locale::Type)locale);
reference = addReference;
item_properties_close(); item_properties_close();
} }
ImGui::SetItemTooltip("Add the item, with the settings specified."); ImGui::SetItemTooltip("Add the item, with the settings specified.");
@@ -849,11 +877,49 @@ namespace anm2ed::timeline
ImGui::EndPopup(); ImGui::EndPopup();
} }
bakePopup.trigger();
if (ImGui::BeginPopupModal(bakePopup.label, &bakePopup.isOpen, ImGuiWindowFlags_NoResize))
{
auto& interval = settings.bakeInterval;
auto& isRoundRotation = settings.bakeIsRoundRotation;
auto& isRoundScale = settings.bakeIsRoundScale;
auto frame = document.frame_get();
ImGui::InputInt("Interval", &interval, step::NORMAL, step::FAST);
ImGui::SetItemTooltip("Set the maximum delay of each frame that will be baked.");
interval = glm::clamp(interval, anm2::FRAME_DELAY_MIN, frame ? frame->delay : anm2::FRAME_DELAY_MIN);
ImGui::Checkbox("Round Rotation", &isRoundRotation);
ImGui::SetItemTooltip("Rotation will be rounded to the nearest whole number.");
ImGui::Checkbox("Round Scale", &isRoundScale);
ImGui::SetItemTooltip("Scale will be rounded to the nearest whole number.");
auto widgetSize = imgui::widget_size_with_row_get(2);
if (ImGui::Button("Bake", widgetSize))
{
document.frames_bake(interval, isRoundScale, isRoundRotation);
bakePopup.close();
}
ImGui::SetItemTooltip("Bake the selected frame(s) with the options selected.");
ImGui::SameLine();
if (ImGui::Button("Cancel", widgetSize)) bakePopup.close();
ImGui::SetItemTooltip("Cancel baking frames.");
ImGui::EndPopup();
}
} }
void Timeline::update(DocumentManager& manager, Settings& settings, Resources& resources, Playback& playback) void Timeline::update(Manager& manager, Settings& settings, Resources& resources)
{ {
auto& document = *manager.get(); auto& document = *manager.get();
auto& playback = document.playback;
auto& anm2 = document.anm2; auto& anm2 = document.anm2;
auto& reference = document.reference; auto& reference = document.reference;
auto animation = document.animation_get(); auto animation = document.animation_get();
@@ -864,8 +930,8 @@ namespace anm2ed::timeline
if (ImGui::Begin("Timeline", &settings.windowIsTimeline)) if (ImGui::Begin("Timeline", &settings.windowIsTimeline))
{ {
isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
frames_child(document, animation, settings, resources, playback); frames_child(document, animation, settings, resources);
items_child(document, animation, settings, resources); items_child(manager, document, animation, settings, resources);
} }
ImGui::PopStyleVar(); ImGui::PopStyleVar();
ImGui::End(); ImGui::End();

View File

@@ -2,9 +2,7 @@
#include "anm2.h" #include "anm2.h"
#include "document.h" #include "document.h"
#include "document_manager.h" #include "manager.h"
#include "imgui.h"
#include "playback.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -16,6 +14,7 @@ namespace anm2ed::timeline
bool isWindowHovered{}; bool isWindowHovered{};
bool isHorizontalScroll{}; bool isHorizontalScroll{};
imgui::PopupHelper propertiesPopup{imgui::PopupHelper("Item Properties")}; imgui::PopupHelper propertiesPopup{imgui::PopupHelper("Item Properties")};
imgui::PopupHelper bakePopup{imgui::PopupHelper("Bake", imgui::POPUP_SMALL, true)};
std::string addItemName{}; std::string addItemName{};
int addItemSpritesheetID{}; int addItemSpritesheetID{};
bool addItemIsRect{}; bool addItemIsRect{};
@@ -26,20 +25,16 @@ namespace anm2ed::timeline
ImDrawList* pickerLineDrawList{}; ImDrawList* pickerLineDrawList{};
ImGuiStyle style{}; ImGuiStyle style{};
void item_child(anm2::Anm2& anm2, anm2::Reference& reference, anm2::Animation* animation, void item_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&,
settings::Settings& settings, resources::Resources& resources, anm2::Type type, int id, int& index); anm2::Type, int, int&);
void items_child(Document& document, anm2::Animation* animation, settings::Settings& settings, void items_child(manager::Manager&, Document&, anm2::Animation*, settings::Settings&, resources::Resources&);
resources::Resources& resources); void frame_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&, anm2::Type, int,
void frame_child(document::Document& document, anm2::Animation* animation, settings::Settings& settings, int&, float);
resources::Resources& resources, playback::Playback& playback, anm2::Type type, int id, int& index, void frames_child(document::Document&, anm2::Animation*, settings::Settings&, resources::Resources&);
float width);
void frames_child(document::Document& document, anm2::Animation* animation, settings::Settings& settings,
resources::Resources& resources, playback::Playback& playback);
void popups(document::Document& document, anm2::Animation* animation, settings::Settings& settings); void popups(document::Document&, anm2::Animation*, settings::Settings&);
public: public:
void update(document_manager::DocumentManager& manager, settings::Settings& settings, void update(manager::Manager&, settings::Settings&, resources::Resources&);
resources::Resources& resources, playback::Playback& playback);
}; };
} }

View File

@@ -64,17 +64,23 @@ namespace anm2ed::toast
} }
} }
void Toasts::add(const std::string& message) void Toasts::info(const std::string& message)
{ {
toasts.emplace_back(Toast(message)); toasts.emplace_back(Toast(message));
logger.info(message); logger.info(message);
} }
void Toasts::add_error(const std::string& message) void Toasts::error(const std::string& message)
{ {
toasts.emplace_back(Toast(message)); toasts.emplace_back(Toast(message));
logger.error(message); logger.error(message);
} }
void Toasts::warning(const std::string& message)
{
toasts.emplace_back(Toast(message));
logger.warning(message);
}
Toasts toasts; Toasts toasts;
} }

View File

@@ -12,7 +12,7 @@ namespace anm2ed::toast
std::string message{}; std::string message{};
float lifetime{}; float lifetime{};
Toast(const std::string& message); Toast(const std::string&);
}; };
class Toasts class Toasts
@@ -21,8 +21,9 @@ namespace anm2ed::toast
std::vector<Toast> toasts{}; std::vector<Toast> toasts{};
void update(); void update();
void add(const std::string& message); void info(const std::string&);
void add_error(const std::string& message); void error(const std::string&);
void warning(const std::string&);
}; };
extern Toasts toasts; extern Toasts toasts;

View File

@@ -8,6 +8,7 @@
#include "types.h" #include "types.h"
using namespace anm2ed::settings; using namespace anm2ed::settings;
using namespace anm2ed::manager;
using namespace anm2ed::resources; using namespace anm2ed::resources;
using namespace anm2ed::types; using namespace anm2ed::types;
using namespace glm; using namespace glm;
@@ -16,8 +17,10 @@ namespace anm2ed::tools
{ {
constexpr auto COLOR_EDIT_LABEL = "##Color Edit"; constexpr auto COLOR_EDIT_LABEL = "##Color Edit";
void Tools::update(Settings& settings, Resources& resources) void Tools::update(Manager& manager, Settings& settings, Resources& resources)
{ {
auto& document = *manager.get();
if (ImGui::Begin("Tools", &settings.windowIsTools)) if (ImGui::Begin("Tools", &settings.windowIsTools))
{ {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2));
@@ -31,8 +34,10 @@ namespace anm2ed::tools
switch (type) switch (type)
{ {
case tool::UNDO: case tool::UNDO:
if (document.is_undo()) document.undo();
break; break;
case tool::REDO: case tool::REDO:
if (document.is_redo()) document.redo();
break; break;
case tool::COLOR: case tool::COLOR:
if (ImGui::IsPopupOpen(COLOR_EDIT_LABEL)) if (ImGui::IsPopupOpen(COLOR_EDIT_LABEL))
@@ -68,8 +73,12 @@ namespace anm2ed::tools
} }
else else
{ {
if (i == tool::UNDO) ImGui::BeginDisabled(!document.is_undo());
if (i == tool::REDO) ImGui::BeginDisabled(!document.is_redo());
if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size))) if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size)))
tool_switch((tool::Type)i); tool_switch((tool::Type)i);
if (i == tool::UNDO) ImGui::EndDisabled();
if (i == tool::REDO) ImGui::EndDisabled();
} }
auto widthIncrement = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x; auto widthIncrement = ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "manager.h"
#include "resources.h" #include "resources.h"
#include "settings.h" #include "settings.h"
@@ -11,6 +12,6 @@ namespace anm2ed::tools
ImVec2 colorEditPosition{}; ImVec2 colorEditPosition{};
public: public:
void update(settings::Settings& settings, resources::Resources& resources); void update(manager::Manager&, settings::Settings&, resources::Resources&);
}; };
} }

View File

@@ -15,10 +15,20 @@ namespace anm2ed::types::change
ANIMATIONS, ANIMATIONS,
ITEMS, ITEMS,
FRAMES, FRAMES,
ALL,
COUNT COUNT
}; };
} }
namespace anm2ed::types::draw_order
{
enum Type
{
BELOW,
ABOVE
};
}
namespace anm2ed::types::shortcut namespace anm2ed::types::shortcut
{ {
enum Type enum Type

View File

@@ -1,20 +1,23 @@
#pragma once #pragma once
#include <algorithm>
#include <map> #include <map>
#include <ranges>
#include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace anm2ed::util::time namespace anm2ed::util::time
{ {
std::string get(const char* format); std::string get(const char*);
} }
namespace anm2ed::util::string namespace anm2ed::util::string
{ {
std::string to_lower(const std::string& string); std::string to_lower(const std::string&);
std::string replace_backslash(const std::string& string); std::string replace_backslash(const std::string&);
bool to_bool(const std::string& string); bool to_bool(const std::string&);
} }
namespace anm2ed::util::map namespace anm2ed::util::map
@@ -52,4 +55,44 @@ namespace anm2ed::util::vector
{ {
return index >= 0 && index < (int)v.size() ? &v[index] : nullptr; return index >= 0 && index < (int)v.size() ? &v[index] : nullptr;
} }
}
template <typename T> std::set<int> move_indices(std::vector<T>& v, std::vector<int>& indices, int index)
{
if (indices.empty()) return {};
std::vector<int> sorted = indices;
std::sort(sorted.begin(), sorted.end());
sorted.erase(std::unique(sorted.begin(), sorted.end()), sorted.end());
// Determine if we are dragging items from below the target (insert before) or above (insert after)
bool insertAfter = !sorted.empty() && sorted.front() <= index;
std::vector<T> moveItems;
moveItems.reserve(sorted.size());
for (int i : sorted)
moveItems.push_back(std::move(v[i]));
for (auto i : sorted | std::views::reverse)
v.erase(v.begin() + i);
int originalSize = (int)v.size() + (int)sorted.size();
int reference = insertAfter ? index + 1 : index;
reference = std::clamp(reference, 0, originalSize);
int removedBeforeReference = 0;
for (int i : sorted)
if (i < reference) ++removedBeforeReference;
int insertPos = reference - removedBeforeReference;
insertPos = std::clamp(insertPos, 0, (int)v.size());
v.insert(v.begin() + insertPos, std::make_move_iterator(moveItems.begin()),
std::make_move_iterator(moveItems.end()));
std::set<int> moveIndices{};
for (int i = 0; i < (int)moveItems.size(); i++)
moveIndices.insert(insertPos + i);
return moveIndices;
}
}

View File

@@ -7,9 +7,8 @@
namespace anm2ed::xml namespace anm2ed::xml
{ {
std::string document_to_string(tinyxml2::XMLDocument& self); std::string document_to_string(tinyxml2::XMLDocument&);
tinyxml2::XMLError query_string_attribute(tinyxml2::XMLElement* element, const char* attribute, std::string* out); tinyxml2::XMLError query_string_attribute(tinyxml2::XMLElement*, const char*, std::string*);
tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement* element, const char* attribute, tinyxml2::XMLError query_path_attribute(tinyxml2::XMLElement*, const char*, std::filesystem::path*);
std::filesystem::path* out); void query_color_attribute(tinyxml2::XMLElement*, const char*, float&);
void query_color_attribute(tinyxml2::XMLElement* element, const char* attribute, float& out); }
}