Refactoring, FFmpeg updates
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
@@ -79,7 +78,7 @@ namespace anm2ed::anm2
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::serialize(XMLDocument& document, XMLElement* parent)
|
||||
XMLElement* Animation::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("Animation");
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
@@ -103,7 +102,19 @@ namespace anm2ed::anm2
|
||||
|
||||
triggers.serialize(document, element, TRIGGER);
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Animation::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document));
|
||||
}
|
||||
|
||||
std::string Animation::to_string()
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
int Animation::length()
|
||||
@@ -126,49 +137,16 @@ namespace anm2ed::anm2
|
||||
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());
|
||||
}
|
||||
|
||||
vec4 Animation::rect(bool isRootTransform)
|
||||
{
|
||||
constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
|
||||
|
||||
float minX = std::numeric_limits<float>::infinity();
|
||||
float minY = std::numeric_limits<float>::infinity();
|
||||
float maxX = -std::numeric_limits<float>::infinity();
|
||||
float maxY = -std::numeric_limits<float>::infinity();
|
||||
bool any = false;
|
||||
|
||||
constexpr ivec2 CORNERS[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
|
||||
|
||||
for (float t = 0.0f; t < (float)frameNum; t += 1.0f)
|
||||
{
|
||||
mat4 transform(1.0f);
|
||||
@@ -202,5 +180,4 @@ namespace anm2ed::anm2
|
||||
if (!any) return vec4(-1.0f);
|
||||
return {minX, minY, maxX - minX, maxY - minY};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,10 +26,11 @@ namespace anm2ed::anm2
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
Item* item_get(Type, int = -1);
|
||||
void item_remove(Type, int = -1);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
std::string to_string();
|
||||
int length();
|
||||
glm::vec4 rect(bool);
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "animations.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
@@ -42,114 +40,4 @@ namespace anm2ed::anm2
|
||||
return length;
|
||||
}
|
||||
|
||||
int Animations::merge(int target, std::set<int>& sources, merge::Type type, bool isDeleteAfter)
|
||||
{
|
||||
Animation& animation = items.at(target);
|
||||
|
||||
if (!animation.name.ends_with(MERGED_STRING)) animation.name = animation.name + " " + MERGED_STRING;
|
||||
|
||||
auto merge_item = [&](Item& destination, Item& source)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case merge::APPEND:
|
||||
destination.frames.insert(destination.frames.end(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::PREPEND:
|
||||
destination.frames.insert(destination.frames.begin(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::REPLACE:
|
||||
if (destination.frames.size() < source.frames.size()) destination.frames.resize(source.frames.size());
|
||||
for (int i = 0; i < (int)source.frames.size(); i++)
|
||||
destination.frames[i] = source.frames[i];
|
||||
break;
|
||||
case merge::IGNORE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& i : sources)
|
||||
{
|
||||
if (i == target) continue;
|
||||
if (i < 0 || i >= (int)items.size()) continue;
|
||||
|
||||
auto& source = items.at(i);
|
||||
|
||||
merge_item(animation.rootAnimation, source.rootAnimation);
|
||||
|
||||
for (auto& [id, layerAnimation] : source.layerAnimations)
|
||||
{
|
||||
if (!animation.layerAnimations.contains(id))
|
||||
{
|
||||
animation.layerAnimations[id] = layerAnimation;
|
||||
animation.layerOrder.emplace_back(id);
|
||||
}
|
||||
merge_item(animation.layerAnimations[id], layerAnimation);
|
||||
}
|
||||
|
||||
for (auto& [id, nullAnimation] : source.nullAnimations)
|
||||
{
|
||||
if (!animation.nullAnimations.contains(id)) animation.nullAnimations[id] = nullAnimation;
|
||||
merge_item(animation.nullAnimations[id], nullAnimation);
|
||||
}
|
||||
|
||||
merge_item(animation.triggers, source.triggers);
|
||||
}
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
for (auto& source : std::ranges::reverse_view(sources))
|
||||
{
|
||||
if (source == target) continue;
|
||||
items.erase(items.begin() + source);
|
||||
}
|
||||
}
|
||||
|
||||
int finalIndex = target;
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
int numDeletedBefore = 0;
|
||||
for (auto& idx : sources)
|
||||
{
|
||||
if (idx == target) continue;
|
||||
if (idx >= 0 && idx < target) ++numDeletedBefore;
|
||||
}
|
||||
finalIndex -= numDeletedBefore;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,7 @@ namespace anm2ed::anm2
|
||||
Animations() = default;
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&);
|
||||
int length();
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
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);
|
||||
int length();
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
#include "time_.h"
|
||||
#include "unordered_map_.h"
|
||||
#include "vector_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace anm2ed::types;
|
||||
@@ -20,41 +17,6 @@ namespace anm2ed::anm2
|
||||
info.createdOn = time::get("%d-%B-%Y %I:%M:%S");
|
||||
}
|
||||
|
||||
bool Anm2::serialize(const std::string& path, std::string* errorString)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto* element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
if (document.SaveFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
if (errorString) *errorString = document.ErrorStr();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Anm2::to_string()
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto* element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
XMLPrinter printer;
|
||||
document.Print(&printer);
|
||||
return std::string(printer.CStr());
|
||||
}
|
||||
|
||||
Anm2::Anm2(const std::string& path, std::string* errorString)
|
||||
{
|
||||
XMLDocument document;
|
||||
@@ -75,302 +37,53 @@ namespace anm2ed::anm2
|
||||
animations = Animations((XMLElement*)animationsElement);
|
||||
}
|
||||
|
||||
bool Anm2::serialize(const std::string& path, std::string* errorString)
|
||||
{
|
||||
XMLDocument document;
|
||||
|
||||
auto* element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
if (document.SaveFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
if (errorString) *errorString = document.ErrorStr();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
XMLElement* Anm2::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("AnimatedActor");
|
||||
document.InsertFirstChild(element);
|
||||
|
||||
info.serialize(document, element);
|
||||
content.serialize(document, element);
|
||||
animations.serialize(document, element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
std::string Anm2::to_string()
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
uint64_t Anm2::hash()
|
||||
{
|
||||
return std::hash<std::string>{}(to_string());
|
||||
}
|
||||
|
||||
Animation* Anm2::animation_get(Reference reference)
|
||||
{
|
||||
return vector::find(animations.items, reference.animationIndex);
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::animation_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& animation : animations.items)
|
||||
names.push_back(animation.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
Item* Anm2::item_get(Reference reference)
|
||||
{
|
||||
if (Animation* animation = animation_get(reference))
|
||||
{
|
||||
switch (reference.itemType)
|
||||
{
|
||||
case ROOT:
|
||||
return &animation->rootAnimation;
|
||||
case LAYER:
|
||||
return unordered_map::find(animation->layerAnimations, reference.itemID);
|
||||
case NULL_:
|
||||
return map::find(animation->nullAnimations, reference.itemID);
|
||||
case TRIGGER:
|
||||
return &animation->triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Frame* Anm2::frame_get(Reference reference)
|
||||
{
|
||||
Item* item = item_get(reference);
|
||||
if (!item) return nullptr;
|
||||
return vector::find(item->frames, reference.frameIndex);
|
||||
if (auto item = item_get(reference); item)
|
||||
if (vector::in_bounds(item->frames, reference.frameIndex)) return &item->frames[reference.frameIndex];
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Anm2::spritesheet_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
Spritesheet spritesheet(directory, path);
|
||||
if (!spritesheet.is_valid()) return false;
|
||||
id = map::next_id_get(content.spritesheets);
|
||||
content.spritesheets[id] = std::move(spritesheet);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Anm2::spritesheet_remove(int id)
|
||||
{
|
||||
content.spritesheets.erase(id);
|
||||
}
|
||||
|
||||
Spritesheet* Anm2::spritesheet_get(int id)
|
||||
{
|
||||
return map::find(content.spritesheets, id);
|
||||
}
|
||||
|
||||
std::set<int> Anm2::spritesheets_unused()
|
||||
{
|
||||
return content.spritesheets_unused();
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::spritesheet_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& [id, spritesheet] : content.spritesheets)
|
||||
names.push_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
|
||||
return names;
|
||||
}
|
||||
|
||||
Reference Anm2::layer_add(Reference reference, std::string name, int spritesheetID, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID;
|
||||
auto& layer = content.layers[id];
|
||||
|
||||
layer.name = !name.empty() ? name : layer.name;
|
||||
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0;
|
||||
|
||||
auto add = [&](Animation* animation, int id)
|
||||
{
|
||||
animation->layerAnimations[id] = Item();
|
||||
animation->layerOrder.push_back(id);
|
||||
};
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.layerAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->layerAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
|
||||
Reference Anm2::null_add(Reference reference, std::string name, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.nulls) : reference.itemID;
|
||||
auto& null = content.nulls[id];
|
||||
|
||||
null.name = !name.empty() ? name : null.name;
|
||||
|
||||
auto add = [&](Animation* animation, int id) { animation->nullAnimations[id] = Item(); };
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.nullAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->nullAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
|
||||
void Anm2::event_add(int& id)
|
||||
{
|
||||
content.event_add(id);
|
||||
}
|
||||
|
||||
std::set<int> Anm2::events_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& frame : animation->triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& frame : animation.triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
|
||||
for (auto& id : content.events | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::layers_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.layers | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::nulls_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.nulls | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::event_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& event : content.events | std::views::values)
|
||||
names.push_back(event.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
bool Anm2::sound_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
id = map::next_id_get(content.sounds);
|
||||
content.sounds[id] = Sound(directory, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::sounds_unused()
|
||||
{
|
||||
std::set<int> used;
|
||||
for (auto& event : content.events | std::views::values)
|
||||
used.insert(event.soundID);
|
||||
|
||||
std::set<int> unused;
|
||||
for (auto& id : content.sounds | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::sound_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& [id, sound] : content.sounds)
|
||||
names.push_back(std::format(SOUND_FORMAT, id, sound.path.c_str()));
|
||||
return names;
|
||||
}
|
||||
|
||||
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.colorOffset = glm::mix(baseFrame.colorOffset, baseFrameNext.colorOffset, 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;
|
||||
}
|
||||
}
|
||||
|
||||
void Anm2::generate_from_grid(Reference reference, ivec2 startPosition, ivec2 size, ivec2 pivot, int columns,
|
||||
int count, int delay)
|
||||
{
|
||||
auto item = item_get(reference);
|
||||
if (!item) return;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
auto row = i / columns;
|
||||
auto column = i % columns;
|
||||
|
||||
Frame frame{};
|
||||
|
||||
frame.delay = delay;
|
||||
frame.pivot = pivot;
|
||||
frame.size = size;
|
||||
frame.crop = startPosition + ivec2(size.x * column, size.y * row);
|
||||
|
||||
item->frames.emplace_back(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ namespace anm2ed::anm2
|
||||
auto operator<=>(const Reference&) const = default;
|
||||
};
|
||||
|
||||
constexpr anm2::Reference REFERENCE_DEFAULT = {-1, anm2::NONE, -1, -1, -1};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
@@ -34,39 +32,47 @@ namespace anm2ed::anm2
|
||||
Animations animations{};
|
||||
|
||||
Anm2();
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&);
|
||||
bool serialize(const std::string&, std::string* = nullptr);
|
||||
std::string to_string();
|
||||
Anm2(const std::string&, std::string* = nullptr);
|
||||
uint64_t hash();
|
||||
Animation* animation_get(Reference);
|
||||
std::vector<std::string> animation_names_get();
|
||||
|
||||
Item* item_get(Reference);
|
||||
|
||||
Frame* frame_get(Reference);
|
||||
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
Spritesheet* spritesheet_get(int);
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
void spritesheet_remove(int);
|
||||
std::vector<std::string> spritesheet_labels_get();
|
||||
std::set<int> spritesheets_unused();
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type type, std::string*);
|
||||
|
||||
int layer_add();
|
||||
Reference layer_add(Reference = REFERENCE_DEFAULT, std::string = {}, int = 0,
|
||||
types::locale::Type = types::locale::GLOBAL);
|
||||
std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
|
||||
void layer_add(int&);
|
||||
std::set<int> layers_unused(Reference = {});
|
||||
bool layers_deserialize(const std::string&, types::merge::Type, std::string*);
|
||||
|
||||
Reference null_add(Reference = REFERENCE_DEFAULT, std::string = {}, types::locale::Type = types::locale::GLOBAL);
|
||||
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
|
||||
|
||||
bool sound_add(const std::string& directory, const std::string& path, int& id);
|
||||
std::vector<std::string> sound_names_get();
|
||||
std::set<int> sounds_unused();
|
||||
void null_add(int&);
|
||||
std::set<int> nulls_unused(Reference = {});
|
||||
bool nulls_deserialize(const std::string&, types::merge::Type, std::string*);
|
||||
|
||||
void event_add(int&);
|
||||
std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::vector<std::string> event_names_get();
|
||||
void bake(Reference, int = 1, bool = true, bool = true);
|
||||
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
std::vector<std::string> event_labels_get();
|
||||
std::set<int> events_unused(Reference = {});
|
||||
bool events_deserialize(const std::string&, types::merge::Type, std::string*);
|
||||
|
||||
bool sound_add(const std::string& directory, const std::string& path, int& id);
|
||||
std::vector<std::string> sound_labels_get();
|
||||
std::set<int> sounds_unused();
|
||||
bool sounds_deserialize(const std::string&, const std::string&, types::merge::Type, std::string*);
|
||||
|
||||
Animation* animation_get(Reference);
|
||||
std::vector<std::string> animation_labels_get();
|
||||
int animations_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);
|
||||
|
||||
Item* item_get(Reference);
|
||||
Reference layer_animation_add(Reference = {}, std::string = {}, int = 0,
|
||||
types::locale::Type = types::locale::GLOBAL);
|
||||
Reference null_animation_add(Reference = {}, std::string = {}, types::locale::Type = types::locale::GLOBAL);
|
||||
|
||||
Frame* frame_get(Reference);
|
||||
};
|
||||
}
|
||||
|
||||
137
src/anm2/anm2_animations.cpp
Normal file
137
src/anm2/anm2_animations.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::types;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Animation* Anm2::animation_get(Reference reference)
|
||||
{
|
||||
return vector::find(animations.items, reference.animationIndex);
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::animation_labels_get()
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back("None");
|
||||
for (auto& animation : animations.items)
|
||||
labels.emplace_back(animation.name);
|
||||
return labels;
|
||||
}
|
||||
|
||||
bool Anm2::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;
|
||||
animations.items.insert(animations.items.begin() + start + count, Animation(element));
|
||||
indices.insert(index);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int Anm2::animations_merge(int target, std::set<int>& sources, merge::Type type, bool isDeleteAfter)
|
||||
{
|
||||
auto& items = animations.items;
|
||||
auto& animation = animations.items.at(target);
|
||||
|
||||
if (!animation.name.ends_with(MERGED_STRING)) animation.name = animation.name + " " + MERGED_STRING;
|
||||
|
||||
auto merge_item = [&](Item& destination, Item& source)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case merge::APPEND:
|
||||
destination.frames.insert(destination.frames.end(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::PREPEND:
|
||||
destination.frames.insert(destination.frames.begin(), source.frames.begin(), source.frames.end());
|
||||
break;
|
||||
case merge::REPLACE:
|
||||
if (destination.frames.size() < source.frames.size()) destination.frames.resize(source.frames.size());
|
||||
for (int i = 0; i < (int)source.frames.size(); i++)
|
||||
destination.frames[i] = source.frames[i];
|
||||
break;
|
||||
case merge::IGNORE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& i : sources)
|
||||
{
|
||||
if (i == target) continue;
|
||||
if (i < 0 || i >= (int)items.size()) continue;
|
||||
|
||||
auto& source = items.at(i);
|
||||
|
||||
merge_item(animation.rootAnimation, source.rootAnimation);
|
||||
|
||||
for (auto& [id, layerAnimation] : source.layerAnimations)
|
||||
{
|
||||
if (!animation.layerAnimations.contains(id))
|
||||
{
|
||||
animation.layerAnimations[id] = layerAnimation;
|
||||
animation.layerOrder.emplace_back(id);
|
||||
}
|
||||
merge_item(animation.layerAnimations[id], layerAnimation);
|
||||
}
|
||||
|
||||
for (auto& [id, nullAnimation] : source.nullAnimations)
|
||||
{
|
||||
if (!animation.nullAnimations.contains(id)) animation.nullAnimations[id] = nullAnimation;
|
||||
merge_item(animation.nullAnimations[id], nullAnimation);
|
||||
}
|
||||
|
||||
merge_item(animation.triggers, source.triggers);
|
||||
}
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
for (auto& source : std::ranges::reverse_view(sources))
|
||||
{
|
||||
if (source == target) continue;
|
||||
items.erase(items.begin() + source);
|
||||
}
|
||||
}
|
||||
|
||||
int finalIndex = target;
|
||||
|
||||
if (isDeleteAfter)
|
||||
{
|
||||
int numDeletedBefore = 0;
|
||||
for (auto& idx : sources)
|
||||
{
|
||||
if (idx == target) continue;
|
||||
if (idx >= 0 && idx < target) ++numDeletedBefore;
|
||||
}
|
||||
finalIndex -= numDeletedBefore;
|
||||
}
|
||||
|
||||
return finalIndex;
|
||||
}
|
||||
|
||||
}
|
||||
75
src/anm2/anm2_events.cpp
Normal file
75
src/anm2/anm2_events.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
void Anm2::event_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(content.events);
|
||||
content.events[id] = Event();
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::event_labels_get()
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back("None");
|
||||
for (auto& event : content.events | std::views::values)
|
||||
labels.emplace_back(event.name);
|
||||
return labels;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::events_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& frame : animation->triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& frame : animation.triggers.frames)
|
||||
used.insert(frame.eventID);
|
||||
|
||||
std::set<int> unused{};
|
||||
for (auto& id : content.events | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
bool Anm2::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 event = Event(element, id);
|
||||
if (type == merge::APPEND) id = map::next_id_get(content.events);
|
||||
content.events[id] = event;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
83
src/anm2/anm2_items.cpp
Normal file
83
src/anm2/anm2_items.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include "map_.h"
|
||||
#include "types.h"
|
||||
#include "unordered_map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Item* Anm2::item_get(Reference reference)
|
||||
{
|
||||
if (Animation* animation = animation_get(reference))
|
||||
{
|
||||
switch (reference.itemType)
|
||||
{
|
||||
case ROOT:
|
||||
return &animation->rootAnimation;
|
||||
case LAYER:
|
||||
return unordered_map::find(animation->layerAnimations, reference.itemID);
|
||||
case NULL_:
|
||||
return map::find(animation->nullAnimations, reference.itemID);
|
||||
case TRIGGER:
|
||||
return &animation->triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Reference Anm2::layer_animation_add(Reference reference, std::string name, int spritesheetID, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.layers) : reference.itemID;
|
||||
auto& layer = content.layers[id];
|
||||
|
||||
layer.name = !name.empty() ? name : layer.name;
|
||||
layer.spritesheetID = content.spritesheets.contains(spritesheetID) ? spritesheetID : 0;
|
||||
|
||||
auto add = [&](Animation* animation, int id)
|
||||
{
|
||||
animation->layerAnimations[id] = Item();
|
||||
animation->layerOrder.push_back(id);
|
||||
};
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.layerAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->layerAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
|
||||
Reference Anm2::null_animation_add(Reference reference, std::string name, locale::Type locale)
|
||||
{
|
||||
auto id = reference.itemID == -1 ? map::next_id_get(content.nulls) : reference.itemID;
|
||||
auto& null = content.nulls[id];
|
||||
|
||||
null.name = !name.empty() ? name : null.name;
|
||||
|
||||
auto add = [&](Animation* animation, int id) { animation->nullAnimations[id] = Item(); };
|
||||
|
||||
if (locale == locale::GLOBAL)
|
||||
{
|
||||
for (auto& animation : animations.items)
|
||||
if (!animation.nullAnimations.contains(id)) add(&animation, id);
|
||||
}
|
||||
else if (locale == locale::LOCAL)
|
||||
{
|
||||
if (auto animation = animation_get(reference))
|
||||
if (!animation->nullAnimations.contains(id)) add(animation, id);
|
||||
}
|
||||
|
||||
return {reference.animationIndex, LAYER, id};
|
||||
}
|
||||
}
|
||||
66
src/anm2/anm2_layers.cpp
Normal file
66
src/anm2/anm2_layers.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
void Anm2::layer_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(content.layers);
|
||||
content.layers[id] = Layer();
|
||||
}
|
||||
|
||||
std::set<int> Anm2::layers_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.layerAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.layers | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
bool Anm2::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(content.layers);
|
||||
content.layers[id] = layer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
src/anm2/anm2_nulls.cpp
Normal file
66
src/anm2/anm2_nulls.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
void Anm2::null_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(content.nulls);
|
||||
content.nulls[id] = Null();
|
||||
}
|
||||
|
||||
std::set<int> Anm2::nulls_unused(Reference reference)
|
||||
{
|
||||
std::set<int> used{};
|
||||
std::set<int> unused{};
|
||||
|
||||
if (auto animation = animation_get(reference); animation)
|
||||
for (auto& id : animation->nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
else
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& id : animation.nullAnimations | std::views::keys)
|
||||
used.insert(id);
|
||||
|
||||
for (auto& id : content.nulls | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
bool Anm2::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 null = Null(element, id);
|
||||
if (type == merge::APPEND) id = map::next_id_get(content.nulls);
|
||||
content.nulls[id] = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
75
src/anm2/anm2_sounds.cpp
Normal file
75
src/anm2/anm2_sounds.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
bool Anm2::sound_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
id = map::next_id_get(content.sounds);
|
||||
content.sounds[id] = Sound(directory, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::sound_labels_get()
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back("None");
|
||||
for (auto& [id, sound] : content.sounds)
|
||||
labels.emplace_back(sound.path.string());
|
||||
return labels;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::sounds_unused()
|
||||
{
|
||||
std::set<int> used;
|
||||
for (auto& animation : animations.items)
|
||||
for (auto& trigger : animation.triggers.frames)
|
||||
if (content.sounds.contains(trigger.soundID)) used.insert(trigger.soundID);
|
||||
|
||||
std::set<int> unused;
|
||||
for (auto& id : content.sounds | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
bool Anm2::sounds_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("Sound"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid sound(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
|
||||
for (auto element = document.FirstChildElement("Sound"); element; element = element->NextSiblingElement("Sound"))
|
||||
{
|
||||
auto sound = Sound(element, id);
|
||||
if (type == merge::APPEND) id = map::next_id_get(content.sounds);
|
||||
content.sounds[id] = std::move(sound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
87
src/anm2/anm2_spritesheets.cpp
Normal file
87
src/anm2/anm2_spritesheets.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Spritesheet* Anm2::spritesheet_get(int id)
|
||||
{
|
||||
return map::find(content.spritesheets, id);
|
||||
}
|
||||
|
||||
void Anm2::spritesheet_remove(int id)
|
||||
{
|
||||
content.spritesheets.erase(id);
|
||||
}
|
||||
|
||||
bool Anm2::spritesheet_add(const std::string& directory, const std::string& path, int& id)
|
||||
{
|
||||
Spritesheet spritesheet(directory, path);
|
||||
if (!spritesheet.is_valid()) return false;
|
||||
id = map::next_id_get(content.spritesheets);
|
||||
content.spritesheets[id] = std::move(spritesheet);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::set<int> Anm2::spritesheets_unused()
|
||||
{
|
||||
std::set<int> used{};
|
||||
for (auto& layer : content.layers | std::views::values)
|
||||
if (layer.is_spritesheet_valid()) used.insert(layer.spritesheetID);
|
||||
|
||||
std::set<int> unused{};
|
||||
for (auto& id : content.spritesheets | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::spritesheet_labels_get()
|
||||
{
|
||||
std::vector<std::string> labels{};
|
||||
labels.emplace_back("None");
|
||||
for (auto& [id, spritesheet] : content.spritesheets)
|
||||
labels.emplace_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
|
||||
return labels;
|
||||
}
|
||||
|
||||
bool Anm2::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;
|
||||
}
|
||||
|
||||
filesystem::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(content.spritesheets);
|
||||
content.spritesheets[id] = std::move(spritesheet);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,5 @@
|
||||
#include "content.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
@@ -60,197 +53,4 @@ namespace anm2ed::anm2
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
std::set<int> Content::spritesheets_unused()
|
||||
{
|
||||
std::set<int> used;
|
||||
for (auto& layer : layers | std::views::values)
|
||||
if (layer.spritesheetID != -1) used.insert(layer.spritesheetID);
|
||||
|
||||
std::set<int> unused;
|
||||
for (auto& id : spritesheets | std::views::keys)
|
||||
if (!used.contains(id)) unused.insert(id);
|
||||
|
||||
return unused;
|
||||
}
|
||||
|
||||
void Content::layer_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(layers);
|
||||
layers[id] = Layer();
|
||||
}
|
||||
|
||||
void Content::null_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(nulls);
|
||||
nulls[id] = Null();
|
||||
}
|
||||
|
||||
void Content::event_add(int& id)
|
||||
{
|
||||
id = map::next_id_get(events);
|
||||
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;
|
||||
}
|
||||
|
||||
filesystem::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;
|
||||
}
|
||||
|
||||
bool Content::sounds_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("Sound"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid sound(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
|
||||
for (auto element = document.FirstChildElement("Sound"); element; element = element->NextSiblingElement("Sound"))
|
||||
{
|
||||
auto sound = Sound(element, id);
|
||||
|
||||
if (type == merge::APPEND) id = map::next_id_get(sounds);
|
||||
|
||||
sounds[id] = std::move(sound);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "event.h"
|
||||
#include "layer.h"
|
||||
@@ -9,8 +8,6 @@
|
||||
#include "sound.h"
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
struct Content
|
||||
@@ -25,22 +22,5 @@ namespace anm2ed::anm2
|
||||
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
Content(tinyxml2::XMLElement*);
|
||||
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
std::set<int> spritesheets_unused();
|
||||
void spritesheet_remove(int&);
|
||||
bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void layer_add(int&);
|
||||
bool layers_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void null_add(int&);
|
||||
bool nulls_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void event_add(int&);
|
||||
bool events_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
|
||||
void sound_add(int&);
|
||||
bool sounds_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
};
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace anm2ed::anm2
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
int soundID{};
|
||||
int soundID{-1};
|
||||
|
||||
Event() = default;
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
|
||||
@@ -123,4 +123,12 @@ namespace anm2ed::anm2
|
||||
{
|
||||
delay = glm::clamp(++delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
||||
}
|
||||
|
||||
bool Frame::is_visible(Type type)
|
||||
{
|
||||
if (type == anm2::TRIGGER)
|
||||
return isVisible && eventID > -1;
|
||||
else
|
||||
return isVisible;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ namespace anm2ed::anm2
|
||||
X(delay, int, FRAME_DELAY_MIN) \
|
||||
X(atFrame, int, -1) \
|
||||
X(eventID, int, -1) \
|
||||
X(soundID, int, -1) \
|
||||
X(pivot, glm::vec2, {}) \
|
||||
X(crop, glm::vec2, {}) \
|
||||
X(position, glm::vec2, {}) \
|
||||
@@ -77,6 +78,7 @@ namespace anm2ed::anm2
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
|
||||
void shorten();
|
||||
void extend();
|
||||
bool is_visible(Type = NONE);
|
||||
};
|
||||
|
||||
struct FrameChange
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "item.h"
|
||||
#include <ranges>
|
||||
|
||||
#include "vector_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
@@ -114,7 +116,7 @@ namespace anm2ed::anm2
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Item::frames_change(anm2::FrameChange& change, ChangeType type, int start, int numberFrames)
|
||||
void Item::frames_change(FrameChange& change, ChangeType type, int start, int numberFrames)
|
||||
{
|
||||
auto useStart = numberFrames > -1 ? start : 0;
|
||||
auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size();
|
||||
@@ -197,4 +199,54 @@ namespace anm2ed::anm2
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Item::frames_bake(int index, int interval, bool isRoundScale, bool isRoundRotation)
|
||||
{
|
||||
if (!vector::in_bounds(frames, index)) return;
|
||||
|
||||
Frame& frame = frames[index];
|
||||
if (frame.delay == FRAME_DELAY_MIN) return;
|
||||
|
||||
Frame frameNext = vector::in_bounds(frames, index + 1) ? frames[index + 1] : frame;
|
||||
|
||||
int delay{};
|
||||
int i = index;
|
||||
|
||||
while (delay < frame.delay)
|
||||
{
|
||||
Frame baked = frame;
|
||||
float interpolation = (float)delay / frame.delay;
|
||||
baked.delay = std::min(interval, frame.delay - delay);
|
||||
baked.isInterpolated = (i == index) ? frame.isInterpolated : false;
|
||||
baked.rotation = glm::mix(frame.rotation, frameNext.rotation, interpolation);
|
||||
baked.position = glm::mix(frame.position, frameNext.position, interpolation);
|
||||
baked.scale = glm::mix(frame.scale, frameNext.scale, interpolation);
|
||||
baked.colorOffset = glm::mix(frame.colorOffset, frameNext.colorOffset, interpolation);
|
||||
baked.tint = glm::mix(frame.tint, frameNext.tint, interpolation);
|
||||
if (isRoundScale) baked.scale = vec2(ivec2(baked.scale));
|
||||
if (isRoundRotation) baked.rotation = (int)baked.rotation;
|
||||
|
||||
if (i == index)
|
||||
frames[i] = baked;
|
||||
else
|
||||
frames.insert(frames.begin() + i, baked);
|
||||
i++;
|
||||
|
||||
delay += baked.delay;
|
||||
}
|
||||
}
|
||||
|
||||
void Item::frames_generate_from_grid(ivec2 startPosition, ivec2 size, ivec2 pivot, int columns, int count, int delay)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Frame frame{};
|
||||
frame.delay = delay;
|
||||
frame.pivot = pivot;
|
||||
frame.size = size;
|
||||
frame.crop = startPosition + ivec2(size.x * (i % columns), size.y * (i / columns));
|
||||
|
||||
frames.emplace_back(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,9 @@ namespace anm2ed::anm2
|
||||
std::string to_string(Type, int = -1);
|
||||
int length(Type);
|
||||
Frame frame_generate(float, Type);
|
||||
void frames_change(anm2::FrameChange&, ChangeType, int, int = 0);
|
||||
void frames_change(FrameChange&, ChangeType, int, int = 0);
|
||||
bool frames_deserialize(const std::string&, Type, int, std::set<int>&, std::string*);
|
||||
void frames_bake(int, int, bool, bool);
|
||||
void frames_generate_from_grid(glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
};
|
||||
}
|
||||
@@ -36,4 +36,8 @@ namespace anm2ed::anm2
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
bool Layer::is_spritesheet_valid()
|
||||
{
|
||||
return spritesheetID > -1;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
|
||||
constexpr auto LAYER_NO_SPRITESHEET_FORMAT = "#{} {} (No Spritesheet)";
|
||||
|
||||
class Layer
|
||||
{
|
||||
@@ -18,5 +19,6 @@ namespace anm2ed::anm2
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
bool is_spritesheet_valid();
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "sound.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
@@ -28,8 +27,8 @@ namespace anm2ed::anm2
|
||||
Sound::Sound(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path) : this->path;
|
||||
this->path = filesystem::path_lower_case_backslash_handle(this->path);
|
||||
audio = Audio(this->path.c_str());
|
||||
}
|
||||
|
||||
@@ -38,8 +37,8 @@ namespace anm2ed::anm2
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
string::backslash_replace_to_lower(this->path);
|
||||
audio = Audio(this->path.c_str());
|
||||
path = filesystem::path_lower_case_backslash_handle(path);
|
||||
audio = Audio(path.c_str());
|
||||
}
|
||||
|
||||
XMLElement* Sound::to_element(XMLDocument& document, int id)
|
||||
@@ -56,4 +55,19 @@ namespace anm2ed::anm2
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
void Sound::reload(const std::string& directory)
|
||||
{
|
||||
*this = Sound(directory, this->path);
|
||||
}
|
||||
|
||||
bool Sound::is_valid()
|
||||
{
|
||||
return audio.is_valid();
|
||||
}
|
||||
|
||||
void Sound::play()
|
||||
{
|
||||
audio.play();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto SOUND_FORMAT = "#{} {}";
|
||||
constexpr auto SOUND_FORMAT_C = "#%d %s";
|
||||
constexpr auto SOUND_FORMAT = "{}";
|
||||
|
||||
class Sound
|
||||
{
|
||||
@@ -26,5 +25,8 @@ namespace anm2ed::anm2
|
||||
Sound(const std::string&, const std::string&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int);
|
||||
void reload(const std::string&);
|
||||
bool is_valid();
|
||||
void play();
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
@@ -18,15 +17,15 @@ namespace anm2ed::anm2
|
||||
// Spritesheet paths from Isaac Rebirth are made with the assumption that paths are case-insensitive
|
||||
// However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyway)
|
||||
// This will handle this case and make the paths OS-agnostic
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
path = filesystem::path_lower_case_backslash_handle(path);
|
||||
texture = Texture(path);
|
||||
}
|
||||
|
||||
Spritesheet::Spritesheet(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
this->path = string::backslash_replace_to_lower(this->path);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path) : this->path;
|
||||
this->path = filesystem::path_lower_case_backslash_handle(this->path);
|
||||
texture = Texture(this->path);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace anm2ed::canvas
|
||||
constexpr auto PIVOT_SIZE = glm::vec2(8, 8);
|
||||
constexpr auto ZOOM_MIN = 1.0f;
|
||||
constexpr auto ZOOM_MAX = 2000.0f;
|
||||
constexpr auto POSITION_FORMAT = "Position: ({:8} {:8})";
|
||||
constexpr auto POSITION_FORMAT = "Position: ({:8}, {:8})";
|
||||
|
||||
constexpr auto DASH_LENGTH = 4.0f;
|
||||
constexpr auto DASH_GAP = 1.0f;
|
||||
@@ -20,6 +20,11 @@ namespace anm2ed::canvas
|
||||
|
||||
constexpr auto STEP = 1.0f;
|
||||
constexpr auto STEP_FAST = 5.0f;
|
||||
|
||||
constexpr auto GRID_SIZE_MIN = 1;
|
||||
constexpr auto GRID_SIZE_MAX = 10000;
|
||||
constexpr auto GRID_OFFSET_MIN = 0;
|
||||
constexpr auto GRID_OFFSET_MAX = 10000;
|
||||
}
|
||||
|
||||
namespace anm2ed
|
||||
|
||||
666
src/document.cpp
666
src/document.cpp
@@ -1,13 +1,10 @@
|
||||
#include "document.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <utility>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "log.h"
|
||||
#include "map_.h"
|
||||
#include "toast.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace anm2ed::anm2;
|
||||
using namespace anm2ed::imgui;
|
||||
@@ -35,6 +32,43 @@ namespace anm2ed
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
Document::Document(Document&& other) noexcept
|
||||
: path(std::move(other.path)), snapshots(std::move(other.snapshots)), current(snapshots.current),
|
||||
anm2(current.anm2), reference(current.reference), playback(current.playback), animation(current.animation),
|
||||
merge(current.merge), event(current.event), layer(current.layer), null(current.null), sound(current.sound),
|
||||
spritesheet(current.spritesheet), message(current.message), previewZoom(other.previewZoom),
|
||||
previewPan(other.previewPan), editorPan(other.editorPan), editorZoom(other.editorZoom),
|
||||
overlayIndex(other.overlayIndex), hoveredFrame(other.hoveredFrame), hash(other.hash), saveHash(other.saveHash),
|
||||
autosaveHash(other.autosaveHash), lastAutosaveTime(other.lastAutosaveTime), isOpen(other.isOpen),
|
||||
isForceDirty(other.isForceDirty), isAnimationPreviewSet(other.isAnimationPreviewSet),
|
||||
isSpritesheetEditorSet(other.isSpritesheetEditorSet)
|
||||
{
|
||||
}
|
||||
|
||||
Document& Document::operator=(Document&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
path = std::move(other.path);
|
||||
snapshots = std::move(other.snapshots);
|
||||
previewZoom = other.previewZoom;
|
||||
previewPan = other.previewPan;
|
||||
editorPan = other.editorPan;
|
||||
editorZoom = other.editorZoom;
|
||||
overlayIndex = other.overlayIndex;
|
||||
hoveredFrame = other.hoveredFrame;
|
||||
hash = other.hash;
|
||||
saveHash = other.saveHash;
|
||||
autosaveHash = other.autosaveHash;
|
||||
lastAutosaveTime = other.lastAutosaveTime;
|
||||
isOpen = other.isOpen;
|
||||
isForceDirty = other.isForceDirty;
|
||||
isAnimationPreviewSet = other.isAnimationPreviewSet;
|
||||
isSpritesheetEditorSet = other.isSpritesheetEditorSet;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Document::save(const std::string& path, std::string* errorString)
|
||||
{
|
||||
this->path = !path.empty() ? path : this->path.string();
|
||||
@@ -51,18 +85,35 @@ namespace anm2ed
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Document::autosave(const std::string& path, std::string* errorString)
|
||||
std::filesystem::path Document::autosave_path_get()
|
||||
{
|
||||
if (anm2.serialize(path, errorString))
|
||||
return directory_get() / std::string("." + filename_get().string() + ".autosave");
|
||||
}
|
||||
|
||||
std::filesystem::path Document::path_from_autosave_get(std::filesystem::path& path)
|
||||
{
|
||||
auto fileName = path.filename().string();
|
||||
if (!fileName.empty() && fileName.front() == '.') fileName.erase(fileName.begin());
|
||||
|
||||
auto restorePath = path.parent_path() / fileName;
|
||||
restorePath.replace_extension("");
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
bool Document::autosave(std::string* errorString)
|
||||
{
|
||||
auto autosavePath = autosave_path_get();
|
||||
if (anm2.serialize(autosavePath, errorString))
|
||||
{
|
||||
autosaveHash = hash;
|
||||
lastAutosaveTime = 0.0f;
|
||||
toasts.info("Autosaving...");
|
||||
logger.info(std::format("Autosaved document to: {}", path));
|
||||
logger.info(std::format("Autosaved document to: {}", autosavePath.string()));
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
toasts.warning(std::format("Could not autosave document to: {} ({})", path, *errorString));
|
||||
toasts.warning(std::format("Could not autosave document to: {} ({})", autosavePath.string(), *errorString));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -84,41 +135,30 @@ namespace anm2ed
|
||||
{
|
||||
hash_set();
|
||||
|
||||
auto layers_set = [&]() { unusedLayerIDs = anm2.layers_unused(); };
|
||||
auto nulls_set = [&]() { unusedNullIDs = anm2.nulls_unused(); };
|
||||
auto layers_set = [&]() { layer.unused = anm2.layers_unused(); };
|
||||
auto nulls_set = [&]() { null.unused = anm2.nulls_unused(); };
|
||||
auto events_set = [&]()
|
||||
{
|
||||
unusedEventIDs = anm2.events_unused();
|
||||
eventNames = anm2.event_names_get();
|
||||
for (auto& name : eventNames)
|
||||
eventNamesCStr.push_back(name.c_str());
|
||||
event.unused = anm2.events_unused();
|
||||
event.labels_set(anm2.event_labels_get());
|
||||
};
|
||||
|
||||
auto animations_set = [&]()
|
||||
{
|
||||
animationNames = anm2.animation_names_get();
|
||||
animationNamesCStr.clear();
|
||||
animationNames.insert(animationNames.begin(), "None");
|
||||
for (auto& name : animationNames)
|
||||
animationNamesCStr.push_back(name.c_str());
|
||||
};
|
||||
auto animations_set = [&]() { animation.labels_set(anm2.animation_labels_get()); };
|
||||
|
||||
auto spritesheets_set = [&]()
|
||||
{
|
||||
unusedSpritesheetIDs = anm2.spritesheets_unused();
|
||||
spritesheetNames = anm2.spritesheet_names_get();
|
||||
spritesheetNamesCStr.clear();
|
||||
for (auto& name : spritesheetNames)
|
||||
spritesheetNamesCStr.push_back(name.c_str());
|
||||
spritesheet.unused = anm2.spritesheets_unused();
|
||||
spritesheet.labels_set(anm2.spritesheet_labels_get());
|
||||
};
|
||||
|
||||
auto sounds_set = [&]()
|
||||
{
|
||||
unusedSoundIDs = anm2.sounds_unused();
|
||||
soundNames = anm2.sound_names_get();
|
||||
soundNamesCStr.clear();
|
||||
for (auto& name : soundNames)
|
||||
soundNamesCStr.push_back(name.c_str());
|
||||
sound.unused = anm2.sounds_unused();
|
||||
sound.labels_set(anm2.sound_labels_get());
|
||||
|
||||
for (auto& animation : anm2.animations.items)
|
||||
for (auto& trigger : animation.triggers.frames)
|
||||
if (!anm2.content.sounds.contains(trigger.soundID)) trigger.soundID = -1;
|
||||
};
|
||||
|
||||
switch (type)
|
||||
@@ -132,23 +172,21 @@ namespace anm2ed
|
||||
case EVENTS:
|
||||
events_set();
|
||||
break;
|
||||
case ANIMATIONS:
|
||||
animations_set();
|
||||
break;
|
||||
case SPRITESHEETS:
|
||||
spritesheets_set();
|
||||
break;
|
||||
case SOUNDS:
|
||||
sounds_set();
|
||||
break;
|
||||
case ITEMS:
|
||||
case ANIMATIONS:
|
||||
animations_set();
|
||||
break;
|
||||
case ALL:
|
||||
layers_set();
|
||||
nulls_set();
|
||||
events_set();
|
||||
animations_set();
|
||||
spritesheets_set();
|
||||
animations_set();
|
||||
sounds_set();
|
||||
break;
|
||||
default:
|
||||
@@ -186,569 +224,56 @@ namespace anm2ed
|
||||
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(Document::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(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_crop_set(anm2::Frame* frame, vec2 crop)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Crop");
|
||||
frame->crop = crop;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_size_set(anm2::Frame* frame, vec2 size)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Size");
|
||||
frame->size = size;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_position_set(anm2::Frame* frame, vec2 position)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Position");
|
||||
frame->position = position;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_pivot_set(anm2::Frame* frame, vec2 pivot)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Pivot");
|
||||
frame->pivot = pivot;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_scale_set(anm2::Frame* frame, vec2 scale)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Scale");
|
||||
frame->scale = scale;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_rotation_set(anm2::Frame* frame, float rotation)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Rotation");
|
||||
frame->rotation = rotation;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_delay_set(anm2::Frame* frame, int delay)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Delay");
|
||||
frame->delay = delay;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_tint_set(anm2::Frame* frame, vec4 tint)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Tint");
|
||||
frame->tint = tint;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_color_offset_set(anm2::Frame* frame, vec3 colorOffset)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Color Offset");
|
||||
frame->colorOffset = colorOffset;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_is_visible_set(anm2::Frame* frame, bool isVisible)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Visibility");
|
||||
frame->isVisible = isVisible;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_is_interpolated_set(anm2::Frame* frame, bool isInterpolated)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Interpolation");
|
||||
frame->isInterpolated = isInterpolated;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_flip_x(anm2::Frame* frame)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Flip X");
|
||||
frame->scale.x = -frame->scale.x;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_flip_y(anm2::Frame* frame)
|
||||
{
|
||||
if (!frame) return;
|
||||
snapshot("Frame Flip Y");
|
||||
frame->scale.y = -frame->scale.y;
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_shorten()
|
||||
{
|
||||
auto frame = frame_get();
|
||||
if (!frame) return;
|
||||
snapshot("Shorten Frame");
|
||||
frame->shorten();
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_extend()
|
||||
{
|
||||
auto frame = frame_get();
|
||||
if (!frame) return;
|
||||
snapshot("Extend Frame");
|
||||
frame->extend();
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frames_change(anm2::FrameChange& frameChange, anm2::ChangeType type, bool isFromSelectedFrame,
|
||||
int numberFrames)
|
||||
{
|
||||
auto item = item_get();
|
||||
if (!item) return;
|
||||
snapshot("Change All Frame Properties");
|
||||
item->frames_change(frameChange, type, isFromSelectedFrame && frame_get() ? reference.frameIndex : 0,
|
||||
isFromSelectedFrame ? numberFrames : -1);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frames_deserialize(const std::string& string)
|
||||
{
|
||||
if (auto item = item_get())
|
||||
{
|
||||
snapshot("Paste Frame(s)");
|
||||
std::set<int> indices{};
|
||||
std::string errorString{};
|
||||
auto start = reference.frameIndex + 1;
|
||||
if (item->frames_deserialize(string, reference.itemType, start, indices, &errorString))
|
||||
change(Document::FRAMES);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize frame(s): {}", errorString));
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize frame(s): select an item first!"));
|
||||
}
|
||||
|
||||
anm2::Item* Document::item_get()
|
||||
{
|
||||
return anm2.item_get(reference);
|
||||
}
|
||||
|
||||
anm2::Spritesheet* Document::spritesheet_get()
|
||||
{
|
||||
return anm2.spritesheet_get(referenceSpritesheet);
|
||||
}
|
||||
|
||||
void Document::spritesheet_add(const std::string& path)
|
||||
{
|
||||
int id{};
|
||||
snapshot("Add Spritesheet");
|
||||
if (anm2.spritesheet_add(directory_get(), path, id))
|
||||
{
|
||||
spritesheetMultiSelect = {id};
|
||||
toasts.info(std::format("Initialized spritesheet #{}: {}", id, path));
|
||||
change(Document::SPRITESHEETS);
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to initialize spritesheet: {}", path));
|
||||
}
|
||||
|
||||
void Document::spritesheets_deserialize(const std::string& string, merge::Type type)
|
||||
{
|
||||
snapshot("Paste Spritesheet(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.spritesheets_deserialize(string, directory_get(), type, &errorString))
|
||||
change(Document::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(Document::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(Document::LAYERS);
|
||||
}
|
||||
|
||||
void Document::layers_remove_unused()
|
||||
{
|
||||
snapshot("Remove Unused Layers");
|
||||
for (auto& id : unusedLayerIDs)
|
||||
anm2.content.layers.erase(id);
|
||||
change(Document::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(Document::NULLS);
|
||||
}
|
||||
|
||||
void Document::null_rect_toggle(anm2::Null& null)
|
||||
{
|
||||
snapshot("Null Rect");
|
||||
null.isShowRect = !null.isShowRect;
|
||||
change(Document::NULLS);
|
||||
}
|
||||
|
||||
void Document::nulls_remove_unused()
|
||||
{
|
||||
snapshot("Remove Unused Nulls");
|
||||
for (auto& id : unusedNullIDs)
|
||||
anm2.content.nulls.erase(id);
|
||||
change(Document::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(Document::NULLS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize null(s): {}", errorString));
|
||||
}
|
||||
|
||||
void Document::event_set(anm2::Event& event)
|
||||
{
|
||||
if (referenceEvent > -1)
|
||||
{
|
||||
snapshot("Set Event");
|
||||
anm2.content.events[referenceEvent] = event;
|
||||
eventMultiSelect = {referenceEvent};
|
||||
}
|
||||
else
|
||||
{
|
||||
snapshot("Add Event");
|
||||
auto id = map::next_id_get(anm2.content.events);
|
||||
anm2.content.events[id] = event;
|
||||
eventMultiSelect = {id};
|
||||
}
|
||||
change(Document::EVENTS);
|
||||
}
|
||||
|
||||
void Document::events_remove_unused()
|
||||
{
|
||||
snapshot("Remove Unused Events");
|
||||
for (auto& id : unusedEventIDs)
|
||||
anm2.content.events.erase(id);
|
||||
change(Document::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(Document::EVENTS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize event(s): {}", errorString));
|
||||
}
|
||||
|
||||
void Document::sound_add(const std::string& path)
|
||||
{
|
||||
int id{};
|
||||
snapshot("Add Sound");
|
||||
if (anm2.sound_add(directory_get(), path, id))
|
||||
{
|
||||
soundMultiSelect = {id};
|
||||
toasts.info(std::format("Initialized sound #{}: {}", id, path));
|
||||
change(Document::SOUNDS);
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to initialize sound: {}", path));
|
||||
}
|
||||
|
||||
void Document::sounds_remove_unused()
|
||||
{
|
||||
snapshot("Remove Unused Sounds");
|
||||
for (auto& id : unusedSoundIDs)
|
||||
anm2.content.sounds.erase(id);
|
||||
change(Document::LAYERS);
|
||||
unusedSoundIDs.clear();
|
||||
}
|
||||
|
||||
void Document::sounds_deserialize(const std::string& string, merge::Type type)
|
||||
{
|
||||
snapshot("Paste Sound(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.sounds_deserialize(string, directory_get(), type, &errorString))
|
||||
change(Document::EVENTS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize event(s): {}", errorString));
|
||||
}
|
||||
|
||||
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(Document::ITEMS);
|
||||
}
|
||||
|
||||
void Document::item_remove(anm2::Animation* animation)
|
||||
{
|
||||
if (!animation) return;
|
||||
snapshot("Remove Item");
|
||||
animation->item_remove(reference.itemType, reference.itemID);
|
||||
reference = {reference.animationIndex};
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
void Document::item_visible_toggle(anm2::Item* item)
|
||||
{
|
||||
if (!item) return;
|
||||
snapshot("Item Visibility");
|
||||
item->isVisible = !item->isVisible;
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
anm2::Animation* Document::animation_get()
|
||||
{
|
||||
return anm2.animation_get(reference);
|
||||
}
|
||||
|
||||
void Document::animation_set(int index)
|
||||
anm2::Spritesheet* Document::spritesheet_get()
|
||||
{
|
||||
snapshot("Select Animation");
|
||||
reference = {index};
|
||||
change(Document::ITEMS);
|
||||
return anm2.spritesheet_get(spritesheet.reference);
|
||||
}
|
||||
|
||||
void Document::animation_add()
|
||||
void Document::spritesheet_add(const std::string& path)
|
||||
{
|
||||
snapshot("Add Animation");
|
||||
anm2::Animation animation;
|
||||
if (anm2::Animation* referenceAnimation = animation_get())
|
||||
auto add = [&]()
|
||||
{
|
||||
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());
|
||||
int id{};
|
||||
if (anm2.spritesheet_add(directory_get(), path, id))
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
this->spritesheet.selection = {id};
|
||||
toasts.info(std::format("Initialized spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to initialize spritesheet: {}", path));
|
||||
};
|
||||
|
||||
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(Document::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(Document::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(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animations_remove()
|
||||
{
|
||||
snapshot("Remove Animation(s)");
|
||||
|
||||
if (!animationMultiSelect.empty())
|
||||
{
|
||||
for (auto& i : animationMultiSelect | std::views::reverse)
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + i);
|
||||
animationMultiSelect.clear();
|
||||
}
|
||||
else if (hoveredAnimation > -1)
|
||||
{
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + hoveredAnimation);
|
||||
hoveredAnimation = -1;
|
||||
}
|
||||
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animation_default()
|
||||
{
|
||||
snapshot("Default Animation");
|
||||
anm2.animations.defaultAnimation = anm2.animations.items[*animationMultiSelect.begin()].name;
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animations_deserialize(const std::string& string)
|
||||
{
|
||||
snapshot("Paste Animation(s)");
|
||||
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(Document::ANIMATIONS);
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize animation(s): {}", errorString));
|
||||
}
|
||||
|
||||
void Document::generate_animation_from_grid(ivec2 startPosition, ivec2 size, ivec2 pivot, int columns, int count,
|
||||
int delay)
|
||||
{
|
||||
snapshot("Generate Animation from Grid");
|
||||
|
||||
anm2.generate_from_grid(reference, startPosition, size, pivot, columns, count, delay);
|
||||
|
||||
if (auto animation = animation_get()) animation->frameNum = animation->length();
|
||||
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
void Document::animations_merge_quick()
|
||||
{
|
||||
snapshot("Merge Animation(s)");
|
||||
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(Document::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(Document::ANIMATIONS);
|
||||
DOCUMENT_EDIT_PTR(this, "Add Spritesheet", Document::SPRITESHEETS, add());
|
||||
}
|
||||
|
||||
void Document::snapshot(const std::string& message)
|
||||
{
|
||||
snapshots.push(anm2, reference, message);
|
||||
this->message = message;
|
||||
snapshots.push(current);
|
||||
}
|
||||
|
||||
void Document::undo()
|
||||
{
|
||||
snapshots.undo(anm2, reference, message);
|
||||
snapshots.undo();
|
||||
toasts.info(std::format("Undo: {}", message));
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
void Document::redo()
|
||||
{
|
||||
snapshots.redo();
|
||||
toasts.info(std::format("Redo: {}", message));
|
||||
snapshots.redo(anm2, reference, message);
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
@@ -761,4 +286,5 @@ namespace anm2ed
|
||||
{
|
||||
return !snapshots.redoStack.is_empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
150
src/document.h
150
src/document.h
@@ -1,18 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include "anm2/anm2.h"
|
||||
#include "imgui_.h"
|
||||
#include "playback.h"
|
||||
#include "snapshots.h"
|
||||
#include "types.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
|
||||
class Document
|
||||
{
|
||||
public:
|
||||
@@ -31,58 +27,29 @@ namespace anm2ed
|
||||
};
|
||||
|
||||
std::filesystem::path path{};
|
||||
anm2::Anm2 anm2{};
|
||||
std::string message{};
|
||||
Playback playback{};
|
||||
|
||||
Snapshots snapshots{};
|
||||
Snapshot& current = snapshots.current;
|
||||
|
||||
anm2::Anm2& anm2 = current.anm2;
|
||||
anm2::Reference& reference = current.reference;
|
||||
Playback& playback = current.playback;
|
||||
Storage& animation = current.animation;
|
||||
Storage& merge = current.merge;
|
||||
Storage& event = current.event;
|
||||
Storage& layer = current.layer;
|
||||
Storage& null = current.null;
|
||||
Storage& sound = current.sound;
|
||||
Storage& spritesheet = current.spritesheet;
|
||||
std::string& message = current.message;
|
||||
|
||||
float previewZoom{200};
|
||||
glm::vec2 previewPan{};
|
||||
glm::vec2 editorPan{};
|
||||
float editorZoom{200};
|
||||
int overlayIndex{-1};
|
||||
|
||||
int overlayIndex{};
|
||||
|
||||
anm2::Reference reference{};
|
||||
int hoveredAnimation{-1};
|
||||
int mergeTarget{-1};
|
||||
imgui::MultiSelectStorage animationMultiSelect;
|
||||
imgui::MultiSelectStorage animationMergeMultiSelect;
|
||||
std::vector<const char*> animationNamesCStr{};
|
||||
std::vector<std::string> animationNames{};
|
||||
|
||||
anm2::Reference hoveredFrame{anm2::REFERENCE_DEFAULT};
|
||||
|
||||
int referenceSpritesheet{-1};
|
||||
int hoveredSpritesheet{-1};
|
||||
std::set<int> unusedSpritesheetIDs{};
|
||||
std::vector<std::string> spritesheetNames{};
|
||||
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;
|
||||
std::vector<const char*> eventNamesCStr{};
|
||||
std::vector<std::string> eventNames{};
|
||||
|
||||
int referenceSound{-1};
|
||||
int hoveredSound{-1};
|
||||
std::set<int> unusedSoundIDs{};
|
||||
imgui::MultiSelectStorage soundMultiSelect;
|
||||
std::vector<const char*> soundNamesCStr{};
|
||||
std::vector<std::string> soundNames{};
|
||||
anm2::Reference hoveredFrame{};
|
||||
|
||||
uint64_t hash{};
|
||||
uint64_t saveHash{};
|
||||
@@ -90,10 +57,15 @@ namespace anm2ed
|
||||
double lastAutosaveTime{};
|
||||
bool isOpen{true};
|
||||
bool isForceDirty{false};
|
||||
bool isAnimationPreviewSet{false};
|
||||
bool isSpritesheetEditorSet{false};
|
||||
|
||||
Document(const std::string&, bool = false, std::string* = nullptr);
|
||||
Document(const Document&) = delete;
|
||||
Document& operator=(const Document&) = delete;
|
||||
Document(Document&&) noexcept;
|
||||
Document& operator=(Document&&) noexcept;
|
||||
bool save(const std::string& = {}, std::string* = nullptr);
|
||||
bool autosave(const std::string&, std::string* = nullptr);
|
||||
void hash_set();
|
||||
void clean();
|
||||
void on_change();
|
||||
@@ -105,83 +77,35 @@ namespace anm2ed
|
||||
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);
|
||||
void frame_crop_set(anm2::Frame*, glm::vec2);
|
||||
void frame_size_set(anm2::Frame*, glm::vec2);
|
||||
void frame_position_set(anm2::Frame*, glm::vec2);
|
||||
void frame_pivot_set(anm2::Frame*, glm::vec2);
|
||||
void frame_scale_set(anm2::Frame*, glm::vec2);
|
||||
void frame_rotation_set(anm2::Frame*, float);
|
||||
void frame_delay_set(anm2::Frame*, int);
|
||||
void frame_tint_set(anm2::Frame*, glm::vec4);
|
||||
void frame_color_offset_set(anm2::Frame*, glm::vec3);
|
||||
void frame_is_visible_set(anm2::Frame*, bool);
|
||||
void frame_is_interpolated_set(anm2::Frame*, bool);
|
||||
void frame_flip_x(anm2::Frame* frame);
|
||||
void frame_flip_y(anm2::Frame* frame);
|
||||
void frame_shorten();
|
||||
void frame_extend();
|
||||
void frames_change(anm2::FrameChange&, anm2::ChangeType, bool, int = -1);
|
||||
void frames_deserialize(const std::string&);
|
||||
|
||||
anm2::Item* item_get();
|
||||
void item_add(anm2::Type, int, std::string&, types::locale::Type, int);
|
||||
void item_remove(anm2::Animation*);
|
||||
void item_visible_toggle(anm2::Item*);
|
||||
|
||||
anm2::Spritesheet* spritesheet_get();
|
||||
void spritesheet_add(const std::string&);
|
||||
void spritesheets_deserialize(const std::string&, types::merge::Type);
|
||||
|
||||
void layer_set(anm2::Layer&);
|
||||
void layers_remove_unused();
|
||||
void layers_deserialize(const std::string&, types::merge::Type);
|
||||
|
||||
void null_set(anm2::Null&);
|
||||
void null_rect_toggle(anm2::Null&);
|
||||
void nulls_remove_unused();
|
||||
void nulls_deserialize(const std::string&, types::merge::Type);
|
||||
|
||||
void event_set(anm2::Event&);
|
||||
void events_remove_unused();
|
||||
void events_deserialize(const std::string&, types::merge::Type);
|
||||
|
||||
void sound_add(const std::string& path);
|
||||
void sounds_remove_unused();
|
||||
void sounds_deserialize(const std::string& string, types::merge::Type);
|
||||
|
||||
void animation_add();
|
||||
void animation_set(int);
|
||||
void animation_duplicate();
|
||||
void animation_default();
|
||||
void animations_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 generate_animation_from_grid(glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
void spritesheet_add(const std::string&);
|
||||
|
||||
bool autosave(std::string* = nullptr);
|
||||
std::filesystem::path autosave_path_get();
|
||||
std::filesystem::path path_from_autosave_get(std::filesystem::path&);
|
||||
|
||||
void snapshot(const std::string& message);
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
bool is_able_to_undo();
|
||||
bool is_able_to_redo();
|
||||
};
|
||||
|
||||
#define DOCUMENT_SNAPSHOT(document, message) document.snapshot(message);
|
||||
#define DOCUMENT_CHANGE(document, changeType) document.change(changeType);
|
||||
|
||||
#define DOCUMENT_EDIT(document, message, changeType, body) \
|
||||
{ \
|
||||
\
|
||||
DOCUMENT_SNAPSHOT(document, message) \
|
||||
document.snapshot(message); \
|
||||
body; \
|
||||
DOCUMENT_CHANGE(document, changeType) \
|
||||
document.change(changeType); \
|
||||
}
|
||||
|
||||
#define DOCUMENT_EDIT_PTR(document, message, changeType, body) \
|
||||
{ \
|
||||
document->snapshot(message); \
|
||||
body; \
|
||||
document->change(changeType); \
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "imgui.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
@@ -28,17 +28,13 @@ namespace anm2ed::imgui
|
||||
return ImGui::InputText(label, string->data(), string->capacity() + 1, flags, input_text_callback, string);
|
||||
}
|
||||
|
||||
bool combo_strings(const std::string& label, int* index, std::vector<std::string>& strings)
|
||||
bool combo_negative_one_indexed(const std::string& label, int* index, std::vector<const char*>& strings)
|
||||
{
|
||||
std::vector<const char*> items{};
|
||||
for (auto& string : strings)
|
||||
items.push_back(string.c_str());
|
||||
return ImGui::Combo(label.c_str(), index, items.data(), (int)items.size());
|
||||
}
|
||||
*index += 1;
|
||||
bool isActivated = ImGui::Combo(label.c_str(), index, strings.data(), (int)strings.size());
|
||||
*index -= 1;
|
||||
|
||||
bool combo_strings(const std::string& label, int* index, std::vector<const char*>& strings)
|
||||
{
|
||||
return ImGui::Combo(label.c_str(), index, strings.data(), (int)strings.size());
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
bool input_int_range(const char* label, int& value, int min, int max, int step, int stepFast,
|
||||
@@ -49,6 +45,21 @@ namespace anm2ed::imgui
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
bool input_int2_range(const char* label, ivec2& value, ivec2 min, ivec2 max, ImGuiInputTextFlags flags)
|
||||
{
|
||||
auto isActivated = ImGui::InputInt2(label, value_ptr(value), flags);
|
||||
value = glm::clamp(value, min, max);
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
bool input_float_range(const char* label, float& value, float min, float max, float step, float stepFast,
|
||||
const char* format, ImGuiInputTextFlags flags)
|
||||
{
|
||||
auto isActivated = ImGui::InputFloat(label, &value, step, stepFast, format, flags);
|
||||
value = glm::clamp(value, min, max);
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
|
||||
ImGuiSelectableFlags flags, bool* isRenamed)
|
||||
{
|
||||
@@ -263,7 +274,8 @@ namespace anm2ed::imgui
|
||||
{
|
||||
internal.UserData = this;
|
||||
|
||||
auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape, this->size(), size);
|
||||
auto io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d,
|
||||
this->size(), size);
|
||||
internal.ApplyRequests(io);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,108 +46,104 @@ namespace anm2ed::imgui
|
||||
#undef X
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, ImGuiKey> KEY_MAP = {{"A", ImGuiKey_A},
|
||||
{"B", ImGuiKey_B},
|
||||
{"C", ImGuiKey_C},
|
||||
{"D", ImGuiKey_D},
|
||||
{"E", ImGuiKey_E},
|
||||
{"F", ImGuiKey_F},
|
||||
{"G", ImGuiKey_G},
|
||||
{"H", ImGuiKey_H},
|
||||
{"I", ImGuiKey_I},
|
||||
{"J", ImGuiKey_J},
|
||||
{"K", ImGuiKey_K},
|
||||
{"L", ImGuiKey_L},
|
||||
{"M", ImGuiKey_M},
|
||||
{"N", ImGuiKey_N},
|
||||
{"O", ImGuiKey_O},
|
||||
{"P", ImGuiKey_P},
|
||||
{"Q", ImGuiKey_Q},
|
||||
{"R", ImGuiKey_R},
|
||||
{"S", ImGuiKey_S},
|
||||
{"T", ImGuiKey_T},
|
||||
{"U", ImGuiKey_U},
|
||||
{"V", ImGuiKey_V},
|
||||
{"W", ImGuiKey_W},
|
||||
{"X", ImGuiKey_X},
|
||||
{"Y", ImGuiKey_Y},
|
||||
{"Z", ImGuiKey_Z},
|
||||
const std::unordered_map<std::string, ImGuiKey> KEY_MAP = {
|
||||
{"A", ImGuiKey_A},
|
||||
{"B", ImGuiKey_B},
|
||||
{"C", ImGuiKey_C},
|
||||
{"D", ImGuiKey_D},
|
||||
{"E", ImGuiKey_E},
|
||||
{"F", ImGuiKey_F},
|
||||
{"G", ImGuiKey_G},
|
||||
{"H", ImGuiKey_H},
|
||||
{"I", ImGuiKey_I},
|
||||
{"J", ImGuiKey_J},
|
||||
{"K", ImGuiKey_K},
|
||||
{"L", ImGuiKey_L},
|
||||
{"M", ImGuiKey_M},
|
||||
{"N", ImGuiKey_N},
|
||||
{"O", ImGuiKey_O},
|
||||
{"P", ImGuiKey_P},
|
||||
{"Q", ImGuiKey_Q},
|
||||
{"R", ImGuiKey_R},
|
||||
{"S", ImGuiKey_S},
|
||||
{"T", ImGuiKey_T},
|
||||
{"U", ImGuiKey_U},
|
||||
{"V", ImGuiKey_V},
|
||||
{"W", ImGuiKey_W},
|
||||
{"X", ImGuiKey_X},
|
||||
{"Y", ImGuiKey_Y},
|
||||
{"Z", ImGuiKey_Z},
|
||||
|
||||
{"0", ImGuiKey_0},
|
||||
{"1", ImGuiKey_1},
|
||||
{"2", ImGuiKey_2},
|
||||
{"3", ImGuiKey_3},
|
||||
{"4", ImGuiKey_4},
|
||||
{"5", ImGuiKey_5},
|
||||
{"6", ImGuiKey_6},
|
||||
{"7", ImGuiKey_7},
|
||||
{"8", ImGuiKey_8},
|
||||
{"9", ImGuiKey_9},
|
||||
{"0", ImGuiKey_0},
|
||||
{"1", ImGuiKey_1},
|
||||
{"2", ImGuiKey_2},
|
||||
{"3", ImGuiKey_3},
|
||||
{"4", ImGuiKey_4},
|
||||
{"5", ImGuiKey_5},
|
||||
{"6", ImGuiKey_6},
|
||||
{"7", ImGuiKey_7},
|
||||
{"8", ImGuiKey_8},
|
||||
{"9", ImGuiKey_9},
|
||||
|
||||
{"Num0", ImGuiKey_Keypad0},
|
||||
{"Num1", ImGuiKey_Keypad1},
|
||||
{"Num2", ImGuiKey_Keypad2},
|
||||
{"Num3", ImGuiKey_Keypad3},
|
||||
{"Num4", ImGuiKey_Keypad4},
|
||||
{"Num5", ImGuiKey_Keypad5},
|
||||
{"Num6", ImGuiKey_Keypad6},
|
||||
{"Num7", ImGuiKey_Keypad7},
|
||||
{"Num8", ImGuiKey_Keypad8},
|
||||
{"Num9", ImGuiKey_Keypad9},
|
||||
{"NumAdd", ImGuiKey_KeypadAdd},
|
||||
{"NumSubtract", ImGuiKey_KeypadSubtract},
|
||||
{"NumMultiply", ImGuiKey_KeypadMultiply},
|
||||
{"NumDivide", ImGuiKey_KeypadDivide},
|
||||
{"NumEnter", ImGuiKey_KeypadEnter},
|
||||
{"NumDecimal", ImGuiKey_KeypadDecimal},
|
||||
{"Num0", ImGuiKey_Keypad0},
|
||||
{"Num1", ImGuiKey_Keypad1},
|
||||
{"Num2", ImGuiKey_Keypad2},
|
||||
{"Num3", ImGuiKey_Keypad3},
|
||||
{"Num4", ImGuiKey_Keypad4},
|
||||
{"Num5", ImGuiKey_Keypad5},
|
||||
{"Num6", ImGuiKey_Keypad6},
|
||||
{"Num7", ImGuiKey_Keypad7},
|
||||
{"Num8", ImGuiKey_Keypad8},
|
||||
{"Num9", ImGuiKey_Keypad9},
|
||||
{"NumAdd", ImGuiKey_KeypadAdd},
|
||||
{"NumSubtract", ImGuiKey_KeypadSubtract},
|
||||
{"NumMultiply", ImGuiKey_KeypadMultiply},
|
||||
{"NumDivide", ImGuiKey_KeypadDivide},
|
||||
{"NumEnter", ImGuiKey_KeypadEnter},
|
||||
{"NumDecimal", ImGuiKey_KeypadDecimal},
|
||||
|
||||
{"F1", ImGuiKey_F1},
|
||||
{"F2", ImGuiKey_F2},
|
||||
{"F3", ImGuiKey_F3},
|
||||
{"F4", ImGuiKey_F4},
|
||||
{"F5", ImGuiKey_F5},
|
||||
{"F6", ImGuiKey_F6},
|
||||
{"F7", ImGuiKey_F7},
|
||||
{"F8", ImGuiKey_F8},
|
||||
{"F9", ImGuiKey_F9},
|
||||
{"F10", ImGuiKey_F10},
|
||||
{"F11", ImGuiKey_F11},
|
||||
{"F12", ImGuiKey_F12},
|
||||
{"F1", ImGuiKey_F1},
|
||||
{"F2", ImGuiKey_F2},
|
||||
{"F3", ImGuiKey_F3},
|
||||
{"F4", ImGuiKey_F4},
|
||||
{"F5", ImGuiKey_F5},
|
||||
{"F6", ImGuiKey_F6},
|
||||
{"F7", ImGuiKey_F7},
|
||||
{"F8", ImGuiKey_F8},
|
||||
{"F9", ImGuiKey_F9},
|
||||
{"F10", ImGuiKey_F10},
|
||||
{"F11", ImGuiKey_F11},
|
||||
{"F12", ImGuiKey_F12},
|
||||
|
||||
{"Up", ImGuiKey_UpArrow},
|
||||
{"Down", ImGuiKey_DownArrow},
|
||||
{"Left", ImGuiKey_LeftArrow},
|
||||
{"Right", ImGuiKey_RightArrow},
|
||||
{"Up", ImGuiKey_UpArrow},
|
||||
{"Down", ImGuiKey_DownArrow},
|
||||
{"Left", ImGuiKey_LeftArrow},
|
||||
{"Right", ImGuiKey_RightArrow},
|
||||
|
||||
{"Space", ImGuiKey_Space},
|
||||
{"Enter", ImGuiKey_Enter},
|
||||
{"Escape", ImGuiKey_Escape},
|
||||
{"Tab", ImGuiKey_Tab},
|
||||
{"Backspace", ImGuiKey_Backspace},
|
||||
{"Delete", ImGuiKey_Delete},
|
||||
{"Insert", ImGuiKey_Insert},
|
||||
{"Home", ImGuiKey_Home},
|
||||
{"End", ImGuiKey_End},
|
||||
{"PageUp", ImGuiKey_PageUp},
|
||||
{"PageDown", ImGuiKey_PageDown},
|
||||
{"Space", ImGuiKey_Space},
|
||||
{"Enter", ImGuiKey_Enter},
|
||||
{"Escape", ImGuiKey_Escape},
|
||||
{"Tab", ImGuiKey_Tab},
|
||||
{"Backspace", ImGuiKey_Backspace},
|
||||
{"Delete", ImGuiKey_Delete},
|
||||
{"Insert", ImGuiKey_Insert},
|
||||
{"Home", ImGuiKey_Home},
|
||||
{"End", ImGuiKey_End},
|
||||
{"PageUp", ImGuiKey_PageUp},
|
||||
{"PageDown", ImGuiKey_PageDown},
|
||||
|
||||
{"Minus", ImGuiKey_Minus},
|
||||
{"Equal", ImGuiKey_Equal},
|
||||
{"LeftBracket", ImGuiKey_LeftBracket},
|
||||
{"RightBracket", ImGuiKey_RightBracket},
|
||||
{"Semicolon", ImGuiKey_Semicolon},
|
||||
{"Apostrophe", ImGuiKey_Apostrophe},
|
||||
{"Comma", ImGuiKey_Comma},
|
||||
{"Period", ImGuiKey_Period},
|
||||
{"Slash", ImGuiKey_Slash},
|
||||
{"Backslash", ImGuiKey_Backslash},
|
||||
{"GraveAccent", ImGuiKey_GraveAccent},
|
||||
|
||||
{"MouseLeft", ImGuiKey_MouseLeft},
|
||||
{"MouseRight", ImGuiKey_MouseRight},
|
||||
{"MouseMiddle", ImGuiKey_MouseMiddle},
|
||||
{"MouseX1", ImGuiKey_MouseX1},
|
||||
{"MouseX2", ImGuiKey_MouseX2}};
|
||||
{"Minus", ImGuiKey_Minus},
|
||||
{"Equal", ImGuiKey_Equal},
|
||||
{"LeftBracket", ImGuiKey_LeftBracket},
|
||||
{"RightBracket", ImGuiKey_RightBracket},
|
||||
{"Semicolon", ImGuiKey_Semicolon},
|
||||
{"Apostrophe", ImGuiKey_Apostrophe},
|
||||
{"Comma", ImGuiKey_Comma},
|
||||
{"Period", ImGuiKey_Period},
|
||||
{"Slash", ImGuiKey_Slash},
|
||||
{"Backslash", ImGuiKey_Backslash},
|
||||
{"GraveAccent", ImGuiKey_GraveAccent},
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, ImGuiKey> MOD_MAP = {
|
||||
{"Ctrl", ImGuiMod_Ctrl},
|
||||
@@ -167,15 +163,17 @@ namespace anm2ed::imgui
|
||||
int input_text_callback(ImGuiInputTextCallbackData*);
|
||||
bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
|
||||
bool input_int_range(const char*, int&, int, int, int = STEP, int = STEP_FAST, ImGuiInputTextFlags = 0);
|
||||
bool combo_strings(const std::string&, int*, std::vector<std::string>&);
|
||||
bool combo_strings(const std::string&, int*, std::vector<const char*>&);
|
||||
bool input_int2_range(const char*, glm::ivec2&, glm::ivec2, glm::ivec2, ImGuiInputTextFlags = 0);
|
||||
bool input_float_range(const char*, float&, float, float, float = STEP, float = STEP_FAST, const char* = "%.3f",
|
||||
ImGuiInputTextFlags = 0);
|
||||
bool combo_negative_one_indexed(const std::string&, int*, std::vector<const char*>&);
|
||||
bool selectable_input_text(const std::string&, const std::string&, std::string&, bool = false,
|
||||
ImGuiSelectableFlags = 0, bool* = nullptr);
|
||||
void set_item_tooltip_shortcut(const char*, const std::string& = {});
|
||||
void external_storage_set(ImGuiSelectionExternalStorage*, int, bool);
|
||||
ImVec2 icon_size_get();
|
||||
bool chord_held(ImGuiKeyChord);
|
||||
bool chord_repeating(ImGuiKeyChord, float = 0.125f, float = 0.025f);
|
||||
bool chord_repeating(ImGuiKeyChord, float = ImGui::GetIO().KeyRepeatDelay, float = ImGui::GetIO().KeyRepeatRate);
|
||||
bool shortcut(std::string, types::shortcut::Type = types::shortcut::FOCUSED_SET);
|
||||
|
||||
class MultiSelectStorage : public std::set<int>
|
||||
|
||||
@@ -23,6 +23,9 @@ namespace anm2ed::imgui
|
||||
void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting)
|
||||
{
|
||||
auto document = manager.get();
|
||||
auto reference = document ? &document->reference : nullptr;
|
||||
auto animation = document ? document->animation_get() : nullptr;
|
||||
auto item = document ? document->item_get() : nullptr;
|
||||
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
@@ -30,43 +33,31 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_save(dialog::ANM2_NEW);
|
||||
if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (manager.recentFiles.empty())
|
||||
if (ImGui::BeginMenu("Open Recent", !manager.recentFiles.empty()))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Open Recent");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::BeginMenu("Open Recent"))
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
|
||||
auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string());
|
||||
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::MenuItem(label.c_str())) manager.open(file);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (!manager.recentFiles.empty())
|
||||
if (ImGui::MenuItem("Clear List")) manager.recent_files_clear();
|
||||
|
||||
ImGui::EndMenu();
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::MenuItem(label.c_str())) manager.open(file);
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
if (!manager.recentFiles.empty())
|
||||
if (ImGui::MenuItem("Clear List")) manager.recent_files_clear();
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(!document);
|
||||
{
|
||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save();
|
||||
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str())) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (ImGui::MenuItem("Explore XML Location")) dialog.file_explorer_open(document->directory_get());
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str(), false, document)) manager.save();
|
||||
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str(), false, document))
|
||||
dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (ImGui::MenuItem("Explore XML Location", nullptr, false, document))
|
||||
dialog.file_explorer_open(document->directory_get());
|
||||
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuitting = true;
|
||||
@@ -92,16 +83,12 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginMenu("Wizard"))
|
||||
{
|
||||
auto animation = document ? document->animation_get() : nullptr;
|
||||
auto item = document ? document->item_get() : nullptr;
|
||||
ImGui::BeginDisabled(!item || document->reference.itemType != anm2::LAYER);
|
||||
if (ImGui::MenuItem("Generate Animation From Grid")) generatePopup.open();
|
||||
if (ImGui::MenuItem("Change All Frame Properties")) changePopup.open();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::Separator();
|
||||
ImGui::BeginDisabled(!animation);
|
||||
if (ImGui::MenuItem("Render Animation")) renderPopup.open();
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::MenuItem("Render Animation", nullptr, false, animation)) renderPopup.open();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -158,20 +145,19 @@ namespace anm2ed::imgui
|
||||
auto& zoom = settings.generateZoom;
|
||||
auto& zoomStep = settings.viewZoomStep;
|
||||
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(2), imgui::size_without_footer_get().y);
|
||||
auto childSize = ImVec2(row_widget_width_get(2), size_without_footer_get().y);
|
||||
|
||||
if (ImGui::BeginChild("##Options Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
ImGui::InputInt2("Start Position", value_ptr(startPosition));
|
||||
ImGui::InputInt2("Frame Size", value_ptr(size));
|
||||
ImGui::InputInt2("Pivot", value_ptr(pivot));
|
||||
ImGui::InputInt("Rows", &rows, imgui::STEP, imgui::STEP_FAST);
|
||||
ImGui::InputInt("Columns", &columns, imgui::STEP, imgui::STEP_FAST);
|
||||
ImGui::InputInt("Rows", &rows, STEP, STEP_FAST);
|
||||
ImGui::InputInt("Columns", &columns, STEP, STEP_FAST);
|
||||
|
||||
ImGui::InputInt("Count", &count, imgui::STEP, imgui::STEP_FAST);
|
||||
count = glm::min(count, rows * columns);
|
||||
input_int_range("Count", count, anm2::FRAME_NUM_MIN, rows * columns);
|
||||
|
||||
ImGui::InputInt("Delay", &delay, imgui::STEP, imgui::STEP_FAST);
|
||||
ImGui::InputInt("Delay", &delay, STEP, STEP_FAST);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -183,7 +169,7 @@ namespace anm2ed::imgui
|
||||
auto& time = generateTime;
|
||||
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
|
||||
|
||||
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, imgui::size_without_footer_get(2).y);
|
||||
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, size_without_footer_get(2).y);
|
||||
|
||||
generate.size_set(to_vec2(previewSize));
|
||||
generate.bind();
|
||||
@@ -196,7 +182,7 @@ namespace anm2ed::imgui
|
||||
.spritesheets[document->anm2.content.layers[document->reference.itemID].spritesheetID]
|
||||
.texture;
|
||||
|
||||
auto index = std::clamp((int)(time * count - 1), 0, count - 1);
|
||||
auto index = std::clamp((int)(time * (count - 1)), 0, (count - 1));
|
||||
auto row = index / columns;
|
||||
auto column = index % columns;
|
||||
auto crop = startPosition + ivec2(size.x * column, size.y * row);
|
||||
@@ -222,11 +208,18 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Generate", widgetSize))
|
||||
{
|
||||
document->generate_animation_from_grid(startPosition, size, pivot, columns, count, delay);
|
||||
auto generate_from_grid = [&]()
|
||||
{
|
||||
item->frames_generate_from_grid(startPosition, size, pivot, columns, count, delay);
|
||||
animation->frameNum = animation->length();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT_PTR(document, "Generate Animation from Grid", Document::FRAMES, generate_from_grid());
|
||||
|
||||
generatePopup.close();
|
||||
}
|
||||
|
||||
@@ -267,59 +260,36 @@ namespace anm2ed::imgui
|
||||
auto& isFromSelectedFrame = settings.changeIsFromSelectedFrame;
|
||||
auto& numberFrames = settings.changeNumberFrames;
|
||||
|
||||
auto propertiesSize = imgui::child_size_get(10);
|
||||
auto propertiesSize = child_size_get(10);
|
||||
|
||||
if (ImGui::BeginChild("##Properties", propertiesSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto start = [&](const char* checkboxLabel, bool& isEnabled)
|
||||
{
|
||||
ImGui::Checkbox(checkboxLabel, &isEnabled);
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(!isEnabled);
|
||||
};
|
||||
auto end = [&]() { ImGui::EndDisabled(); };
|
||||
#define PROPERTIES_WIDGET(body) \
|
||||
ImGui::Checkbox(checkboxLabel, &isEnabled); \
|
||||
ImGui::SameLine(); \
|
||||
ImGui::BeginDisabled(!isEnabled); \
|
||||
body; \
|
||||
ImGui::EndDisabled();
|
||||
|
||||
auto bool_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, bool& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::Checkbox(valueLabel, &value);
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::Checkbox(valueLabel, &value)); };
|
||||
|
||||
auto color3_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec3& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::ColorEdit3(valueLabel, value_ptr(value));
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::ColorEdit3(valueLabel, value_ptr(value))); };
|
||||
|
||||
auto color4_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec4& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::ColorEdit4(valueLabel, value_ptr(value));
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::ColorEdit4(valueLabel, value_ptr(value))); };
|
||||
|
||||
auto float2_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, vec2& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputFloat2(valueLabel, value_ptr(value), math::vec2_format_get(value));
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::InputFloat2(valueLabel, value_ptr(value), math::vec2_format_get(value))); };
|
||||
|
||||
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputFloat(valueLabel, &value, imgui::STEP, imgui::STEP_FAST, math::float_format_get(value));
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::InputFloat(valueLabel, &value, STEP, STEP_FAST, math::float_format_get(value))); };
|
||||
|
||||
auto int_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputInt(valueLabel, &value, imgui::STEP, imgui::STEP_FAST);
|
||||
end();
|
||||
};
|
||||
{ PROPERTIES_WIDGET(ImGui::InputInt(valueLabel, &value, STEP, STEP_FAST)); };
|
||||
|
||||
#undef PROPERTIES_WIDGET
|
||||
|
||||
float2_value("##Is Crop", "Crop", isCrop, crop);
|
||||
float2_value("##Is Size", "Size", isSize, size);
|
||||
@@ -336,67 +306,53 @@ namespace anm2ed::imgui
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto settingsSize = imgui::child_size_get(2);
|
||||
auto settingsSize = child_size_get(2);
|
||||
|
||||
if (ImGui::BeginChild("##Settings", settingsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
ImGui::Checkbox("From Selected Frame", &isFromSelectedFrame);
|
||||
ImGui::SetItemTooltip("The frames after the currently referenced frame will be changed with these values.\nIf"
|
||||
"off, will use all frames.");
|
||||
" off, will use all frames.");
|
||||
|
||||
ImGui::BeginDisabled(!isFromSelectedFrame);
|
||||
ImGui::InputInt("Number of Frames", &numberFrames, imgui::STEP, imgui::STEP_FAST);
|
||||
numberFrames = glm::clamp(numberFrames, anm2::FRAME_NUM_MIN,
|
||||
(int)document->item_get()->frames.size() - document->reference.frameIndex);
|
||||
input_int_range("Number of Frames", numberFrames, anm2::FRAME_NUM_MIN,
|
||||
item->frames.size() - reference->frameIndex);
|
||||
ImGui::SetItemTooltip("Set the number of frames that will be changed.");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(4);
|
||||
auto widgetSize = widget_size_with_row_get(4);
|
||||
|
||||
auto frame_change = [&](anm2::ChangeType type)
|
||||
{
|
||||
anm2::FrameChange frameChange;
|
||||
frameChange.crop = isCrop ? std::make_optional(crop) : std::nullopt;
|
||||
frameChange.size = isSize ? std::make_optional(size) : std::nullopt;
|
||||
frameChange.position = isPosition ? std::make_optional(position) : std::nullopt;
|
||||
frameChange.pivot = isPivot ? std::make_optional(pivot) : std::nullopt;
|
||||
frameChange.scale = isScale ? std::make_optional(scale) : std::nullopt;
|
||||
frameChange.rotation = isRotation ? std::make_optional(rotation) : std::nullopt;
|
||||
frameChange.delay = isDelay ? std::make_optional(delay) : std::nullopt;
|
||||
frameChange.tint = isTint ? std::make_optional(tint) : std::nullopt;
|
||||
frameChange.colorOffset = isColorOffset ? std::make_optional(colorOffset) : std::nullopt;
|
||||
frameChange.isVisible = isVisibleSet ? std::make_optional(isVisible) : std::nullopt;
|
||||
frameChange.isInterpolated = isInterpolatedSet ? std::make_optional(isInterpolated) : std::nullopt;
|
||||
if (isCrop) frameChange.crop = std::make_optional(crop);
|
||||
if (isSize) frameChange.size = std::make_optional(size);
|
||||
if (isPosition) frameChange.position = std::make_optional(position);
|
||||
if (isPivot) frameChange.pivot = std::make_optional(pivot);
|
||||
if (isScale) frameChange.scale = std::make_optional(scale);
|
||||
if (isRotation) frameChange.rotation = std::make_optional(rotation);
|
||||
if (isDelay) frameChange.delay = std::make_optional(delay);
|
||||
if (isTint) frameChange.tint = std::make_optional(tint);
|
||||
if (isColorOffset) frameChange.colorOffset = std::make_optional(colorOffset);
|
||||
if (isVisibleSet) frameChange.isVisible = std::make_optional(isVisible);
|
||||
if (isInterpolatedSet) frameChange.isInterpolated = std::make_optional(isInterpolated);
|
||||
|
||||
document->frames_change(frameChange, type, isFromSelectedFrame, numberFrames);
|
||||
DOCUMENT_EDIT_PTR(document, "Change Frame Properties", Document::FRAMES,
|
||||
item->frames_change(frameChange, type,
|
||||
isFromSelectedFrame && document->frame_get() ? reference->frameIndex : 0,
|
||||
isFromSelectedFrame ? numberFrames : -1));
|
||||
|
||||
changePopup.close();
|
||||
};
|
||||
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
frame_change(anm2::ADD);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
if (ImGui::Button("Add", widgetSize)) frame_change(anm2::ADD);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Subtract", widgetSize))
|
||||
{
|
||||
frame_change(anm2::SUBTRACT);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
if (ImGui::Button("Subtract", widgetSize)) frame_change(anm2::SUBTRACT);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Adjust", widgetSize))
|
||||
{
|
||||
frame_change(anm2::ADJUST);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
if (ImGui::Button("Adjust", widgetSize)) frame_change(anm2::ADJUST);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) changePopup.close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
@@ -406,7 +362,7 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginPopupModal(configurePopup.label, &configurePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get(2);
|
||||
auto childSize = size_without_footer_get(2);
|
||||
|
||||
if (ImGui::BeginTabBar("##Configure Tabs"))
|
||||
{
|
||||
@@ -420,23 +376,32 @@ namespace anm2ed::imgui
|
||||
ImGui::SetItemTooltip("Enables autosaving of documents.");
|
||||
|
||||
ImGui::BeginDisabled(!editSettings.fileIsAutosave);
|
||||
ImGui::InputInt("Autosave Time (minutes", &editSettings.fileAutosaveTime, imgui::STEP, imgui::STEP_FAST);
|
||||
editSettings.fileAutosaveTime = glm::clamp(editSettings.fileAutosaveTime, 0, 10);
|
||||
input_int_range("Autosave Time (minutes)", editSettings.fileAutosaveTime, 0, 10);
|
||||
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SeparatorText("View");
|
||||
ImGui::SeparatorText("Keyboard");
|
||||
|
||||
ImGui::InputFloat("Display Scale", &editSettings.displayScale, 0.25f, 0.25f, "%.2f");
|
||||
ImGui::SetItemTooltip("Change the scale of the display.");
|
||||
editSettings.displayScale = glm::clamp(editSettings.displayScale, 0.5f, 2.0f);
|
||||
input_float_range("Repeat Delay (seconds)", editSettings.keyboardRepeatDelay, 0.05f, 1.0f, 0.05f, 0.05f,
|
||||
"%.2f");
|
||||
ImGui::SetItemTooltip("Set how often, after repeating begins, key inputs will be fired.");
|
||||
|
||||
ImGui::InputFloat("Zoom Step", &editSettings.viewZoomStep, 10.0f, 10.0f, "%.2f");
|
||||
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
|
||||
editSettings.viewZoomStep = glm::clamp(editSettings.viewZoomStep, 1.0f, 250.0f);
|
||||
input_float_range("Repeat Rate (seconds)", editSettings.keyboardRepeatRate, 0.005f, 1.0f, 0.005f, 0.005f,
|
||||
"%.3f");
|
||||
ImGui::SetItemTooltip("Set how often, after repeating begins, key inputs will be fired.");
|
||||
|
||||
ImGui::SeparatorText("UI");
|
||||
|
||||
input_float_range("UI Scale", editSettings.uiScale, 0.5f, 2.0f, 0.25f, 0.25f, "%.2f");
|
||||
ImGui::SetItemTooltip("Change the scale of the UI.");
|
||||
|
||||
ImGui::Checkbox("Vsync", &editSettings.isVsync);
|
||||
ImGui::SetItemTooltip("Toggle vertical sync; synchronizes program update rate with monitor refresh rate.");
|
||||
|
||||
ImGui::SeparatorText("View");
|
||||
|
||||
input_float_range("Zoom Step", editSettings.viewZoomStep, 10.0f, 250.0f, 10.0f, 10.0f, "%.0f");
|
||||
ImGui::SetItemTooltip("When zooming in/out with mouse or shortcut, this value will be used.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -445,12 +410,10 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginTabItem("Shortcuts"))
|
||||
{
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
|
||||
if (ImGui::BeginChild("##Tab Child", childSize, true))
|
||||
{
|
||||
|
||||
if (ImGui::BeginTable("Shortcuts", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY))
|
||||
{
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
@@ -485,12 +448,12 @@ namespace anm2ed::imgui
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt;
|
||||
if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super;
|
||||
|
||||
for (auto& key : imgui::KEY_MAP | std::views::values)
|
||||
for (auto& key : KEY_MAP | std::views::values)
|
||||
{
|
||||
if (ImGui::IsKeyPressed(key))
|
||||
{
|
||||
chord |= key;
|
||||
*settingString = imgui::chord_to_string(chord);
|
||||
*settingString = chord_to_string(chord);
|
||||
selectedShortcut = -1;
|
||||
break;
|
||||
}
|
||||
@@ -510,7 +473,7 @@ namespace anm2ed::imgui
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(3);
|
||||
auto widgetSize = widget_size_with_row_get(3);
|
||||
|
||||
if (ImGui::Button("Save", widgetSize))
|
||||
{
|
||||
@@ -543,23 +506,42 @@ namespace anm2ed::imgui
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
auto& format = settings.renderFormat;
|
||||
auto& scale = settings.renderScale;
|
||||
auto& isRaw = settings.renderIsRawAnimation;
|
||||
auto& type = settings.renderType;
|
||||
auto& start = manager.recordingStart;
|
||||
auto& end = manager.recordingEnd;
|
||||
auto& isRange = settings.renderIsRange;
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto& isRange = manager.isRecordingRange;
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
auto dialogType = type == render::PNGS ? dialog::PNG_DIRECTORY_SET
|
||||
: type == render::GIF ? dialog::GIF_PATH_SET
|
||||
: type == render::WEBM ? dialog::WEBM_PATH_SET
|
||||
: dialog::NONE;
|
||||
|
||||
if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, imgui::icon_size_get()))
|
||||
auto replace_extension = [&]()
|
||||
{ path = std::filesystem::path(path).replace_extension(render::EXTENSIONS[type]); };
|
||||
|
||||
auto range_to_length = [&]()
|
||||
{
|
||||
start = 0;
|
||||
end = animation->frameNum;
|
||||
};
|
||||
|
||||
if (renderPopup.isJustOpened)
|
||||
{
|
||||
replace_extension();
|
||||
if (!isRange) range_to_length();
|
||||
}
|
||||
|
||||
if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
|
||||
dialog.file_open(dialog::FFMPEG_PATH_SET);
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string("FFmpeg Path", &ffmpegPath);
|
||||
input_text_string("FFmpeg Path", &ffmpegPath);
|
||||
ImGui::SetItemTooltip("Set the path where the FFmpeg installation is located.\nFFmpeg is required to render "
|
||||
"animations.\nhttps://ffmpeg.org");
|
||||
dialog.set_string_to_selected_path(ffmpegPath, dialog::FFMPEG_PATH_SET);
|
||||
|
||||
if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, imgui::icon_size_get()))
|
||||
if (ImGui::ImageButton("##Path Set", resources.icons[icon::FOLDER].id, icon_size_get()))
|
||||
{
|
||||
if (dialogType == dialog::PNG_DIRECTORY_SET)
|
||||
dialog.folder_open(dialogType);
|
||||
@@ -567,25 +549,44 @@ namespace anm2ed::imgui
|
||||
dialog.file_open(dialogType);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
||||
input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
||||
ImGui::SetItemTooltip("Set the output path or directory for the animation.");
|
||||
dialog.set_string_to_selected_path(path, dialogType);
|
||||
|
||||
ImGui::Combo("Type", &type, render::STRINGS, render::COUNT);
|
||||
if (ImGui::Combo("Type", &type, render::STRINGS, render::COUNT)) replace_extension();
|
||||
ImGui::SetItemTooltip("Set the type of the output.");
|
||||
|
||||
ImGui::BeginDisabled(type != render::PNGS);
|
||||
imgui::input_text_string("Format", &format);
|
||||
input_text_string("Format", &format);
|
||||
ImGui::SetItemTooltip(
|
||||
"For outputted images, each image will use this format.\n{} represents the index of each image.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!isRange);
|
||||
imgui::input_int_range("Start", start, 0, animation->frameNum - 1);
|
||||
ImGui::InputInt("End", &end, start, animation->frameNum);
|
||||
input_int_range("Start", start, 0, animation->frameNum - 1);
|
||||
ImGui::SetItemTooltip("Set the starting time of the animation.");
|
||||
input_int_range("End", end, start + 1, animation->frameNum);
|
||||
ImGui::SetItemTooltip("Set the ending time of the animation.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Checkbox("Custom Range", &isRange);
|
||||
ImGui::BeginDisabled(!isRaw);
|
||||
input_float_range("Scale", scale, 1.0f, 100.0f, STEP, STEP_FAST, "%.1fx");
|
||||
ImGui::SetItemTooltip("Set the output scale of the animation.");
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::Checkbox("Custom Range", &isRange))
|
||||
if (!isRange) range_to_length();
|
||||
ImGui::SetItemTooltip("Toggle using a custom range for the animation.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Checkbox("Raw", &isRaw);
|
||||
ImGui::SetItemTooltip("Record only the layers of the animation.");
|
||||
|
||||
if (ImGui::Button("Render", widgetSize))
|
||||
{
|
||||
manager.isRecording = true;
|
||||
manager.isRecordingStart = true;
|
||||
playback.time = start;
|
||||
playback.isPlaying = true;
|
||||
renderPopup.close();
|
||||
@@ -599,6 +600,8 @@ namespace anm2ed::imgui
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
renderPopup.end();
|
||||
|
||||
aboutPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(aboutPopup.label, &aboutPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
@@ -607,10 +610,10 @@ namespace anm2ed::imgui
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (imgui::shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_NEW);
|
||||
if (imgui::shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_OPEN);
|
||||
if (imgui::shortcut(settings.shortcutSave, shortcut::GLOBAL)) document->save();
|
||||
if (imgui::shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (imgui::shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
|
||||
if (shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_NEW);
|
||||
if (shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.file_open(dialog::ANM2_OPEN);
|
||||
if (shortcut(settings.shortcutSave, shortcut::GLOBAL)) document->save();
|
||||
if (shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& anm2 = document.anm2;
|
||||
auto& playback = document.playback;
|
||||
auto& zoom = document.previewZoom;
|
||||
auto& pan = document.previewPan;
|
||||
auto& isRootTransform = settings.previewIsRootTransform;
|
||||
auto& scale = settings.renderScale;
|
||||
|
||||
if (playback.isPlaying)
|
||||
{
|
||||
@@ -41,18 +45,52 @@ namespace anm2ed::imgui
|
||||
if (isSound && !anm2.content.sounds.empty())
|
||||
if (auto animation = document.animation_get(); animation)
|
||||
if (animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
|
||||
anm2.content.sounds[anm2.content.events[trigger.eventID].soundID].audio.play();
|
||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER);
|
||||
trigger.is_visible(anm2::TRIGGER))
|
||||
if (anm2.content.sounds.contains(trigger.soundID)) anm2.content.sounds[trigger.soundID].audio.play();
|
||||
|
||||
document.reference.frameTime = playback.time;
|
||||
}
|
||||
|
||||
if (manager.isRecording)
|
||||
{
|
||||
if (manager.isRecordingStart)
|
||||
{
|
||||
if (settings.renderIsRawAnimation)
|
||||
{
|
||||
savedSettings = settings;
|
||||
settings.previewBackgroundColor = vec4();
|
||||
settings.previewIsGrid = false;
|
||||
settings.previewIsAxes = false;
|
||||
settings.timelineIsOnlyShowLayers = true;
|
||||
|
||||
savedZoom = zoom;
|
||||
savedPan = pan;
|
||||
|
||||
if (auto animation = document.animation_get())
|
||||
{
|
||||
auto rect = animation->rect(isRootTransform);
|
||||
size = vec2(rect.z, rect.w) * scale;
|
||||
set_to_rect(zoom, pan, rect);
|
||||
}
|
||||
|
||||
isSizeTrySet = false;
|
||||
|
||||
bind();
|
||||
viewport_set();
|
||||
clear(settings.previewBackgroundColor);
|
||||
unbind();
|
||||
}
|
||||
|
||||
manager.isRecordingStart = false;
|
||||
|
||||
return; // Need to wait an additional frame. Kind of hacky, but oh well.
|
||||
}
|
||||
|
||||
auto pixels = pixels_get();
|
||||
renderFrames.push_back(Texture(pixels.data(), size));
|
||||
|
||||
if (playback.isFinished)
|
||||
if (playback.time > manager.recordingEnd || playback.isFinished)
|
||||
{
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
@@ -72,7 +110,7 @@ namespace anm2ed::imgui
|
||||
isSuccess = false;
|
||||
break;
|
||||
}
|
||||
logger.info(std::format("Saved frame to PNG: {}", outputPath.string()));
|
||||
logger.info(std::format("Saved frame to: {}", outputPath.string()));
|
||||
}
|
||||
|
||||
if (isSuccess)
|
||||
@@ -89,6 +127,12 @@ namespace anm2ed::imgui
|
||||
}
|
||||
|
||||
renderFrames.clear();
|
||||
|
||||
pan = savedPan;
|
||||
zoom = savedZoom;
|
||||
settings = savedSettings;
|
||||
isSizeTrySet = true;
|
||||
|
||||
playback.isPlaying = false;
|
||||
playback.isFinished = false;
|
||||
manager.isRecording = false;
|
||||
@@ -127,22 +171,26 @@ namespace anm2ed::imgui
|
||||
auto& shaderGrid = resources.shaders[shader::GRID];
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
|
||||
settings.previewPan = pan;
|
||||
settings.previewZoom = zoom;
|
||||
auto center_view = [&]() { pan = vec2(); };
|
||||
|
||||
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
|
||||
{
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(4),
|
||||
auto childSize = ImVec2(row_widget_width_get(4),
|
||||
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
|
||||
|
||||
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
|
||||
ImGui::Checkbox("Grid", &isGrid);
|
||||
ImGui::SetItemTooltip("Toggle the visibility of the grid.");
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::InputInt2("Size", value_ptr(gridSize));
|
||||
ImGui::InputInt2("Offset", value_ptr(gridOffset));
|
||||
ImGui::SetItemTooltip("Change the grid's color.");
|
||||
|
||||
input_int2_range("Size", gridSize, ivec2(GRID_SIZE_MIN), ivec2(GRID_SIZE_MAX));
|
||||
ImGui::SetItemTooltip("Change the size of all cells in the grid.");
|
||||
|
||||
input_int2_range("Offset", gridOffset, ivec2(GRID_OFFSET_MIN), ivec2(GRID_OFFSET_MAX));
|
||||
ImGui::SetItemTooltip("Change the offset of the grid.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -151,19 +199,20 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
ImGui::SetItemTooltip("Change the zoom of the preview.");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutCenterView);
|
||||
shortcut(settings.shortcutCenterView);
|
||||
if (ImGui::Button("Center View", widgetSize)) pan = vec2();
|
||||
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
|
||||
set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutFit);
|
||||
shortcut(settings.shortcutFit);
|
||||
if (ImGui::Button("Fit", widgetSize))
|
||||
if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform));
|
||||
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
|
||||
set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
|
||||
|
||||
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
|
||||
}
|
||||
@@ -174,14 +223,19 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SetItemTooltip("Change the background color.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Axes", &isAxes);
|
||||
ImGui::SetItemTooltip("Toggle the axes' visbility.");
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(axesColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SetItemTooltip("Set the color of the axes.");
|
||||
|
||||
imgui::combo_strings("Overlay", &overlayIndex, document.animationNamesCStr);
|
||||
combo_negative_one_indexed("Overlay", &overlayIndex, document.animation.labels);
|
||||
ImGui::SetItemTooltip("Set an animation to be drawn over the current animation.");
|
||||
|
||||
ImGui::InputFloat("Alpha", &overlayTransparency, 0, 0, "%.0f");
|
||||
ImGui::SetItemTooltip("Set the alpha of the overlayed animation.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -189,12 +243,14 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Helpers Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
auto helpersChildSize = ImVec2(imgui::row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
|
||||
auto helpersChildSize = ImVec2(row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
|
||||
|
||||
if (ImGui::BeginChild("##Helpers Child 1", helpersChildSize))
|
||||
{
|
||||
ImGui::Checkbox("Root Transform", &isRootTransform);
|
||||
ImGui::SetItemTooltip("Root frames will transform the rest of the animation.");
|
||||
ImGui::Checkbox("Pivots", &isPivots);
|
||||
ImGui::SetItemTooltip("Toggle the visibility of the animation's pivots.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -203,7 +259,9 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##Helpers Child 2", helpersChildSize))
|
||||
{
|
||||
ImGui::Checkbox("Alt Icons", &isAltIcons);
|
||||
ImGui::SetItemTooltip("Toggle a different appearance of the target icons.");
|
||||
ImGui::Checkbox("Border", &isBorder);
|
||||
ImGui::SetItemTooltip("Toggle the visibility of borders around layers.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
@@ -211,7 +269,7 @@ namespace anm2ed::imgui
|
||||
|
||||
auto cursorScreenPos = ImGui::GetCursorScreenPos();
|
||||
|
||||
size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
||||
if (isSizeTrySet) size_set(to_vec2(ImGui::GetContentRegionAvail()));
|
||||
bind();
|
||||
viewport_set();
|
||||
clear(backgroundColor);
|
||||
@@ -234,23 +292,23 @@ namespace anm2ed::imgui
|
||||
|
||||
vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
|
||||
|
||||
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color);
|
||||
auto icon = isAltIcons ? icon::TARGET_ALT : icon::TARGET;
|
||||
texture_render(shaderTexture, resources.icons[icon].id, rootTransform, color);
|
||||
}
|
||||
|
||||
for (auto& id : animation->layerOrder)
|
||||
{
|
||||
auto& layerAnimation = animation->layerAnimations.at(id);
|
||||
auto& layerAnimation = animation->layerAnimations[id];
|
||||
if (!layerAnimation.isVisible) continue;
|
||||
|
||||
auto& layer = anm2.content.layers.at(id);
|
||||
|
||||
if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.isVisible)
|
||||
if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.is_visible())
|
||||
{
|
||||
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
|
||||
if (!spritesheet) continue;
|
||||
if (!spritesheet || !spritesheet->is_valid()) continue;
|
||||
|
||||
auto& texture = spritesheet->texture;
|
||||
if (!texture.is_valid()) continue;
|
||||
|
||||
auto layerModel = math::quad_model_get(frame.size, frame.position, frame.pivot,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
@@ -288,7 +346,8 @@ namespace anm2ed::imgui
|
||||
|
||||
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
|
||||
{
|
||||
auto icon = isShowRect ? icon::POINT : icon::TARGET;
|
||||
auto icon = isShowRect ? icon::POINT : isAltIcons ? icon::TARGET_ALT : icon::TARGET;
|
||||
|
||||
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
|
||||
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
|
||||
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
|
||||
@@ -341,9 +400,8 @@ namespace anm2ed::imgui
|
||||
|
||||
render(animation, frameTime);
|
||||
|
||||
if (overlayIndex > 0)
|
||||
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
|
||||
1.0f - math::uint8_to_float(overlayTransparency));
|
||||
if (auto overlayAnimation = anm2.animation_get({overlayIndex}))
|
||||
render(overlayAnimation, frameTime, {}, 1.0f - math::uint8_to_float(overlayTransparency));
|
||||
|
||||
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
|
||||
}
|
||||
@@ -356,7 +414,8 @@ namespace anm2ed::imgui
|
||||
|
||||
if (animation && animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
{
|
||||
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER); trigger.isVisible)
|
||||
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER);
|
||||
trigger.isVisible && trigger.eventID > -1)
|
||||
{
|
||||
auto clipMin = ImGui::GetItemRectMin();
|
||||
auto clipMax = ImGui::GetItemRectMax();
|
||||
@@ -374,45 +433,57 @@ namespace anm2ed::imgui
|
||||
|
||||
if (isPreviewHovered)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
||||
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
||||
|
||||
auto isLeftJustPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightJustPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpJustPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownJustPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow);
|
||||
auto isLeftDown = ImGui::IsKeyDown(ImGuiKey_LeftArrow);
|
||||
auto isRightDown = ImGui::IsKeyDown(ImGuiKey_RightArrow);
|
||||
auto isUpDown = ImGui::IsKeyDown(ImGuiKey_UpArrow);
|
||||
auto isDownDown = ImGui::IsKeyDown(ImGuiKey_DownArrow);
|
||||
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
|
||||
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
|
||||
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
|
||||
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
|
||||
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
|
||||
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
|
||||
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
|
||||
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
||||
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
||||
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
|
||||
auto isKeyJustPressed = isLeftJustPressed || isRightJustPressed || isUpJustPressed || isDownJustPressed;
|
||||
auto isKeyDown = isLeftDown || isRightDown || isUpDown || isDownDown;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
|
||||
auto isZoomIn = chord_repeating(string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = chord_repeating(string_to_chord(settings.shortcutZoomOut));
|
||||
|
||||
auto isBegin = isMouseClicked || isKeyJustPressed;
|
||||
auto isDuring = isMouseDown || isKeyDown;
|
||||
auto isEnd = isMouseReleased || isKeyReleased;
|
||||
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
|
||||
auto frame = document.frame_get();
|
||||
auto useTool = tool;
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
auto isBegin = isMouseClick || isKeyPressed;
|
||||
auto isEnd = isMouseReleased || isKeyReleased;
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
|
||||
if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE;
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
auto& areaType = tool::INFO[useTool].areaType;
|
||||
auto cursor = areaType == tool::ANIMATION_PREVIEW || areaType == tool::ALL ? tool::INFO[useTool].cursor
|
||||
: ImGuiMouseCursor_NotAllowed;
|
||||
ImGui::SetMouseCursor(cursor);
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
@@ -423,28 +494,62 @@ namespace anm2ed::imgui
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Position");
|
||||
if (isMouseDown) frame->position = mousePos;
|
||||
if (isLeft) frame->position.x -= step;
|
||||
if (isRight) frame->position.x += step;
|
||||
if (isUp) frame->position.y -= step;
|
||||
if (isDown) frame->position.y += step;
|
||||
if (isLeftPressed) frame->position.x -= step;
|
||||
if (isRightPressed) frame->position.x += step;
|
||||
if (isUpPressed) frame->position.y -= step;
|
||||
if (isDownPressed) frame->position.y += step;
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
if (isDuring)
|
||||
{
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
auto positionFormat = math::vec2_format_get(frame->position);
|
||||
auto positionString = std::format("Position: ({}, {})", positionFormat, positionFormat);
|
||||
ImGui::Text(positionString.c_str(), frame->position.x, frame->position.y);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case tool::SCALE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Scale");
|
||||
if (isMouseDown) frame->scale += mouseDelta;
|
||||
if (isLeft) frame->scale.x -= step;
|
||||
if (isRight) frame->scale.x += step;
|
||||
if (isUp) frame->scale.y -= step;
|
||||
if (isDown) frame->scale.y += step;
|
||||
if (isLeftPressed) frame->scale.x -= step;
|
||||
if (isRightPressed) frame->scale.x += step;
|
||||
if (isUpPressed) frame->scale.y -= step;
|
||||
if (isDownPressed) frame->scale.y += step;
|
||||
|
||||
if (isDuring)
|
||||
{
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
auto scaleFormat = math::vec2_format_get(frame->scale);
|
||||
auto scaleString = std::format("Scale: ({}, {})", scaleFormat, scaleFormat);
|
||||
ImGui::Text(scaleString.c_str(), frame->scale.x, frame->scale.y);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::ROTATE:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Rotation");
|
||||
if (isMouseDown) frame->rotation += mouseDelta.y;
|
||||
if (isLeft || isDown) frame->rotation -= step;
|
||||
if (isUp || isRight) frame->rotation += step;
|
||||
if (isLeftPressed || isDownPressed) frame->rotation -= step;
|
||||
if (isUpPressed || isRightPressed) frame->rotation += step;
|
||||
|
||||
if (isDuring)
|
||||
{
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
auto rotationFormat = math::float_format_get(frame->rotation);
|
||||
auto rotationString = std::format("Rotation: {}", rotationFormat);
|
||||
ImGui::Text(rotationString.c_str(), frame->rotation);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
default:
|
||||
@@ -452,7 +557,8 @@ namespace anm2ed::imgui
|
||||
}
|
||||
|
||||
if (mouseWheel != 0 || isZoomIn || isZoomOut)
|
||||
zoom_set(zoom, pan, vec2(mousePos), (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
zoom_set(zoom, pan, mouseWheel != 0 ? vec2(mousePos) : vec2(),
|
||||
(mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
@@ -478,5 +584,14 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (!document.isAnimationPreviewSet)
|
||||
{
|
||||
center_view();
|
||||
zoom = settings.previewStartZoom;
|
||||
document.isAnimationPreviewSet = true;
|
||||
}
|
||||
|
||||
settings.previewStartZoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace anm2ed::imgui
|
||||
class AnimationPreview : public Canvas
|
||||
{
|
||||
bool isPreviewHovered{};
|
||||
bool isSizeTrySet{true};
|
||||
Settings savedSettings{};
|
||||
float savedZoom{};
|
||||
glm::vec2 savedPan{};
|
||||
glm::ivec2 mousePos{};
|
||||
std::vector<resource::Texture> renderFrames{};
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "toast.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
@@ -12,10 +16,11 @@ namespace anm2ed::imgui
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& hovered = document.hoveredAnimation;
|
||||
auto& multiSelect = document.animationMultiSelect;
|
||||
auto& mergeMultiSelect = document.animationMergeMultiSelect;
|
||||
auto& mergeTarget = document.mergeTarget;
|
||||
auto& hovered = document.animation.hovered;
|
||||
auto& selection = document.animation.selection;
|
||||
auto& mergeSelection = document.merge.selection;
|
||||
auto& mergeReference = document.merge.reference;
|
||||
auto& overlayIndex = document.overlayIndex;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
@@ -25,7 +30,7 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
multiSelect.start(anm2.animations.items.size());
|
||||
selection.start(anm2.animations.items.size());
|
||||
|
||||
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
|
||||
{
|
||||
@@ -40,11 +45,11 @@ namespace anm2ed::imgui
|
||||
: font::REGULAR;
|
||||
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
ImGui::SetNextItemSelectionUserData(i);
|
||||
ImGui::SetNextItemSelectionUserData((int)i);
|
||||
if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i),
|
||||
animation.name, multiSelect.contains(i)))
|
||||
document.animation_set(i);
|
||||
if (ImGui::IsItemHovered()) hovered = i;
|
||||
animation.name, selection.contains((int)i)))
|
||||
reference = {(int)i};
|
||||
if (ImGui::IsItemHovered()) hovered = (int)i;
|
||||
ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
@@ -69,11 +74,12 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginDragDropSource())
|
||||
{
|
||||
static std::vector<int> selection;
|
||||
selection.assign(multiSelect.begin(), multiSelect.end());
|
||||
ImGui::SetDragDropPayload("Animation Drag Drop", selection.data(), selection.size() * sizeof(int));
|
||||
for (auto& i : selection)
|
||||
ImGui::TextUnformatted(anm2.animations.items[i].name.c_str());
|
||||
static std::vector<int> dragDropSelection{};
|
||||
dragDropSelection.assign(selection.begin(), selection.end());
|
||||
ImGui::SetDragDropPayload("Animation Drag Drop", dragDropSelection.data(),
|
||||
dragDropSelection.size() * sizeof(int));
|
||||
for (auto& i : dragDropSelection)
|
||||
ImGui::Text("%s", anm2.animations.items[(int)i].name.c_str());
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
|
||||
@@ -85,7 +91,8 @@ namespace anm2ed::imgui
|
||||
auto payloadCount = payload->DataSize / sizeof(int);
|
||||
std::vector<int> indices(payloadIndices, payloadIndices + payloadCount);
|
||||
std::sort(indices.begin(), indices.end());
|
||||
document.animations_move(indices, i);
|
||||
DOCUMENT_EDIT(document, "Move Animation(s)", Document::ANIMATIONS,
|
||||
selection = vector::move_indices(anm2.animations.items, indices, i));
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
@@ -93,14 +100,14 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& i : multiSelect)
|
||||
for (auto& i : selection)
|
||||
clipboardText += anm2.animations.items[i].to_string();
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -111,13 +118,41 @@ namespace anm2ed::imgui
|
||||
auto cut = [&]()
|
||||
{
|
||||
copy();
|
||||
document.animations_remove();
|
||||
|
||||
auto remove = [&]()
|
||||
{
|
||||
if (!selection.empty())
|
||||
{
|
||||
for (auto& i : selection | std::views::reverse)
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + i);
|
||||
selection.clear();
|
||||
}
|
||||
else if (hovered > -1)
|
||||
{
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + hovered);
|
||||
hovered = -1;
|
||||
}
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Cut Animation(s)", Document::ANIMATIONS, remove());
|
||||
};
|
||||
|
||||
auto paste = [&]()
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.animations_deserialize(clipboardText);
|
||||
|
||||
auto deserialize = [&]()
|
||||
{
|
||||
auto start = selection.empty() ? anm2.animations.items.size() : *selection.rbegin() + 1;
|
||||
std::set<int> indices{};
|
||||
std::string errorString{};
|
||||
if (anm2.animations_deserialize(clipboardText, start, indices, &errorString))
|
||||
selection = indices;
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize animation(s): {}", errorString));
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Paste Animation(s)", Document::ANIMATIONS, deserialize());
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
|
||||
@@ -126,15 +161,9 @@ namespace anm2ed::imgui
|
||||
|
||||
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();
|
||||
|
||||
if (ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, !selection.empty() || hovered > -1)) cut();
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, !selection.empty() || hovered > -1)) copy();
|
||||
if (ImGui::MenuItem("Paste", settings.shortcutPaste.c_str(), false, !clipboard.is_empty())) paste();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
@@ -143,33 +172,98 @@ namespace anm2ed::imgui
|
||||
auto widgetSize = widget_size_with_row_get(5);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) document.animation_add();
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
auto 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 = 0;
|
||||
if (!anm2.animations.items.empty())
|
||||
index = selection.empty() ? (int)anm2.animations.items.size() - 1 : *selection.rbegin() + 1;
|
||||
|
||||
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
|
||||
selection = {index};
|
||||
reference = {index};
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Add Animation", Document::ANIMATIONS, add());
|
||||
}
|
||||
set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
ImGui::BeginDisabled(selection.empty());
|
||||
{
|
||||
shortcut(settings.shortcutDuplicate);
|
||||
if (ImGui::Button("Duplicate", widgetSize)) document.animation_duplicate();
|
||||
if (ImGui::Button("Duplicate", widgetSize))
|
||||
{
|
||||
auto duplicate = [&]()
|
||||
{
|
||||
auto duplicated = selection;
|
||||
auto end = std::ranges::max(duplicated);
|
||||
for (auto& id : duplicated)
|
||||
{
|
||||
anm2.animations.items.insert(anm2.animations.items.begin() + end, anm2.animations.items[id]);
|
||||
selection.insert(++end);
|
||||
selection.erase(id);
|
||||
}
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Duplicate Animation(s)", Document::ANIMATIONS, duplicate());
|
||||
}
|
||||
set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (shortcut(settings.shortcutMerge, shortcut::FOCUSED))
|
||||
if (multiSelect.size() > 0) document.animations_merge_quick();
|
||||
if (shortcut(settings.shortcutMerge, shortcut::FOCUSED) && !selection.empty())
|
||||
{
|
||||
auto merge_quick = [&]()
|
||||
{
|
||||
int merged{};
|
||||
if (selection.contains(overlayIndex)) overlayIndex = -1;
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
if (selection.size() > 1)
|
||||
merged = anm2.animations_merge(*selection.begin(), selection);
|
||||
else if (selection.size() == 1 && *selection.begin() != (int)anm2.animations.items.size() - 1)
|
||||
{
|
||||
auto start = *selection.begin();
|
||||
auto next = *selection.begin() + 1;
|
||||
std::set<int> animationSet{};
|
||||
animationSet.insert(start);
|
||||
animationSet.insert(next);
|
||||
|
||||
merged = anm2.animations_merge(start, animationSet);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
selection = {merged};
|
||||
reference = {merged};
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Merge Animations", Document::ANIMATIONS, merge_quick())
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(selection.size() != 1);
|
||||
{
|
||||
if (ImGui::Button("Merge", widgetSize))
|
||||
{
|
||||
mergePopup.open();
|
||||
mergeMultiSelect.clear();
|
||||
mergeTarget = *multiSelect.begin();
|
||||
mergeSelection.clear();
|
||||
mergeReference = *selection.begin();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
set_item_tooltip_shortcut("Open the merge popup.\nUsing the shortcut will merge the animations with\nthe last "
|
||||
"configured merge settings.",
|
||||
settings.shortcutMerge);
|
||||
@@ -177,14 +271,31 @@ namespace anm2ed::imgui
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove", widgetSize)) document.animations_remove();
|
||||
if (ImGui::Button("Remove", widgetSize))
|
||||
{
|
||||
auto remove = [&]()
|
||||
{
|
||||
for (auto& i : selection | std::views::reverse)
|
||||
{
|
||||
if (i == overlayIndex) overlayIndex = -1;
|
||||
anm2.animations.items.erase(anm2.animations.items.begin() + i);
|
||||
}
|
||||
selection.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Animation(s)", Document::ANIMATIONS, remove());
|
||||
}
|
||||
set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutDefault);
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
if (ImGui::Button("Default", widgetSize)) document.animation_default();
|
||||
ImGui::BeginDisabled(selection.size() != 1);
|
||||
if (ImGui::Button("Default", widgetSize))
|
||||
{
|
||||
DOCUMENT_EDIT(document, "Default Animation", Document::ANIMATIONS,
|
||||
anm2.animations.defaultAnimation = anm2.animations.items[*selection.begin()].name);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
|
||||
}
|
||||
@@ -196,7 +307,7 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto merge_close = [&]()
|
||||
{
|
||||
mergeMultiSelect.clear();
|
||||
mergeSelection.clear();
|
||||
mergePopup.close();
|
||||
};
|
||||
|
||||
@@ -212,19 +323,21 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("Animations", animationsSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
mergeMultiSelect.start(anm2.animations.items.size());
|
||||
mergeSelection.start(anm2.animations.items.size());
|
||||
|
||||
for (auto [i, animation] : std::views::enumerate(anm2.animations.items))
|
||||
{
|
||||
if (i == mergeReference) continue;
|
||||
|
||||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetNextItemSelectionUserData(i);
|
||||
ImGui::Selectable(animation.name.c_str(), mergeMultiSelect.contains(i));
|
||||
ImGui::Selectable(animation.name.c_str(), mergeSelection.contains(i));
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
mergeMultiSelect.finish();
|
||||
mergeSelection.finish();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -258,7 +371,17 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::Button("Merge", widgetSize))
|
||||
{
|
||||
document.animations_merge((merge::Type)type, isDeleteAnimationsAfter);
|
||||
auto merge = [&]()
|
||||
{
|
||||
if (mergeSelection.contains(overlayIndex)) overlayIndex = -1;
|
||||
auto merged =
|
||||
anm2.animations_merge(mergeReference, mergeSelection, (merge::Type)type, isDeleteAnimationsAfter);
|
||||
|
||||
selection = {merged};
|
||||
reference = {merged};
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Merge Animations", Document::ANIMATIONS, merge());
|
||||
merge_close();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
@@ -11,10 +15,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& unused = document.unusedEventIDs;
|
||||
auto& hovered = document.hoveredEvent;
|
||||
auto& reference = document.referenceEvent;
|
||||
auto& multiSelect = document.eventMultiSelect;
|
||||
auto& unused = document.event.unused;
|
||||
auto& hovered = document.event.hovered;
|
||||
auto& reference = document.event.reference;
|
||||
auto& selection = document.event.selection;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
@@ -24,23 +28,15 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Events Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.events.size());
|
||||
selection.start(anm2.content.events.size());
|
||||
|
||||
for (auto& [id, event] : anm2.content.events)
|
||||
{
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
ImGui::Selectable(event.name.c_str(), multiSelect.contains(id));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
reference = id;
|
||||
editEvent = document.anm2.content.events[reference];
|
||||
propertiesPopup.open();
|
||||
}
|
||||
}
|
||||
if (selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id),
|
||||
event.name, selection.contains(id)))
|
||||
if (ImGui::IsItemHovered()) hovered = id;
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
@@ -52,14 +48,14 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
clipboardText += anm2.content.events[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -69,8 +65,12 @@ namespace anm2ed::imgui
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.events_deserialize(clipboardText, type);
|
||||
std::string errorString{};
|
||||
document.snapshot("Paste Event(s)");
|
||||
if (anm2.events_deserialize(clipboard.get(), type, &errorString))
|
||||
document.change(Document::EVENTS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize event(s): {}", errorString));
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
@@ -78,25 +78,16 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, false);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, !selection.empty() || hovered > -1)) copy();
|
||||
|
||||
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", !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);
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -108,53 +99,36 @@ namespace anm2ed::imgui
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
reference = -1;
|
||||
editEvent = anm2::Event();
|
||||
propertiesPopup.open();
|
||||
auto add = [&]()
|
||||
{
|
||||
auto id = map::next_id_get(anm2.content.events);
|
||||
anm2.content.events[id] = anm2::Event();
|
||||
selection = {id};
|
||||
reference = {id};
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Add Event", Document::EVENTS, add());
|
||||
}
|
||||
set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
{
|
||||
auto remove_unused = [&]()
|
||||
{
|
||||
for (auto& id : unused)
|
||||
anm2.content.events.erase(id);
|
||||
unused.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Unused Events", Document::EVENTS, remove_unused());
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
propertiesPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = child_size_get(2);
|
||||
auto& event = editEvent;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &event.name);
|
||||
ImGui::SetItemTooltip("Set the event's name.");
|
||||
combo_strings("Sound", &event.soundID, document.soundNames);
|
||||
ImGui::SetItemTooltip("Set the event sound; it will play when a trigger associated with this event activates.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.event_set(event);
|
||||
propertiesPopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) propertiesPopup.close();
|
||||
|
||||
propertiesPopup.end();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ namespace anm2ed::imgui
|
||||
{
|
||||
class Events
|
||||
{
|
||||
anm2::Event editEvent{};
|
||||
PopupHelper propertiesPopup{PopupHelper("Event Properties", POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "frame_properties.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "math_.h"
|
||||
@@ -18,9 +16,7 @@ namespace anm2ed::imgui
|
||||
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& type = reference.itemType;
|
||||
auto& type = document.reference.itemType;
|
||||
auto frame = document.frame_get();
|
||||
auto useFrame = frame ? *frame : anm2::Frame();
|
||||
|
||||
@@ -28,13 +24,16 @@ namespace anm2ed::imgui
|
||||
{
|
||||
if (type == anm2::TRIGGER)
|
||||
{
|
||||
std::vector<std::string> eventNames{};
|
||||
for (auto& event : anm2.content.events | std::views::values)
|
||||
eventNames.emplace_back(event.name);
|
||||
|
||||
if (imgui::combo_strings("Event", frame ? &useFrame.eventID : &dummy_value<int>(), eventNames))
|
||||
if (combo_negative_one_indexed("Event", frame ? &useFrame.eventID : &dummy_value<int>(),
|
||||
document.event.labels))
|
||||
DOCUMENT_EDIT(document, "Trigger Event", Document::FRAMES, frame->eventID = useFrame.eventID);
|
||||
ImGui::SetItemTooltip("Change the event this trigger uses.");
|
||||
|
||||
if (combo_negative_one_indexed("Sound", frame ? &useFrame.soundID : &dummy_value<int>(),
|
||||
document.sound.labels))
|
||||
DOCUMENT_EDIT(document, "Trigger Sound", Document::FRAMES, frame->soundID = useFrame.soundID);
|
||||
ImGui::SetItemTooltip("Change the sound this trigger uses.");
|
||||
|
||||
if (ImGui::InputInt("At Frame", frame ? &useFrame.atFrame : &dummy_value<int>(), imgui::STEP,
|
||||
imgui::STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
DOCUMENT_EDIT(document, "Trigger At Frame", Document::FRAMES, frame->atFrame = useFrame.atFrame);
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
@@ -11,10 +15,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceLayer;
|
||||
auto& unused = document.unusedLayerIDs;
|
||||
auto& hovered = document.hoveredLayer;
|
||||
auto& multiSelect = document.layersMultiSelect;
|
||||
auto& reference = document.layer.reference;
|
||||
auto& unused = document.layer.unused;
|
||||
auto& hovered = document.layer.hovered;
|
||||
auto& selection = document.layer.selection;
|
||||
auto& propertiesPopup = manager.layerPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
@@ -25,11 +29,11 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Layers Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.layers.size());
|
||||
selection.start(anm2.content.layers.size());
|
||||
|
||||
for (auto& [id, layer] : anm2.content.layers)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isSelected = selection.contains(id);
|
||||
|
||||
ImGui::PushID(id);
|
||||
|
||||
@@ -55,14 +59,14 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
clipboardText += anm2.content.layers[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -72,8 +76,12 @@ namespace anm2ed::imgui
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.layers_deserialize(clipboardText, type);
|
||||
std::string errorString{};
|
||||
document.snapshot("Paste Layer(s)");
|
||||
if (anm2.layers_deserialize(clipboard.get(), type, &errorString))
|
||||
document.change(Document::NULLS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize layer(s): {}", errorString));
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
@@ -81,25 +89,16 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, false);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, !selection.empty() || hovered > -1)) copy();
|
||||
|
||||
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", !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);
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -115,7 +114,17 @@ namespace anm2ed::imgui
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.layers_remove_unused();
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
{
|
||||
auto remove_unused = [&]()
|
||||
{
|
||||
for (auto& id : unused)
|
||||
anm2.content.layers.erase(id);
|
||||
unused.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Unused Layers", Document::LAYERS, remove_unused());
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
@@ -134,7 +143,7 @@ namespace anm2ed::imgui
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &layer.name);
|
||||
ImGui::SetItemTooltip("Set the item's name.");
|
||||
combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
|
||||
combo_negative_one_indexed("Spritesheet", &layer.spritesheetID, document.spritesheet.labels);
|
||||
ImGui::SetItemTooltip("Set the layer item's spritesheet.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
@@ -143,7 +152,26 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.layer_set(layer);
|
||||
auto add = [&]()
|
||||
{
|
||||
auto id = map::next_id_get(anm2.content.layers);
|
||||
anm2.content.layers[id] = layer;
|
||||
selection = {id};
|
||||
};
|
||||
|
||||
auto set = [&]()
|
||||
{
|
||||
anm2.content.layers[reference] = layer;
|
||||
selection = {reference};
|
||||
};
|
||||
|
||||
if (reference == -1)
|
||||
{
|
||||
DOCUMENT_EDIT(document, "Add Layer", Document::LAYERS, add());
|
||||
}
|
||||
else
|
||||
DOCUMENT_EDIT(document, "Set Layer Properties", Document::LAYERS, set());
|
||||
|
||||
manager.layer_properties_close();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "map_.h"
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
@@ -11,10 +15,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceNull;
|
||||
auto& unused = document.unusedNullIDs;
|
||||
auto& hovered = document.hoveredNull;
|
||||
auto& multiSelect = document.nullMultiSelect;
|
||||
auto& reference = document.null.reference;
|
||||
auto& unused = document.null.unused;
|
||||
auto& hovered = document.null.hovered;
|
||||
auto& selection = document.null.selection;
|
||||
auto& propertiesPopup = manager.nullPropertiesPopup;
|
||||
|
||||
hovered = -1;
|
||||
@@ -25,11 +29,11 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Nulls Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.nulls.size());
|
||||
selection.start(anm2.content.nulls.size());
|
||||
|
||||
for (auto& [id, null] : anm2.content.nulls)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isSelected = selection.contains(id);
|
||||
auto isReferenced = reference == id;
|
||||
|
||||
ImGui::PushID(id);
|
||||
@@ -55,14 +59,14 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
clipboardText += anm2.content.nulls[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -72,8 +76,12 @@ namespace anm2ed::imgui
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.nulls_deserialize(clipboardText, type);
|
||||
std::string errorString{};
|
||||
document.snapshot("Paste Null(s)");
|
||||
if (anm2.nulls_deserialize(clipboard.get(), type, &errorString))
|
||||
document.change(Document::NULLS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize null(s): {}", errorString));
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
@@ -81,25 +89,16 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, false);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, selection.empty() || hovered > -1)) copy();
|
||||
|
||||
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", !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);
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -115,7 +114,17 @@ namespace anm2ed::imgui
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.nulls_remove_unused();
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
{
|
||||
auto remove_unused = [&]()
|
||||
{
|
||||
for (auto& id : unused)
|
||||
anm2.content.nulls.erase(id);
|
||||
unused.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Unused Events", Document::EVENTS, remove_unused());
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)", settings.shortcutRemove);
|
||||
}
|
||||
@@ -143,7 +152,26 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.null_set(null);
|
||||
auto add = [&]()
|
||||
{
|
||||
auto id = map::next_id_get(anm2.content.nulls);
|
||||
anm2.content.nulls[id] = null;
|
||||
selection = {id};
|
||||
};
|
||||
|
||||
auto set = [&]()
|
||||
{
|
||||
anm2.content.nulls[reference] = null;
|
||||
selection = {reference};
|
||||
};
|
||||
|
||||
if (reference == -1)
|
||||
{
|
||||
DOCUMENT_EDIT(document, "Add Null", Document::NULLS, add());
|
||||
}
|
||||
else
|
||||
DOCUMENT_EDIT(document, "Set Null Properties", Document::NULLS, set());
|
||||
|
||||
manager.null_properties_close();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
@@ -12,10 +14,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.referenceNull;
|
||||
auto& unused = document.unusedNullIDs;
|
||||
auto& hovered = document.hoveredNull;
|
||||
auto& multiSelect = document.soundMultiSelect;
|
||||
auto& reference = document.sound.reference;
|
||||
auto& unused = document.sound.unused;
|
||||
auto& hovered = document.null.hovered;
|
||||
auto& selection = document.sound.selection;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
@@ -25,24 +27,18 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Sounds Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.sounds.size());
|
||||
selection.start(anm2.content.sounds.size());
|
||||
|
||||
for (auto& [id, sound] : anm2.content.sounds)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isSelected = selection.contains(id);
|
||||
auto isReferenced = reference == id;
|
||||
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
if (ImGui::Selectable(std::format(anm2::SOUND_FORMAT, id, sound.path.string()).c_str(), isSelected))
|
||||
sound.audio.play();
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
;
|
||||
}
|
||||
if (ImGui::Selectable(sound.path.c_str(), isSelected)) sound.play();
|
||||
if (ImGui::IsItemHovered()) hovered = id;
|
||||
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
@@ -58,14 +54,14 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
clipboardText += anm2.content.sounds[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -75,8 +71,12 @@ namespace anm2ed::imgui
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.sounds_deserialize(clipboardText, type);
|
||||
std::string errorString{};
|
||||
document.snapshot("Paste Sound(s)");
|
||||
if (anm2.sounds_deserialize(clipboard.get(), document.directory_get(), type, &errorString))
|
||||
document.change(Document::SOUNDS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize sound(s): {}", errorString));
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
@@ -84,25 +84,16 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, false);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), !selection.empty() && hovered > -1)) copy();
|
||||
|
||||
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", !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);
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -119,7 +110,16 @@ namespace anm2ed::imgui
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
;
|
||||
{
|
||||
auto remove_unused = [&]()
|
||||
{
|
||||
for (auto& id : unused)
|
||||
anm2.content.sounds.erase(id);
|
||||
unused.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Unused Sounds", Document::SOUNDS, remove_unused());
|
||||
};
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Remove unused sounds (i.e., ones not used in any trigger.)",
|
||||
settings.shortcutRemove);
|
||||
@@ -128,7 +128,20 @@ namespace anm2ed::imgui
|
||||
|
||||
if (dialog.is_selected(dialog::SOUND_OPEN))
|
||||
{
|
||||
document.sound_add(dialog.path);
|
||||
auto add = [&]()
|
||||
{
|
||||
int id{};
|
||||
if (anm2.sound_add(document.directory_get(), dialog.path, id))
|
||||
{
|
||||
selection = {id};
|
||||
toasts.info(std::format("Initialized sound #{}: {}", id, dialog.path));
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to initialize sound: {}", dialog.path));
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Add Sound", Document::SOUNDS, add());
|
||||
|
||||
dialog.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "spritesheet_editor.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "math_.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
@@ -20,17 +23,20 @@ namespace anm2ed::imgui
|
||||
|
||||
void SpritesheetEditor::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& referenceSpritesheet = document.referenceSpritesheet;
|
||||
auto& referenceSpritesheet = document.spritesheet.reference;
|
||||
auto& pan = document.editorPan;
|
||||
auto& zoom = document.editorZoom;
|
||||
auto& backgroundColor = settings.editorBackgroundColor;
|
||||
auto& gridColor = settings.editorGridColor;
|
||||
auto& gridSize = settings.editorGridSize;
|
||||
auto& gridOffset = settings.editorGridOffset;
|
||||
auto& toolColor = settings.toolColor;
|
||||
auto& isGrid = settings.editorIsGrid;
|
||||
auto& isGridSnap = settings.editorIsGridSnap;
|
||||
auto& zoomStep = settings.viewZoomStep;
|
||||
auto& isBorder = settings.editorIsBorder;
|
||||
auto spritesheet = document.spritesheet_get();
|
||||
@@ -39,6 +45,8 @@ namespace anm2ed::imgui
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
auto& dashedShader = resources.shaders[shader::DASHED];
|
||||
|
||||
auto center_view = [&]() { pan = -size * 0.5f; };
|
||||
|
||||
if (ImGui::Begin("Spritesheet Editor", &settings.windowIsSpritesheetEditor))
|
||||
{
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(3),
|
||||
@@ -47,10 +55,19 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::Checkbox("Grid", &isGrid);
|
||||
ImGui::SetItemTooltip("Toggle the visibility of the grid.");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Snap", &isGridSnap);
|
||||
ImGui::SetItemTooltip("Cropping will snap points to the grid.");
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::InputInt2("Size", value_ptr(gridSize));
|
||||
ImGui::InputInt2("Offset", value_ptr(gridOffset));
|
||||
ImGui::SetItemTooltip("Change the grid's color.");
|
||||
|
||||
input_int2_range("Size", gridSize, ivec2(GRID_SIZE_MIN), ivec2(GRID_SIZE_MAX));
|
||||
ImGui::SetItemTooltip("Change the size of all cells in the grid.");
|
||||
|
||||
input_int2_range("Offset", gridOffset, ivec2(GRID_OFFSET_MIN), ivec2(GRID_OFFSET_MAX));
|
||||
ImGui::SetItemTooltip("Change the offset of the grid.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -59,11 +76,12 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
ImGui::SetItemTooltip("Change the zoom of the editor.");
|
||||
|
||||
auto widgetSize = ImVec2(imgui::row_widget_width_get(2), 0);
|
||||
|
||||
imgui::shortcut(settings.shortcutCenterView);
|
||||
if (ImGui::Button("Center View", widgetSize)) pan = -size * 0.5f;
|
||||
if (ImGui::Button("Center View", widgetSize)) center_view();
|
||||
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -82,8 +100,10 @@ namespace anm2ed::imgui
|
||||
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SetItemTooltip("Change the background color.");
|
||||
|
||||
ImGui::Checkbox("Border", &isBorder);
|
||||
ImGui::SetItemTooltip("Toggle a border appearing around the spritesheet.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -127,45 +147,85 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
previousMousePos = mousePos;
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
auto isMouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isMouseLeftDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
||||
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
||||
auto isMouseRightClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Right);
|
||||
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
||||
auto isMouseRightReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Right);
|
||||
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
|
||||
auto mouseWheel = ImGui::GetIO().MouseWheel;
|
||||
auto& toolColor = settings.toolColor;
|
||||
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
|
||||
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
|
||||
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
|
||||
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
|
||||
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto useTool = tool;
|
||||
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isMouseClicked = isMouseLeftClicked || isMouseRightClicked;
|
||||
auto isMouseDown = isMouseLeftDown || isMouseRightDown;
|
||||
auto isMouseReleased = isMouseLeftReleased || isMouseRightReleased;
|
||||
|
||||
auto isLeftJustPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
|
||||
auto isRightJustPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
|
||||
auto isUpJustPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
|
||||
auto isDownJustPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
|
||||
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow);
|
||||
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow);
|
||||
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow);
|
||||
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow);
|
||||
auto isLeftDown = ImGui::IsKeyDown(ImGuiKey_LeftArrow);
|
||||
auto isRightDown = ImGui::IsKeyDown(ImGuiKey_RightArrow);
|
||||
auto isUpDown = ImGui::IsKeyDown(ImGuiKey_UpArrow);
|
||||
auto isDownDown = ImGui::IsKeyDown(ImGuiKey_DownArrow);
|
||||
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
|
||||
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
|
||||
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
|
||||
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
|
||||
auto frame = document.frame_get();
|
||||
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
||||
auto isKeyJustPressed = isLeftJustPressed || isRightJustPressed || isUpJustPressed || isDownJustPressed;
|
||||
auto isKeyDown = isLeftDown || isRightDown || isUpDown || isDownDown;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
auto isBegin = isMouseClick || isKeyPressed;
|
||||
|
||||
auto isZoomIn = chord_repeating(string_to_chord(settings.shortcutZoomIn));
|
||||
auto isZoomOut = chord_repeating(string_to_chord(settings.shortcutZoomOut));
|
||||
|
||||
auto isBegin = isMouseClicked || isKeyJustPressed;
|
||||
auto isDuring = isMouseDown || isKeyDown;
|
||||
auto isEnd = isMouseReleased || isKeyReleased;
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
auto frame = document.frame_get();
|
||||
auto useTool = tool;
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto stepX = isGridSnap ? step * gridSize.x : step;
|
||||
auto stepY = isGridSnap ? step * gridSize.y : step;
|
||||
previousMousePos = mousePos;
|
||||
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
|
||||
|
||||
auto snap_rect = [&](glm::vec2 minPoint, glm::vec2 maxPoint)
|
||||
{
|
||||
if (isGridSnap)
|
||||
{
|
||||
if (gridSize.x != 0)
|
||||
{
|
||||
minPoint.x = std::floor(minPoint.x / gridSize.x) * gridSize.x;
|
||||
maxPoint.x = std::ceil(maxPoint.x / gridSize.x) * gridSize.x;
|
||||
}
|
||||
if (gridSize.y != 0)
|
||||
{
|
||||
minPoint.y = std::floor(minPoint.y / gridSize.y) * gridSize.y;
|
||||
maxPoint.y = std::ceil(maxPoint.y / gridSize.y) * gridSize.y;
|
||||
}
|
||||
}
|
||||
return std::pair{minPoint, maxPoint};
|
||||
};
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::CROP;
|
||||
if (tool == tool::CROP && isMouseRightDown) useTool = tool::MOVE;
|
||||
if (tool == tool::DRAW && isMouseRightDown) useTool = tool::ERASE;
|
||||
if (tool == tool::ERASE && isMouseRightDown) useTool = tool::DRAW;
|
||||
|
||||
auto& areaType = tool::INFO[useTool].areaType;
|
||||
auto cursor = areaType == tool::SPRITESHEET_EDITOR || areaType == tool::ALL ? tool::INFO[useTool].cursor
|
||||
: ImGuiMouseCursor_NotAllowed;
|
||||
ImGui::SetMouseCursor(cursor);
|
||||
ImGui::SetKeyboardFocusHere(-1);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
@@ -176,36 +236,84 @@ namespace anm2ed::imgui
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot("Frame Pivot");
|
||||
if (isMouseDown) frame->pivot = ivec2(mousePos - frame->crop);
|
||||
if (isLeft) frame->pivot.x -= step;
|
||||
if (isRight) frame->pivot.x += step;
|
||||
if (isUp) frame->pivot.y -= step;
|
||||
if (isDown) frame->pivot.y += step;
|
||||
if (isLeftPressed) frame->pivot.x -= step;
|
||||
if (isRightPressed) frame->pivot.x += step;
|
||||
if (isUpPressed) frame->pivot.y -= step;
|
||||
if (isDownPressed) frame->pivot.y += step;
|
||||
if (isDuring)
|
||||
{
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
auto pivotFormat = math::vec2_format_get(frame->pivot);
|
||||
auto pivotString = std::format("Pivot: ({}, {})", pivotFormat, pivotFormat);
|
||||
ImGui::Text(pivotString.c_str(), frame->pivot.x, frame->pivot.y);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::CROP:
|
||||
if (!frame) break;
|
||||
if (isBegin) document.snapshot(isMod ? "Frame Size" : "Frame Crop");
|
||||
if (isMouseClicked) frame->crop = ivec2(mousePos);
|
||||
if (isMouseDown) frame->size = ivec2(mousePos - frame->crop);
|
||||
if (isLeft) isMod ? frame->size.x -= step : frame->crop.x -= step;
|
||||
if (isRight) isMod ? frame->size.x += step : frame->crop.x += step;
|
||||
if (isUp) isMod ? frame->size.y -= step : frame->crop.y -= step;
|
||||
if (isDown) isMod ? frame->size.y += step : frame->crop.y += step;
|
||||
if (isBegin) document.snapshot("Frame Crop");
|
||||
|
||||
if (isMouseClicked)
|
||||
{
|
||||
cropAnchor = mousePos;
|
||||
frame->crop = cropAnchor;
|
||||
frame->size = vec2();
|
||||
}
|
||||
if (isMouseDown)
|
||||
{
|
||||
auto [minPoint, maxPoint] = snap_rect(glm::min(cropAnchor, mousePos), glm::max(cropAnchor, mousePos));
|
||||
frame->crop = minPoint;
|
||||
frame->size = maxPoint - minPoint;
|
||||
}
|
||||
if (isLeftPressed) frame->crop.x -= stepX;
|
||||
if (isRightPressed) frame->crop.x += stepX;
|
||||
if (isUpPressed) frame->crop.y -= stepY;
|
||||
if (isDownPressed) frame->crop.y += stepY;
|
||||
if (isDuring)
|
||||
{
|
||||
if (!isMouseDown)
|
||||
{
|
||||
auto minPoint = glm::min(frame->crop, frame->crop + frame->size);
|
||||
auto maxPoint = glm::max(frame->crop, frame->crop + frame->size);
|
||||
frame->crop = minPoint;
|
||||
frame->size = maxPoint - minPoint;
|
||||
if (isGridSnap)
|
||||
{
|
||||
auto [snapMin, snapMax] = snap_rect(frame->crop, frame->crop + frame->size);
|
||||
frame->crop = snapMin;
|
||||
frame->size = snapMax - snapMin;
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
auto cropFormat = math::vec2_format_get(frame->crop);
|
||||
auto sizeFormat = math::vec2_format_get(frame->size);
|
||||
auto cropString = std::format("Crop: ({}, {})", cropFormat, cropFormat);
|
||||
auto sizeString = std::format("Size: ({}, {})", sizeFormat, sizeFormat);
|
||||
ImGui::Text(cropString.c_str(), frame->crop.x, frame->crop.y);
|
||||
ImGui::Text(sizeString.c_str(), frame->size.x, frame->size.y);
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::DRAW:
|
||||
case tool::ERASE:
|
||||
{
|
||||
if (!spritesheet) break;
|
||||
auto color = tool == tool::DRAW ? toolColor : vec4();
|
||||
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
|
||||
auto color = useTool == tool::DRAW ? toolColor : vec4();
|
||||
if (isMouseClicked) document.snapshot(useTool == tool::DRAW ? "Draw" : "Erase");
|
||||
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
||||
if (isMouseReleased) document.change(Document::FRAMES);
|
||||
if (isMouseReleased) document.change(Document::SPRITESHEETS);
|
||||
break;
|
||||
}
|
||||
case tool::COLOR_PICKER:
|
||||
{
|
||||
if (isMouseDown)
|
||||
if (isDuring)
|
||||
{
|
||||
auto position = to_vec2(ImGui::GetMousePos());
|
||||
toolColor = pixel_read(position, {settings.windowSize.x, settings.windowSize.y});
|
||||
@@ -222,9 +330,26 @@ namespace anm2ed::imgui
|
||||
}
|
||||
|
||||
if (mouseWheel != 0 || isZoomIn || isZoomOut)
|
||||
zoom_set(zoom, pan, mousePos, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
{
|
||||
auto focus = mouseWheel != 0 ? vec2(mousePos) : vec2();
|
||||
if (auto spritesheet = document.spritesheet_get(); spritesheet && mouseWheel == 0)
|
||||
focus = spritesheet->texture.size / 2;
|
||||
|
||||
zoom_set(zoom, pan, focus, (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (!document.isSpritesheetEditorSet)
|
||||
{
|
||||
size = settings.editorSize;
|
||||
zoom = settings.editorStartZoom;
|
||||
center_view();
|
||||
document.isSpritesheetEditorSet = true;
|
||||
}
|
||||
|
||||
settings.editorSize = size;
|
||||
settings.editorStartZoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace anm2ed::imgui
|
||||
{
|
||||
glm::vec2 mousePos{};
|
||||
glm::vec2 previousMousePos{};
|
||||
glm::vec2 cropAnchor{};
|
||||
|
||||
public:
|
||||
SpritesheetEditor();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "document.h"
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
@@ -15,10 +16,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& multiSelect = document.spritesheetMultiSelect;
|
||||
auto& unused = document.unusedSpritesheetIDs;
|
||||
auto& hovered = document.hoveredSpritesheet;
|
||||
auto& reference = document.referenceSpritesheet;
|
||||
auto& selection = document.spritesheet.selection;
|
||||
auto& unused = document.spritesheet.unused;
|
||||
auto& hovered = document.spritesheet.hovered;
|
||||
auto& reference = document.spritesheet.reference;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
@@ -30,10 +31,10 @@ namespace anm2ed::imgui
|
||||
{
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
if (!selection.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
clipboardText += anm2.content.spritesheets[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
@@ -43,43 +44,38 @@ namespace anm2ed::imgui
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.spritesheets_deserialize(clipboardText, type);
|
||||
std::string errorString{};
|
||||
document.snapshot("Paste Spritesheet(s)");
|
||||
if (anm2.spritesheets_deserialize(clipboard.get(), document.directory_get(), type, &errorString))
|
||||
document.change(Document::SPRITESHEETS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize spritesheet(s): {}", errorString));
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (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::MenuItem("Cut", settings.shortcutCut.c_str(), false, true);
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, !selection.empty() || hovered > -1)) copy();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
if (ImGui::BeginMenu("Paste", !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);
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopStyleVar(2);
|
||||
};
|
||||
|
||||
auto childSize = imgui::size_without_footer_get(2);
|
||||
auto childSize = size_without_footer_get(2);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2());
|
||||
|
||||
@@ -89,7 +85,7 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2());
|
||||
|
||||
multiSelect.start(anm2.content.spritesheets.size());
|
||||
selection.start(anm2.content.spritesheets.size());
|
||||
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
{
|
||||
@@ -97,7 +93,7 @@ namespace anm2ed::imgui
|
||||
|
||||
if (ImGui::BeginChild("##Spritesheet Child", spritesheetChildSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isSelected = selection.contains(id);
|
||||
auto isReferenced = id == reference;
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto& texture = spritesheet.texture.is_valid() ? spritesheet.texture : resources.icons[icon::NONE];
|
||||
@@ -177,7 +173,7 @@ namespace anm2ed::imgui
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
selection.finish();
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
@@ -185,11 +181,11 @@ namespace anm2ed::imgui
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto rowOneWidgetSize = imgui::widget_size_with_row_get(4);
|
||||
auto rowOneWidgetSize = widget_size_with_row_get(4);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_OPEN);
|
||||
imgui::set_item_tooltip_shortcut("Add a new spritesheet.", settings.shortcutAdd);
|
||||
set_item_tooltip_shortcut("Add a new spritesheet.", settings.shortcutAdd);
|
||||
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
|
||||
{
|
||||
@@ -199,16 +195,21 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
ImGui::BeginDisabled(selection.empty());
|
||||
{
|
||||
if (ImGui::Button("Reload", rowOneWidgetSize))
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
auto reload = [&]()
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get());
|
||||
toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
for (auto& id : selection)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get());
|
||||
toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Reload Spritesheet(s)", Document::SPRITESHEETS, reload());
|
||||
}
|
||||
ImGui::SetItemTooltip("Reloads the selected spritesheets.");
|
||||
}
|
||||
@@ -216,7 +217,7 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
ImGui::BeginDisabled(selection.size() != 1);
|
||||
{
|
||||
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_REPLACE);
|
||||
ImGui::SetItemTooltip("Replace the selected spritesheet with a new one.");
|
||||
@@ -225,10 +226,15 @@ namespace anm2ed::imgui
|
||||
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_REPLACE))
|
||||
{
|
||||
auto& id = *multiSelect.begin();
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet = anm2::Spritesheet(document.directory_get(), dialog.path);
|
||||
toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
auto replace = [&]()
|
||||
{
|
||||
auto& id = *selection.begin();
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet = anm2::Spritesheet(document.directory_get(), dialog.path);
|
||||
toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Replace Spritesheet", Document::SPRITESHEETS, replace());
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
@@ -236,50 +242,54 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
{
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove Unused", rowOneWidgetSize))
|
||||
{
|
||||
for (auto& id : unused)
|
||||
auto remove_unused = [&]()
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
toasts.info(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
anm2.spritesheet_remove(id);
|
||||
}
|
||||
unused.clear();
|
||||
document.change(Document::SPRITESHEETS);
|
||||
for (auto& id : unused)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
toasts.info(std::format("Removed spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
anm2.content.spritesheets.erase(id);
|
||||
}
|
||||
unused.clear();
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Unused Spritesheets", Document::SPRITESHEETS, remove_unused());
|
||||
}
|
||||
imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
|
||||
settings.shortcutRemove);
|
||||
set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
auto rowTwoWidgetSize = imgui::widget_size_with_row_get(3);
|
||||
auto rowTwoWidgetSize = widget_size_with_row_get(3);
|
||||
|
||||
imgui::shortcut(settings.shortcutSelectAll);
|
||||
ImGui::BeginDisabled(multiSelect.size() == anm2.content.spritesheets.size());
|
||||
shortcut(settings.shortcutSelectAll);
|
||||
ImGui::BeginDisabled(selection.size() == anm2.content.spritesheets.size());
|
||||
{
|
||||
if (ImGui::Button("Select All", rowTwoWidgetSize))
|
||||
for (auto& id : anm2.content.spritesheets | std::views::keys)
|
||||
multiSelect.insert(id);
|
||||
selection.insert(id);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll);
|
||||
set_item_tooltip_shortcut("Select all spritesheets.", settings.shortcutSelectAll);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutSelectNone);
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
if (ImGui::Button("Select None", rowTwoWidgetSize)) multiSelect.clear();
|
||||
imgui::set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone);
|
||||
shortcut(settings.shortcutSelectNone);
|
||||
ImGui::BeginDisabled(selection.empty());
|
||||
if (ImGui::Button("Select None", rowTwoWidgetSize)) selection.clear();
|
||||
set_item_tooltip_shortcut("Unselect all spritesheets.", settings.shortcutSelectNone);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
ImGui::BeginDisabled(selection.empty());
|
||||
{
|
||||
if (ImGui::Button("Save", rowTwoWidgetSize))
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
for (auto& id : selection)
|
||||
{
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
if (spritesheet.save(document.directory_get()))
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
@@ -48,41 +50,59 @@ namespace anm2ed::imgui
|
||||
void Timeline::context_menu(Document& document, Settings& settings, Clipboard& clipboard)
|
||||
{
|
||||
auto& hoveredFrame = document.hoveredFrame;
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, style.WindowPadding);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (auto frame = document.anm2.frame_get(hoveredFrame))
|
||||
{
|
||||
clipboard.set(frame->to_string(hoveredFrame.itemType));
|
||||
}
|
||||
if (auto frame = anm2.frame_get(hoveredFrame)) clipboard.set(frame->to_string(hoveredFrame.itemType));
|
||||
};
|
||||
|
||||
auto cut = [&]()
|
||||
{
|
||||
copy();
|
||||
document.frames_delete(document.item_get());
|
||||
auto frames_delete = [&]()
|
||||
{
|
||||
if (auto item = anm2.item_get(reference); item)
|
||||
{
|
||||
item->frames.erase(item->frames.begin() + reference.frameIndex);
|
||||
reference.frameIndex = glm::max(-1, --reference.frameIndex);
|
||||
}
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Cut Frame(s)", Document::FRAMES, frames_delete());
|
||||
};
|
||||
|
||||
auto paste = [&]() { document.frames_deserialize(clipboard.get()); };
|
||||
auto paste = [&]()
|
||||
{
|
||||
if (auto item = document.item_get())
|
||||
{
|
||||
document.snapshot("Paste Frame(s)");
|
||||
std::set<int> indices{};
|
||||
std::string errorString{};
|
||||
auto start = reference.frameIndex + 1;
|
||||
if (item->frames_deserialize(clipboard.get(), reference.itemType, start, indices, &errorString))
|
||||
document.change(Document::FRAMES);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize frame(s): {}", errorString));
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize frame(s): select an item first!"));
|
||||
};
|
||||
|
||||
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 (shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste();
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled(hoveredFrame == anm2::REFERENCE_DEFAULT);
|
||||
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")) paste();
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::MenuItem("Cut", settings.shortcutCut.c_str(), false, hoveredFrame != anm2::Reference{})) cut();
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str(), false, hoveredFrame != anm2::Reference{})) copy();
|
||||
|
||||
if (ImGui::MenuItem("Paste", nullptr, false, !clipboard.is_empty())) paste();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
@@ -165,7 +185,7 @@ namespace anm2ed::imgui
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) reference = itemReference;
|
||||
}
|
||||
|
||||
ImGui::Image(resources.icons[icon].id, imgui::icon_size_get());
|
||||
ImGui::Image(resources.icons[icon].id, icon_size_get());
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
|
||||
@@ -181,8 +201,8 @@ namespace anm2ed::imgui
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
int visibleIcon = isVisible ? icon::VISIBLE : icon::INVISIBLE;
|
||||
|
||||
if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get()))
|
||||
document.item_visible_toggle(item);
|
||||
if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, icon_size_get()))
|
||||
DOCUMENT_EDIT(document, "Item Visibility", Document::FRAMES, isVisible = !isVisible);
|
||||
ImGui::SetItemTooltip(isVisible ? "The item is shown. Press to hide." : "The item is hidden. Press to show.");
|
||||
|
||||
if (type == anm2::NULL_)
|
||||
@@ -194,8 +214,8 @@ namespace anm2ed::imgui
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
if (ImGui::ImageButton("##Rect Toggle", resources.icons[rectIcon].id, imgui::icon_size_get()))
|
||||
document.null_rect_toggle(null);
|
||||
if (ImGui::ImageButton("##Rect Toggle", resources.icons[rectIcon].id, icon_size_get()))
|
||||
DOCUMENT_EDIT(document, "Null Rect", Document::FRAMES, null.isShowRect = !null.isShowRect);
|
||||
ImGui::SetItemTooltip(isShowRect ? "The null's rect is shown. Press to hide."
|
||||
: "The null's rect is hidden. Press to show.");
|
||||
}
|
||||
@@ -217,7 +237,7 @@ namespace anm2ed::imgui
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
|
||||
|
||||
auto unusedIcon = isShowUnused ? icon::SHOW_UNUSED : icon::HIDE_UNUSED;
|
||||
if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, imgui::icon_size_get()))
|
||||
if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, icon_size_get()))
|
||||
isShowUnused = !isShowUnused;
|
||||
ImGui::SetItemTooltip(isShowUnused ? "Unused layers/nulls are shown. Press to hide."
|
||||
: "Unused layers/nulls are hidden. Press to show.");
|
||||
@@ -229,7 +249,7 @@ namespace anm2ed::imgui
|
||||
ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
|
||||
if (ImGui::ImageButton("##Layers Toggle", resources.icons[layersIcon].id, imgui::icon_size_get()))
|
||||
if (ImGui::ImageButton("##Layers Toggle", resources.icons[layersIcon].id, icon_size_get()))
|
||||
isOnlyShowLayers = !isOnlyShowLayers;
|
||||
ImGui::SetItemTooltip(isOnlyShowLayers ? "Only layers are visible. Press to show all items."
|
||||
: "All items are visible. Press to only show layers.");
|
||||
@@ -329,21 +349,30 @@ namespace anm2ed::imgui
|
||||
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);
|
||||
auto widgetSize = widget_size_with_row_get(2, ImGui::GetContentRegionAvail().x - style.WindowPadding.x);
|
||||
|
||||
ImGui::BeginDisabled(!animation);
|
||||
{
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) propertiesPopup.open();
|
||||
imgui::set_item_tooltip_shortcut("Add a new item to the animation.", settings.shortcutAdd);
|
||||
set_item_tooltip_shortcut("Add a new item to the animation.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(!document.item_get() && reference.itemType != anm2::LAYER &&
|
||||
reference.itemType != anm2::NULL_);
|
||||
{
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove", widgetSize)) document.item_remove(animation);
|
||||
imgui::set_item_tooltip_shortcut("Remove the selected items from the animation.", settings.shortcutRemove);
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove", widgetSize))
|
||||
{
|
||||
auto remove = [&]()
|
||||
{
|
||||
animation->item_remove(reference.itemType, reference.itemID);
|
||||
reference = {reference.animationIndex};
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Remove Item", Document::ITEMS, remove());
|
||||
}
|
||||
set_item_tooltip_shortcut("Remove the selected items from the animation.", settings.shortcutRemove);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
@@ -534,11 +563,12 @@ namespace anm2ed::imgui
|
||||
if (ImGui::Button("##Frame Button", size))
|
||||
{
|
||||
if (type != anm2::TRIGGER && ImGui::IsKeyDown(ImGuiMod_Alt))
|
||||
document.frame_is_interpolated_set(&frame, !frame.isInterpolated);
|
||||
DOCUMENT_EDIT(document, "Frame Interpolation", Document::FRAMES,
|
||||
frame.isInterpolated = !frame.isInterpolated);
|
||||
if (type == anm2::LAYER)
|
||||
{
|
||||
document.referenceSpritesheet = anm2.content.layers[id].spritesheetID;
|
||||
document.layersMultiSelect = {id};
|
||||
document.spritesheet.reference = anm2.content.layers[id].spritesheetID;
|
||||
document.layer.selection = {id};
|
||||
}
|
||||
reference = frameReference;
|
||||
reference.frameTime = frameTime;
|
||||
@@ -575,8 +605,8 @@ namespace anm2ed::imgui
|
||||
Clipboard& clipboard)
|
||||
{
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& playback = document.playback;
|
||||
auto& hoveredFrame = document.hoveredFrame;
|
||||
|
||||
auto itemsChildWidth = ImGui::GetTextLineHeightWithSpacing() * 15;
|
||||
|
||||
@@ -697,16 +727,16 @@ namespace anm2ed::imgui
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(ImGui::GetStyle().WindowPadding.x, ImGui::GetCursorPos().y + ImGui::GetStyle().ItemSpacing.y));
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(10);
|
||||
auto widgetSize = widget_size_with_row_get(10);
|
||||
|
||||
ImGui::BeginDisabled(!animation);
|
||||
{
|
||||
auto label = playback.isPlaying ? "Pause" : "Play";
|
||||
auto tooltip = playback.isPlaying ? "Pause the animation." : "Play the animation.";
|
||||
|
||||
imgui::shortcut(settings.shortcutPlayPause);
|
||||
shortcut(settings.shortcutPlayPause);
|
||||
if (ImGui::Button(label, widgetSize)) playback.toggle();
|
||||
imgui::set_item_tooltip_shortcut(tooltip, settings.shortcutPlayPause);
|
||||
set_item_tooltip_shortcut(tooltip, settings.shortcutPlayPause);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -714,17 +744,45 @@ namespace anm2ed::imgui
|
||||
|
||||
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);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Insert Frame", widgetSize))
|
||||
{
|
||||
auto insert_frame = [&]()
|
||||
{
|
||||
auto frame = document.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;
|
||||
}
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Insert Frame", Document::FRAMES, insert_frame());
|
||||
}
|
||||
set_item_tooltip_shortcut("Insert a frame, based on the current selection.", settings.shortcutAdd);
|
||||
|
||||
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);
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Delete Frame", widgetSize))
|
||||
{
|
||||
auto delete_frame = [&]()
|
||||
{
|
||||
item->frames.erase(item->frames.begin() + reference.frameIndex);
|
||||
reference.frameIndex = glm::max(-1, --reference.frameIndex);
|
||||
};
|
||||
|
||||
DOCUMENT_EDIT(document, "Delete Frame(s)", Document::FRAMES, delete_frame());
|
||||
}
|
||||
set_item_tooltip_shortcut("Delete the selected frames.", settings.shortcutRemove);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -743,8 +801,8 @@ namespace anm2ed::imgui
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetNextItemWidth(widgetSize.x);
|
||||
ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), imgui::STEP,
|
||||
imgui::STEP_FAST, !animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0);
|
||||
ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), STEP, STEP_FAST,
|
||||
!animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0);
|
||||
if (animation) animation->frameNum = clamp(animation->frameNum, anm2::FRAME_NUM_MIN, anm2::FRAME_NUM_MAX);
|
||||
ImGui::SetItemTooltip("Set the animation's length.");
|
||||
|
||||
@@ -766,7 +824,7 @@ namespace anm2ed::imgui
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetNextItemWidth(widgetSize.x);
|
||||
imgui::input_text_string("Author", &anm2.info.createdBy);
|
||||
input_text_string("Author", &anm2.info.createdBy);
|
||||
ImGui::SetItemTooltip("Set the author of the document.");
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -818,8 +876,8 @@ namespace anm2ed::imgui
|
||||
isUnusedItemsSet = true;
|
||||
}
|
||||
|
||||
auto footerSize = imgui::footer_size_get();
|
||||
auto optionsSize = imgui::child_size_get(11);
|
||||
auto footerSize = footer_size_get();
|
||||
auto optionsSize = child_size_get(11);
|
||||
auto itemsSize = ImVec2(0, ImGui::GetContentRegionAvail().y -
|
||||
(optionsSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 4));
|
||||
if (ImGui::BeginChild("Options", optionsSize, ImGuiChildFlags_Borders))
|
||||
@@ -890,12 +948,11 @@ namespace anm2ed::imgui
|
||||
|
||||
ImGui::BeginDisabled(source == source::EXISTING);
|
||||
{
|
||||
imgui::input_text_string("Name", &addItemName);
|
||||
input_text_string("Name", &addItemName);
|
||||
ImGui::SetItemTooltip("Set the item's name.");
|
||||
ImGui::BeginDisabled(type != anm2::LAYER);
|
||||
{
|
||||
auto spritesheets = anm2.spritesheet_names_get();
|
||||
imgui::combo_strings("Spritesheet", &addItemSpritesheetID, spritesheets);
|
||||
combo_negative_one_indexed("Spritesheet", &addItemSpritesheetID, document.spritesheet.labels);
|
||||
ImGui::SetItemTooltip("Set the layer item's spritesheet.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
@@ -934,11 +991,24 @@ namespace anm2ed::imgui
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
document.item_add((anm2::Type)type, addItemID, addItemName, (locale::Type)locale, addItemSpritesheetID);
|
||||
anm2::Reference addReference{};
|
||||
|
||||
document.snapshot("Add Item");
|
||||
if (type == anm2::LAYER)
|
||||
addReference = anm2.layer_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName,
|
||||
addItemSpritesheetID - 1, (locale::Type)locale);
|
||||
else if (type == anm2::NULL_)
|
||||
addReference = anm2.null_animation_add({reference.animationIndex, anm2::LAYER, addItemID}, addItemName,
|
||||
(locale::Type)locale);
|
||||
|
||||
document.change(Document::ITEMS);
|
||||
|
||||
reference = addReference;
|
||||
|
||||
item_properties_close();
|
||||
}
|
||||
ImGui::SetItemTooltip("Add the item, with the settings specified.");
|
||||
@@ -961,7 +1031,7 @@ namespace anm2ed::imgui
|
||||
|
||||
auto frame = document.frame_get();
|
||||
|
||||
imgui::input_int_range("Interval", interval, anm2::FRAME_DELAY_MIN, frame ? frame->delay : anm2::FRAME_DELAY_MIN);
|
||||
input_int_range("Interval", interval, anm2::FRAME_DELAY_MIN, frame ? frame->delay : anm2::FRAME_DELAY_MIN);
|
||||
ImGui::SetItemTooltip("Set the maximum delay of each frame that will be baked.");
|
||||
|
||||
ImGui::Checkbox("Round Rotation", &isRoundRotation);
|
||||
@@ -970,11 +1040,13 @@ namespace anm2ed::imgui
|
||||
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);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Bake", widgetSize))
|
||||
{
|
||||
document.frames_bake(interval, isRoundScale, isRoundRotation);
|
||||
if (auto item = document.item_get())
|
||||
DOCUMENT_EDIT(document, "Bake Frames", Document::FRAMES,
|
||||
item->frames_bake(reference.frameIndex, interval, isRoundScale, isRoundRotation));
|
||||
bakePopup.close();
|
||||
}
|
||||
ImGui::SetItemTooltip("Bake the selected frame(s) with the options selected.");
|
||||
@@ -1009,24 +1081,41 @@ namespace anm2ed::imgui
|
||||
|
||||
popups(document, animation, settings);
|
||||
|
||||
if (imgui::shortcut(settings.shortcutPlayPause, shortcut::GLOBAL)) playback.toggle();
|
||||
if (shortcut(settings.shortcutPlayPause, shortcut::GLOBAL)) playback.toggle();
|
||||
|
||||
if (animation)
|
||||
{
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutPreviousFrame)))
|
||||
if (chord_repeating(string_to_chord(settings.shortcutPreviousFrame)))
|
||||
{
|
||||
playback.decrement(settings.playbackIsClampPlayhead ? animation->frameNum : anm2::FRAME_NUM_MAX);
|
||||
reference.frameTime = playback.time;
|
||||
}
|
||||
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutNextFrame)))
|
||||
if (chord_repeating(string_to_chord(settings.shortcutNextFrame)))
|
||||
{
|
||||
playback.increment(settings.playbackIsClampPlayhead ? animation->frameNum : anm2::FRAME_NUM_MAX);
|
||||
reference.frameTime = playback.time;
|
||||
}
|
||||
}
|
||||
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutShortenFrame))) document.frame_shorten();
|
||||
if (imgui::chord_repeating(imgui::string_to_chord(settings.shortcutExtendFrame))) document.frame_extend();
|
||||
if (ImGui::IsKeyChordPressed(string_to_chord(settings.shortcutShortenFrame))) document.snapshot("Shorten Frame");
|
||||
if (chord_repeating(string_to_chord(settings.shortcutShortenFrame)))
|
||||
{
|
||||
if (auto frame = document.frame_get())
|
||||
{
|
||||
frame->shorten();
|
||||
document.change(Document::FRAMES);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyChordPressed(string_to_chord(settings.shortcutExtendFrame))) document.snapshot("Extend Frame");
|
||||
if (chord_repeating(string_to_chord(settings.shortcutExtendFrame)))
|
||||
{
|
||||
if (auto frame = document.frame_get())
|
||||
{
|
||||
frame->extend();
|
||||
document.change(Document::FRAMES);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ namespace anm2ed::imgui
|
||||
PopupHelper propertiesPopup{PopupHelper("Item Properties", POPUP_NORMAL)};
|
||||
PopupHelper bakePopup{PopupHelper("Bake", POPUP_TO_CONTENT)};
|
||||
std::string addItemName{};
|
||||
int addItemSpritesheetID{};
|
||||
bool addItemIsRect{};
|
||||
int addItemID{-1};
|
||||
int addItemSpritesheetID{-1};
|
||||
bool isUnusedItemsSet{};
|
||||
std::set<int> unusedItems{};
|
||||
glm::vec2 scroll{};
|
||||
|
||||
@@ -31,12 +31,10 @@ namespace anm2ed::imgui
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("New", widgetSize)) dialog.file_open(dialog::ANM2_NEW); // handled in taskbar.cpp
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Open", widgetSize)) dialog.file_open(dialog::ANM2_OPEN); // handled in taskbar.cpp
|
||||
|
||||
if (ImGui::BeginChild("##Recent Files Child", ImVec2(), ImGuiChildFlags_Borders))
|
||||
if (ImGui::BeginChild("##Recent Files Child", {}, ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
|
||||
@@ -90,20 +90,23 @@ namespace anm2ed
|
||||
|
||||
void Manager::autosave(Document& document)
|
||||
{
|
||||
auto filename = "." + document.filename_get().string() + ".autosave";
|
||||
auto path = document.directory_get() / filename;
|
||||
std::string errorString{};
|
||||
document.autosave(path, &errorString);
|
||||
auto autosavePath = document.autosave_path_get();
|
||||
document.autosave(&errorString);
|
||||
|
||||
autosaveFiles.erase(std::remove(autosaveFiles.begin(), autosaveFiles.end(), path), autosaveFiles.end());
|
||||
autosaveFiles.insert(autosaveFiles.begin(), path);
|
||||
autosaveFiles.erase(std::remove(autosaveFiles.begin(), autosaveFiles.end(), autosavePath), autosaveFiles.end());
|
||||
autosaveFiles.insert(autosaveFiles.begin(), autosavePath);
|
||||
|
||||
autosave_files_write();
|
||||
}
|
||||
|
||||
void Manager::close(int index)
|
||||
{
|
||||
if (index < 0 || index >= (int)documents.size()) return;
|
||||
if (!vector::in_bounds(documents, index)) return;
|
||||
|
||||
autosaveFiles.erase(std::remove(autosaveFiles.begin(), autosaveFiles.end(), get()->autosave_path_get()),
|
||||
autosaveFiles.end());
|
||||
autosave_files_write();
|
||||
|
||||
documents.erase(documents.begin() + index);
|
||||
|
||||
@@ -146,7 +149,7 @@ namespace anm2ed
|
||||
else
|
||||
editLayer = document->anm2.content.layers.at(id);
|
||||
|
||||
document->referenceLayer = id;
|
||||
document->layer.reference = id;
|
||||
|
||||
layerPropertiesPopup.open();
|
||||
}
|
||||
@@ -177,7 +180,7 @@ namespace anm2ed
|
||||
else
|
||||
editNull = document->anm2.content.nulls.at(id);
|
||||
|
||||
document->referenceNull = id;
|
||||
document->null.reference = id;
|
||||
|
||||
nullPropertiesPopup.open();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ namespace anm2ed
|
||||
int pendingSelected{-1};
|
||||
|
||||
bool isRecording{};
|
||||
bool isRecordingStart{};
|
||||
int recordingStart{};
|
||||
int recordingEnd{};
|
||||
bool isRecordingRange{};
|
||||
|
||||
anm2::Layer editLayer{};
|
||||
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
18
src/render.h
18
src/render.h
@@ -5,21 +5,27 @@
|
||||
namespace anm2ed::render
|
||||
{
|
||||
#define RENDER_LIST \
|
||||
X(PNGS, "PNGs") \
|
||||
X(GIF, "GIF") \
|
||||
X(WEBM, "WebM") \
|
||||
X(MP4, "MP4")
|
||||
X(PNGS, "PNGs", "") \
|
||||
X(GIF, "GIF", ".gif") \
|
||||
X(WEBM, "WebM", ".webm") \
|
||||
X(MP4, "MP4", ".mp4")
|
||||
|
||||
enum Type
|
||||
{
|
||||
#define X(symbol, string) symbol,
|
||||
#define X(symbol, string, extension) symbol,
|
||||
RENDER_LIST
|
||||
#undef X
|
||||
COUNT
|
||||
};
|
||||
|
||||
constexpr const char* STRINGS[] = {
|
||||
#define X(symbol, string) string,
|
||||
#define X(symbol, string, extension) string,
|
||||
RENDER_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr const char* EXTENSIONS[] = {
|
||||
#define X(symbol, string, extension) extension,
|
||||
RENDER_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
@@ -22,9 +22,9 @@ namespace anm2ed::resource
|
||||
internal = nullptr;
|
||||
}
|
||||
|
||||
void Audio::play()
|
||||
void Audio::play(MIX_Mixer* mixer)
|
||||
{
|
||||
MIX_PlayAudio(mixer_get(), internal);
|
||||
MIX_PlayAudio(mixer ? mixer : mixer_get(), internal);
|
||||
}
|
||||
|
||||
Audio::Audio(Audio&& other) noexcept
|
||||
@@ -46,4 +46,9 @@ namespace anm2ed::resource
|
||||
{
|
||||
unload();
|
||||
}
|
||||
|
||||
bool Audio::is_valid()
|
||||
{
|
||||
return internal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace anm2ed::resource
|
||||
Audio(const Audio&) = delete;
|
||||
Audio& operator=(const Audio&) = delete;
|
||||
|
||||
void play();
|
||||
bool is_valid();
|
||||
void play(MIX_Mixer* = nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -129,6 +129,10 @@ namespace anm2ed::resource::icon
|
||||
<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 TARGET_ALT_DATA = R"(
|
||||
<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"/> <circle cx="12" cy="12" r="0.3" fill="#FFF" stroke="none"/> </svg>
|
||||
)";
|
||||
|
||||
constexpr auto INTERPOLATED_DATA = R"(
|
||||
<svg viewBox="0 0 24 24" fill="#FFF" xmlns="http://www.w3.org/2000/svg"> <circle cx="12" cy="12" r="2.5"/> </svg>
|
||||
)";
|
||||
@@ -185,7 +189,8 @@ namespace anm2ed::resource::icon
|
||||
X(PLAYHEAD, PLAYHEAD_DATA, SIZE_NORMAL) \
|
||||
X(PIVOT, PIVOT_DATA, SIZE_NORMAL) \
|
||||
X(POINT, UNINTERPOLATED_DATA, SIZE_NORMAL) \
|
||||
X(TARGET, TARGET_DATA, SIZE_HUGE)
|
||||
X(TARGET, TARGET_DATA, SIZE_HUGE) \
|
||||
X(TARGET_ALT, TARGET_ALT_DATA, SIZE_HUGE)
|
||||
|
||||
enum Type
|
||||
{
|
||||
|
||||
@@ -43,11 +43,14 @@ namespace anm2ed
|
||||
/* Symbol / Name / String / Type / Default */ \
|
||||
X(WINDOW_SIZE, windowSize, "Window Size", IVEC2_WH, {1600, 900}) \
|
||||
X(IS_VSYNC, isVsync, "Vsync", BOOL, true) \
|
||||
X(DISPLAY_SCALE, displayScale, "Display Scale", FLOAT, 1.0f) \
|
||||
X(UI_SCALE, uiScale, "UI Scale", FLOAT, 1.0f) \
|
||||
\
|
||||
X(FILE_IS_AUTOSAVE, fileIsAutosave, "Autosave", BOOL, true) \
|
||||
X(FILE_AUTOSAVE_TIME, fileAutosaveTime, "Autosave Time", INT, 1) \
|
||||
\
|
||||
X(KEYBOARD_REPEAT_DELAY, keyboardRepeatDelay, "Repeat Delay", FLOAT, 0.300f) \
|
||||
X(KEYBOARD_REPEAT_RATE, keyboardRepeatRate, "Repeat Rate", FLOAT, 0.050f) \
|
||||
\
|
||||
X(VIEW_ZOOM_STEP, viewZoomStep, "Zoom Step", FLOAT, 50.0f) \
|
||||
\
|
||||
X(PLAYBACK_IS_LOOP, playbackIsLoop, "Loop", BOOL, true) \
|
||||
@@ -87,8 +90,7 @@ namespace anm2ed
|
||||
X(PREVIEW_IS_BORDER, previewIsBorder, "Border", BOOL, false) \
|
||||
X(PREVIEW_IS_ALT_ICONS, previewIsAltIcons, "Alt Icons", BOOL, false) \
|
||||
X(PREVIEW_OVERLAY_TRANSPARENCY, previewOverlayTransparency, "Alpha", FLOAT, 255) \
|
||||
X(PREVIEW_ZOOM, previewZoom, "Zoom", FLOAT, 200.0f) \
|
||||
X(PREVIEW_PAN, previewPan, "Pan", VEC2, {}) \
|
||||
X(PREVIEW_START_ZOOM, previewStartZoom, "Start Zoom", FLOAT, 200.0f) \
|
||||
X(PREVIEW_GRID_SIZE, previewGridSize, "Size", IVEC2, {32, 32}) \
|
||||
X(PREVIEW_GRID_OFFSET, previewGridOffset, "Offset", IVEC2, {}) \
|
||||
X(PREVIEW_GRID_COLOR, previewGridColor, "Color", VEC4, {1.0f, 1.0f, 1.0f, 0.125f}) \
|
||||
@@ -109,9 +111,9 @@ namespace anm2ed
|
||||
X(EDITOR_IS_GRID, editorIsGrid, "Grid", BOOL, true) \
|
||||
X(EDITOR_IS_GRID_SNAP, editorIsGridSnap, "Snap", BOOL, true) \
|
||||
X(EDITOR_IS_BORDER, editorIsBorder, "Border", BOOL, true) \
|
||||
X(EDITOR_ZOOM, editorZoom, "Zoom", FLOAT, 200.0f) \
|
||||
X(EDITOR_PAN, editorPan, "Pan", VEC2, {0.0, 0.0}) \
|
||||
X(EDITOR_GRID_SIZE, editorGridSize, "Size", IVEC2, {32, 32}) \
|
||||
X(EDITOR_START_ZOOM, editorStartZoom, "Zoom", FLOAT, 200.0f) \
|
||||
X(EDITOR_SIZE, editorSize, "Size", IVEC2_WH, {1200, 600}) \
|
||||
X(EDITOR_GRID_SIZE, editorGridSize, "Grid Size", IVEC2, {32, 32}) \
|
||||
X(EDITOR_GRID_OFFSET, editorGridOffset, "Offset", IVEC2, {32, 32}) \
|
||||
X(EDITOR_GRID_COLOR, editorGridColor, "Color", VEC4, {1.0, 1.0, 1.0, 0.125}) \
|
||||
X(EDITOR_BACKGROUND_COLOR, editorBackgroundColor, "Background Color", VEC4, {0.113, 0.184, 0.286, 1.0}) \
|
||||
@@ -143,9 +145,7 @@ namespace anm2ed
|
||||
X(RENDER_TYPE, renderType, "Output", INT, render::PNGS) \
|
||||
X(RENDER_PATH, renderPath, "Path", STRING, ".") \
|
||||
X(RENDER_FORMAT, renderFormat, "Format", STRING, "{}.png") \
|
||||
X(RENDER_IS_USE_ANIMATION_BOUNDS, renderIsUseAnimationBounds, "Use Animation Bounds", BOOL, true) \
|
||||
X(RENDER_IS_TRANSPARENT, renderIsTransparent, "Transparent", BOOL, true) \
|
||||
X(RENDER_IS_RANGE, renderIsRange, "Range", BOOL, false) \
|
||||
X(RENDER_IS_RAW_ANIMATION, renderIsRawAnimation, "Raw Animation", BOOL, true) \
|
||||
X(RENDER_SCALE, renderScale, "Scale", FLOAT, 1.0f) \
|
||||
X(RENDER_FFMPEG_PATH, renderFFmpegPath, "FFmpeg Path", STRING, FFMPEG_PATH_DEFAULT)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace anm2ed
|
||||
return top == 0;
|
||||
}
|
||||
|
||||
void SnapshotStack::push(Snapshot& snapshot)
|
||||
void SnapshotStack::push(const Snapshot& snapshot)
|
||||
{
|
||||
if (top >= MAX)
|
||||
{
|
||||
@@ -31,34 +31,27 @@ namespace anm2ed
|
||||
top = 0;
|
||||
}
|
||||
|
||||
void Snapshots::push(const anm2::Anm2& anm2, anm2::Reference reference, const std::string& message)
|
||||
void Snapshots::push(const Snapshot& snapshot)
|
||||
{
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
undoStack.push(snapshot);
|
||||
redoStack.clear();
|
||||
}
|
||||
|
||||
void Snapshots::undo(anm2::Anm2& anm2, anm2::Reference& reference, std::string& message)
|
||||
void Snapshots::undo()
|
||||
{
|
||||
if (auto current = undoStack.pop())
|
||||
if (auto snapshot = undoStack.pop())
|
||||
{
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
redoStack.push(snapshot);
|
||||
anm2 = current->anm2;
|
||||
reference = current->reference;
|
||||
message = current->message;
|
||||
redoStack.push(current);
|
||||
current = *snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
void Snapshots::redo(anm2::Anm2& anm2, anm2::Reference& reference, std::string& message)
|
||||
void Snapshots::redo()
|
||||
{
|
||||
if (auto current = redoStack.pop())
|
||||
if (auto snapshot = redoStack.pop())
|
||||
{
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
undoStack.push(snapshot);
|
||||
anm2 = current->anm2;
|
||||
reference = current->reference;
|
||||
message = current->message;
|
||||
undoStack.push(current);
|
||||
current = *snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "anm2/anm2.h"
|
||||
#include "playback.h"
|
||||
#include "storage.h"
|
||||
|
||||
namespace anm2ed::snapshots
|
||||
{
|
||||
@@ -15,6 +17,14 @@ namespace anm2ed
|
||||
public:
|
||||
anm2::Anm2 anm2{};
|
||||
anm2::Reference reference{};
|
||||
Playback playback{};
|
||||
Storage animation{};
|
||||
Storage merge{};
|
||||
Storage event{};
|
||||
Storage layer{};
|
||||
Storage null{};
|
||||
Storage sound{};
|
||||
Storage spritesheet{};
|
||||
std::string message = snapshots::ACTION;
|
||||
};
|
||||
|
||||
@@ -25,7 +35,7 @@ namespace anm2ed
|
||||
int top{};
|
||||
|
||||
bool is_empty();
|
||||
void push(Snapshot& snapshot);
|
||||
void push(const Snapshot&);
|
||||
Snapshot* pop();
|
||||
void clear();
|
||||
};
|
||||
@@ -35,11 +45,12 @@ namespace anm2ed
|
||||
public:
|
||||
SnapshotStack undoStack{};
|
||||
SnapshotStack redoStack{};
|
||||
Snapshot current{};
|
||||
|
||||
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 push(const Snapshot&);
|
||||
void undo();
|
||||
void redo();
|
||||
void reset();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace anm2ed
|
||||
|
||||
void State::tick(Settings& settings)
|
||||
{
|
||||
dockspace.tick(manager, settings);
|
||||
|
||||
if (auto document = manager.get())
|
||||
{
|
||||
@@ -36,6 +35,8 @@ namespace anm2ed
|
||||
document->playback.tick(document->anm2.info.fps, animation->frameNum,
|
||||
(animation->isLoop || settings.playbackIsLoop) && !manager.isRecording);
|
||||
}
|
||||
|
||||
dockspace.tick(manager, settings);
|
||||
}
|
||||
|
||||
void State::update(SDL_Window*& window, Settings& settings)
|
||||
@@ -57,10 +58,8 @@ namespace anm2ed
|
||||
if (auto document = manager.get())
|
||||
document->spritesheet_add(droppedFile);
|
||||
else
|
||||
toasts.warning(std::format("Could not open spritesheet: (open a document first!)", droppedFile));
|
||||
toasts.info("Failed to add spritesheet! Open a document first.");
|
||||
}
|
||||
else
|
||||
toasts.warning(std::format("Could not parse file: {} (must be .anm2 or .png)", droppedFile));
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_QUIT:
|
||||
@@ -80,7 +79,9 @@ namespace anm2ed
|
||||
dockspace.update(taskbar, documents, manager, settings, resources, dialog, clipboard);
|
||||
toasts.update();
|
||||
|
||||
ImGui::GetStyle().FontScaleMain = settings.displayScale;
|
||||
ImGui::GetStyle().FontScaleMain = settings.uiScale;
|
||||
ImGui::GetIO().KeyRepeatDelay = settings.keyboardRepeatDelay;
|
||||
ImGui::GetIO().KeyRepeatRate = settings.keyboardRepeatRate;
|
||||
SDL_GetWindowSize(window, &settings.windowSize.x, &settings.windowSize.y);
|
||||
|
||||
if (isQuitting && manager.documents.empty()) isQuit = true;
|
||||
|
||||
12
src/storage.cpp
Normal file
12
src/storage.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "storage.h"
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
void Storage::labels_set(std::vector<std::string> labels)
|
||||
{
|
||||
labelsString = labels;
|
||||
this->labels.clear();
|
||||
for (auto& label : labelsString)
|
||||
this->labels.emplace_back(label.c_str());
|
||||
}
|
||||
}
|
||||
28
src/storage.h
Normal file
28
src/storage.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "anm2/anm2.h"
|
||||
#include "imgui_.h"
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
class Storage
|
||||
{
|
||||
public:
|
||||
int reference{-1};
|
||||
int hovered{-1};
|
||||
std::set<int> unused{};
|
||||
std::vector<std::string> labelsString{};
|
||||
std::vector<const char*> labels{};
|
||||
imgui::MultiSelectStorage selection{};
|
||||
|
||||
void labels_set(std::vector<std::string>);
|
||||
};
|
||||
|
||||
class FrameStorage
|
||||
{
|
||||
public:
|
||||
anm2::Type referenceType{anm2::NONE};
|
||||
int referenceID{-1};
|
||||
int referenceFrameIndex{-1};
|
||||
};
|
||||
}
|
||||
36
src/tool.h
36
src/tool.h
@@ -21,53 +21,61 @@ namespace anm2ed::tool
|
||||
COUNT
|
||||
};
|
||||
|
||||
enum AreaType
|
||||
{
|
||||
ANIMATION_PREVIEW,
|
||||
SPRITESHEET_EDITOR,
|
||||
ALL
|
||||
};
|
||||
|
||||
struct Info
|
||||
{
|
||||
ImGuiMouseCursor cursor{ImGuiMouseCursor_None};
|
||||
resource::icon::Type icon{};
|
||||
ShortcutType shortcut{};
|
||||
AreaType areaType;
|
||||
const char* label{};
|
||||
const char* tooltip{};
|
||||
};
|
||||
|
||||
constexpr Info INFO[] = {
|
||||
{ImGuiMouseCursor_Hand, resource::icon::PAN, SHORTCUT_PAN, "##Pan",
|
||||
{ImGuiMouseCursor_Hand, resource::icon::PAN, SHORTCUT_PAN, ALL, "##Pan",
|
||||
"Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou can also use the middle mouse button to "
|
||||
"pan at any time."},
|
||||
|
||||
{ImGuiMouseCursor_ResizeAll, resource::icon::MOVE, SHORTCUT_MOVE, "##Move",
|
||||
{ImGuiMouseCursor_ResizeAll, resource::icon::MOVE, SHORTCUT_MOVE, ALL, "##Move",
|
||||
"Use the move tool.\nAnimation Preview: Will move the position of the frame."
|
||||
"\nSpritesheet Editor: Will move the pivot, and holding right click will use the Crop functionality instead."
|
||||
"\nUse mouse or directional keys to change the value."},
|
||||
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::ROTATE, SHORTCUT_ROTATE, "##Rotate",
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::ROTATE, SHORTCUT_ROTATE, ANIMATION_PREVIEW, "##Rotate",
|
||||
"Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are "
|
||||
"pressed.\n(Animation Preview only.)"},
|
||||
|
||||
{ImGuiMouseCursor_ResizeNESW, resource::icon::SCALE, SHORTCUT_SCALE, "##Scale",
|
||||
{ImGuiMouseCursor_ResizeNESW, resource::icon::SCALE, SHORTCUT_SCALE, ANIMATION_PREVIEW, "##Scale",
|
||||
"Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are "
|
||||
"pressed.\n(Animation Preview only.)"},
|
||||
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::CROP, SHORTCUT_CROP, "##Crop",
|
||||
"Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged."
|
||||
"\nAlternatively, you can use the arrow keys and Ctrl/Shift to move the size/position, respectively."
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::CROP, SHORTCUT_CROP, SPRITESHEET_EDITOR, "##Crop",
|
||||
"Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged, or directional keys are "
|
||||
"pressed.\nHold CTRL with arrow keys to change position."
|
||||
"\nHolding right click will use the Move tool's functionality."
|
||||
"\n(Spritesheet Editor only.)"},
|
||||
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::DRAW, SHORTCUT_DRAW, "##Draw",
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::DRAW, SHORTCUT_DRAW, SPRITESHEET_EDITOR, "##Draw",
|
||||
"Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)"},
|
||||
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::ERASE, SHORTCUT_ERASE, "##Erase",
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::ERASE, SHORTCUT_ERASE, SPRITESHEET_EDITOR, "##Erase",
|
||||
"Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)"},
|
||||
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::COLOR_PICKER, SHORTCUT_COLOR_PICKER, "##Color Picker",
|
||||
"Selects a color from the canvas.\n(Spritesheet Editor only.)"},
|
||||
{ImGuiMouseCursor_Arrow, resource::icon::COLOR_PICKER, SHORTCUT_COLOR_PICKER, SPRITESHEET_EDITOR,
|
||||
"##Color Picker", "Selects a color from the canvas.\n(Spritesheet Editor only.)"},
|
||||
|
||||
{ImGuiMouseCursor_None, resource::icon::UNDO, SHORTCUT_UNDO, "##Undo", "Undoes the last action."},
|
||||
{ImGuiMouseCursor_None, resource::icon::UNDO, SHORTCUT_UNDO, ALL, "##Undo", "Undoes the last action."},
|
||||
|
||||
{ImGuiMouseCursor_None, resource::icon::REDO, SHORTCUT_REDO, "##Redo", "Redoes the last action."},
|
||||
{ImGuiMouseCursor_None, resource::icon::REDO, SHORTCUT_REDO, ALL, "##Redo", "Redoes the last action."},
|
||||
|
||||
{ImGuiMouseCursor_None, resource::icon::NONE, SHORTCUT_COLOR, "##Color",
|
||||
{ImGuiMouseCursor_None, resource::icon::NONE, SHORTCUT_COLOR, ALL, "##Color",
|
||||
"Selects the color to be used for drawing.\n(Spritesheet Editor only.)"},
|
||||
};
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include "string_.h"
|
||||
|
||||
namespace anm2ed::util::filesystem
|
||||
{
|
||||
std::string path_preferences_get()
|
||||
@@ -27,6 +29,13 @@ namespace anm2ed::util::filesystem
|
||||
return e == ("." + extension);
|
||||
}
|
||||
|
||||
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path& path)
|
||||
{
|
||||
if (path_is_exist(path)) return path;
|
||||
if (path_is_exist(string::backslash_replace(path))) return path;
|
||||
return string::to_lower(path);
|
||||
}
|
||||
|
||||
WorkingDirectory::WorkingDirectory(const std::string& path, bool isFile)
|
||||
{
|
||||
previous = std::filesystem::current_path();
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace anm2ed::util::filesystem
|
||||
std::string path_preferences_get();
|
||||
bool path_is_exist(const std::string&);
|
||||
bool path_is_extension(const std::string&, const std::string&);
|
||||
std::filesystem::path path_lower_case_backslash_handle(std::filesystem::path&);
|
||||
|
||||
class WorkingDirectory
|
||||
{
|
||||
|
||||
@@ -19,13 +19,6 @@ namespace anm2ed::util::string
|
||||
return transformed;
|
||||
}
|
||||
|
||||
std::string backslash_replace_to_lower(const std::string& string)
|
||||
{
|
||||
std::string transformed = string;
|
||||
transformed = backslash_replace(transformed);
|
||||
return to_lower(transformed);
|
||||
}
|
||||
|
||||
std::string quote(const std::string& string)
|
||||
{
|
||||
return "\"" + string + "\"";
|
||||
|
||||
@@ -6,6 +6,5 @@ namespace anm2ed::util::string
|
||||
{
|
||||
std::string to_lower(const std::string&);
|
||||
std::string backslash_replace(const std::string&);
|
||||
std::string backslash_replace_to_lower(const std::string&);
|
||||
bool to_bool(const std::string&);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,15 @@ namespace anm2ed::util::vector
|
||||
return index >= 0 && index < (int)v.size() ? &v[index] : nullptr;
|
||||
}
|
||||
|
||||
template <typename T> bool in_bounds(std::vector<T>& v, int index)
|
||||
{
|
||||
return index >= 0 && index < (int)v.size();
|
||||
}
|
||||
template <typename T> void clamp_in_bounds(std::vector<T>& v, int& index)
|
||||
{
|
||||
index = std::clamp(index, 0, (int)v.size() - 1);
|
||||
}
|
||||
|
||||
template <typename T> std::set<int> move_indices(std::vector<T>& v, std::vector<int>& indices, int index)
|
||||
{
|
||||
if (indices.empty()) return {};
|
||||
@@ -52,12 +61,4 @@ namespace anm2ed::util::vector
|
||||
return moveIndices;
|
||||
}
|
||||
|
||||
template <typename T> bool in_bounds(std::vector<T>& v, int& index)
|
||||
{
|
||||
return index >= 0 || index <= (int)v.size() - 1;
|
||||
}
|
||||
template <typename T> void clamp_in_bounds(std::vector<T>& v, int& index)
|
||||
{
|
||||
index = std::clamp(index, 0, (int)v.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user