Big refactor, shuffling a lot of files around
This commit is contained in:
@@ -19,10 +19,30 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
|
||||
set(SDL_STATIC ON CACHE BOOL "" FORCE)
|
||||
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_HIDAPI OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
|
||||
set(SDL_TRAY OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(external/SDL EXCLUDE_FROM_ALL)
|
||||
|
||||
set(SDLMIXER_DEPS_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_FLAC_LIBFLAC OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_GME OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_MOD_XMP OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_MP3_MPG123 OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_MIDI_FLUIDSYNTH OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_OPUS OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_VORBIS_VORBISFILE OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_VORBIS_TREMOR OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_WAVPACK OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_TEST OFF CACHE BOOL "" FORCE)
|
||||
set(SDLMIXER_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory(external/SDL_mixer EXCLUDE_FROM_ALL)
|
||||
|
||||
add_subdirectory(external/lunasvg)
|
||||
|
||||
set(GLAD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp)
|
||||
@@ -39,6 +59,18 @@ set(IMGUI_SRC
|
||||
set(TINYXML2_SRC external/tinyxml2/tinyxml2.cpp)
|
||||
|
||||
file(GLOB PROJECT_SRC CONFIGURE_DEPENDS
|
||||
src/anm2/*.cpp
|
||||
src/anm2/*.h
|
||||
src/resource/*.cpp
|
||||
src/resource/*.h
|
||||
src/imgui/*.cpp
|
||||
src/imgui/*.h
|
||||
src/imgui/window/*.cpp
|
||||
src/imgui/window/*.h
|
||||
src/util/*.cpp
|
||||
src/util/*.h
|
||||
src/window/*.cpp
|
||||
src/window/*.h
|
||||
src/*.cpp
|
||||
src/*.h
|
||||
)
|
||||
@@ -88,6 +120,8 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
external/glm
|
||||
external/tinyxml2
|
||||
external/lunasvg
|
||||
external/SDL
|
||||
external/SDL_mixer
|
||||
include
|
||||
include/glad
|
||||
src
|
||||
@@ -96,7 +130,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
src/util
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE GL SDL3-static lunasvg)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE GL SDL3-static SDL3_mixer::SDL3_mixer lunasvg)
|
||||
|
||||
message(STATUS "System: ${CMAKE_SYSTEM_NAME}")
|
||||
message(STATUS "Project: ${PROJECT_NAME}")
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::animation_preview
|
||||
{
|
||||
class AnimationPreview : public canvas::Canvas
|
||||
{
|
||||
bool isPreviewHovered{};
|
||||
glm::ivec2 mousePos{};
|
||||
|
||||
public:
|
||||
AnimationPreview();
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&);
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "imgui.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::animations
|
||||
{
|
||||
class Animations
|
||||
{
|
||||
imgui::PopupHelper mergePopup{imgui::PopupHelper("Merge Animations")};
|
||||
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
|
||||
};
|
||||
}
|
||||
1453
src/anm2.cpp
1453
src/anm2.cpp
File diff suppressed because it is too large
Load Diff
264
src/anm2.h
264
src/anm2.h
@@ -1,264 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
#include <vector>
|
||||
|
||||
#include "texture.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FRAME_NUM_MIN = 1;
|
||||
constexpr auto FRAME_NUM_MAX = 100000000;
|
||||
constexpr auto FRAME_DELAY_MIN = 1;
|
||||
constexpr auto FRAME_DELAY_MAX = FRAME_NUM_MAX;
|
||||
constexpr auto FPS_MIN = 1;
|
||||
constexpr auto FPS_MAX = 120;
|
||||
|
||||
constexpr auto MERGED_STRING = "(Merged)";
|
||||
|
||||
constexpr auto NO_PATH = "(No Path)";
|
||||
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
|
||||
constexpr auto NULL_FORMAT = "#{} {}";
|
||||
constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
|
||||
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
|
||||
|
||||
enum Type
|
||||
{
|
||||
NONE,
|
||||
ROOT,
|
||||
LAYER,
|
||||
NULL_,
|
||||
TRIGGER
|
||||
};
|
||||
|
||||
class Reference
|
||||
{
|
||||
public:
|
||||
int animationIndex{-1};
|
||||
Type itemType{NONE};
|
||||
int itemID{-1};
|
||||
int frameIndex{-1};
|
||||
int frameTime{-1};
|
||||
|
||||
auto operator<=>(const Reference&) const = default;
|
||||
};
|
||||
|
||||
constexpr anm2::Reference REFERENCE_DEFAULT = {-1, anm2::NONE, -1, -1, -1};
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
std::string createdBy{"robot"};
|
||||
std::string createdOn{};
|
||||
int fps = 30;
|
||||
int version{};
|
||||
|
||||
Info();
|
||||
Info(tinyxml2::XMLElement*);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
};
|
||||
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
texture::Texture texture;
|
||||
|
||||
Spritesheet();
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
Spritesheet(const std::string&, const std::string& = {});
|
||||
bool save(const std::string&, const std::string& = {});
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
void reload(const std::string&);
|
||||
bool is_valid();
|
||||
std::string to_string(int id);
|
||||
};
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{};
|
||||
|
||||
Layer();
|
||||
Layer(tinyxml2::XMLElement*, int&);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
|
||||
class Null
|
||||
{
|
||||
public:
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
|
||||
Null();
|
||||
Null(tinyxml2::XMLElement*, int&);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
|
||||
Event();
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
|
||||
struct Content
|
||||
{
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
|
||||
Content();
|
||||
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
Content(tinyxml2::XMLElement*);
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
void spritesheet_remove(int&);
|
||||
std::set<int> spritesheets_unused();
|
||||
void layer_add(int&);
|
||||
void null_add(int&);
|
||||
void event_add(int&);
|
||||
bool spritesheets_deserialize(const std::string&, const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
bool layers_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
bool nulls_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
bool events_deserialize(const std::string&, types::merge::Type, std::string* = nullptr);
|
||||
};
|
||||
|
||||
#define MEMBERS \
|
||||
X(isVisible, bool, true) \
|
||||
X(isInterpolated, bool, false) \
|
||||
X(rotation, float, 0.0f) \
|
||||
X(delay, int, FRAME_DELAY_MIN) \
|
||||
X(atFrame, int, -1) \
|
||||
X(eventID, int, -1) \
|
||||
X(pivot, glm::vec2, {}) \
|
||||
X(crop, glm::vec2, {}) \
|
||||
X(position, glm::vec2, {}) \
|
||||
X(size, glm::vec2, {}) \
|
||||
X(scale, glm::vec2, glm::vec2(100.0f)) \
|
||||
X(colorOffset, glm::vec3, types::color::TRANSPARENT) \
|
||||
X(tint, glm::vec4, types::color::WHITE)
|
||||
|
||||
class Frame
|
||||
{
|
||||
public:
|
||||
#define X(name, type, ...) type name = __VA_ARGS__;
|
||||
MEMBERS
|
||||
#undef X
|
||||
|
||||
Frame();
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
std::string to_string(Type type);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
|
||||
void shorten();
|
||||
void extend();
|
||||
};
|
||||
|
||||
struct FrameChange
|
||||
{
|
||||
#define X(name, type, ...) std::optional<type> name{};
|
||||
MEMBERS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef MEMBERS
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{true};
|
||||
|
||||
Item();
|
||||
|
||||
Item(tinyxml2::XMLElement*, Type, int* = nullptr);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
|
||||
int length(Type);
|
||||
Frame frame_generate(float, Type);
|
||||
void frames_change(anm2::FrameChange&, types::frame_change::Type, int, int = 0);
|
||||
bool frames_deserialize(const std::string&, Type, int, std::set<int>&, std::string*);
|
||||
};
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{FRAME_NUM_MIN};
|
||||
bool isLoop{true};
|
||||
Item rootAnimation;
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers;
|
||||
|
||||
Animation();
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
Item* item_get(Type, int = -1);
|
||||
void item_remove(Type, int = -1);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
int length();
|
||||
glm::vec4 rect(bool);
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
struct Animations
|
||||
{
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
|
||||
Animations();
|
||||
|
||||
Animations(tinyxml2::XMLElement*);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
int length();
|
||||
int merge(int, std::set<int>&, types::merge::Type = types::merge::APPEND, bool = true);
|
||||
bool animations_deserialize(const std::string&, int, std::set<int>&, std::string* = nullptr);
|
||||
};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
Info info{};
|
||||
Content content{};
|
||||
Animations animations{};
|
||||
|
||||
Anm2();
|
||||
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);
|
||||
Item* item_get(Reference);
|
||||
Frame* frame_get(Reference);
|
||||
bool spritesheet_add(const std::string&, const std::string&, int&);
|
||||
Spritesheet* spritesheet_get(int);
|
||||
void spritesheet_remove(int);
|
||||
std::set<int> spritesheets_unused();
|
||||
int layer_add();
|
||||
Reference layer_add(Reference = REFERENCE_DEFAULT, std::string = {}, int = 0,
|
||||
types::locale::Type = types::locale::GLOBAL);
|
||||
Reference null_add(Reference = REFERENCE_DEFAULT, std::string = {}, types::locale::Type = types::locale::GLOBAL);
|
||||
void event_add(int&);
|
||||
std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::vector<std::string> animation_names_get();
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
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);
|
||||
};
|
||||
}
|
||||
206
src/anm2/animation.cpp
Normal file
206
src/anm2/animation.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
#include "animation.h"
|
||||
|
||||
#include "map_.h"
|
||||
#include "math_.h"
|
||||
#include "unordered_map_.h"
|
||||
#include "xml_.h"
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Animation::Animation(XMLElement* element)
|
||||
{
|
||||
int id{};
|
||||
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("FrameNum", &frameNum);
|
||||
element->QueryBoolAttribute("Loop", &isLoop);
|
||||
|
||||
if (auto rootAnimationElement = element->FirstChildElement("RootAnimation"))
|
||||
rootAnimation = Item(rootAnimationElement, ROOT);
|
||||
|
||||
if (auto layerAnimationsElement = element->FirstChildElement("LayerAnimations"))
|
||||
{
|
||||
for (auto child = layerAnimationsElement->FirstChildElement("LayerAnimation"); child;
|
||||
child = child->NextSiblingElement("LayerAnimation"))
|
||||
{
|
||||
layerAnimations[id] = Item(child, LAYER, &id);
|
||||
layerOrder.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto nullAnimationsElement = element->FirstChildElement("NullAnimations"))
|
||||
for (auto child = nullAnimationsElement->FirstChildElement("NullAnimation"); child;
|
||||
child = child->NextSiblingElement("NullAnimation"))
|
||||
nullAnimations[id] = Item(child, NULL_, &id);
|
||||
|
||||
if (auto triggersElement = element->FirstChildElement("Triggers")) triggers = Item(triggersElement, TRIGGER);
|
||||
}
|
||||
|
||||
Item* Animation::item_get(Type type, int id)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ROOT:
|
||||
return &rootAnimation;
|
||||
case LAYER:
|
||||
return unordered_map::find(layerAnimations, id);
|
||||
case NULL_:
|
||||
return map::find(nullAnimations, id);
|
||||
case TRIGGER:
|
||||
return &triggers;
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Animation::item_remove(Type type, int id)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case LAYER:
|
||||
layerAnimations.erase(id);
|
||||
for (auto [i, value] : std::views::enumerate(layerOrder))
|
||||
if (value == id) layerOrder.erase(layerOrder.begin() + i);
|
||||
break;
|
||||
case NULL_:
|
||||
nullAnimations.erase(id);
|
||||
break;
|
||||
case ROOT:
|
||||
case TRIGGER:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
auto element = document.NewElement("Animation");
|
||||
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);
|
||||
|
||||
parent->InsertEndChild(element);
|
||||
}
|
||||
|
||||
int Animation::length()
|
||||
{
|
||||
int length{};
|
||||
|
||||
if (int rootAnimationLength = rootAnimation.length(ROOT); rootAnimationLength > length)
|
||||
length = rootAnimationLength;
|
||||
|
||||
for (auto& layerAnimation : layerAnimations | std::views::values)
|
||||
if (int layerAnimationLength = layerAnimation.length(LAYER); layerAnimationLength > length)
|
||||
length = layerAnimationLength;
|
||||
|
||||
for (auto& nullAnimation : nullAnimations | std::views::values)
|
||||
if (int nullAnimationLength = nullAnimation.length(NULL_); nullAnimationLength > length)
|
||||
length = nullAnimationLength;
|
||||
|
||||
if (int triggersLength = triggers.length(TRIGGER); triggersLength > length) length = triggersLength;
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
if (isRootTransform)
|
||||
{
|
||||
auto root = rootAnimation.frame_generate(t, anm2::ROOT);
|
||||
transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation);
|
||||
}
|
||||
|
||||
for (auto& [id, layerAnimation] : layerAnimations)
|
||||
{
|
||||
auto frame = layerAnimation.frame_generate(t, anm2::LAYER);
|
||||
|
||||
if (frame.size == vec2() || !frame.isVisible) continue;
|
||||
|
||||
auto layerTransform = transform * math::quad_model_get(frame.size, frame.position, frame.pivot,
|
||||
math::percent_to_unit(frame.scale), frame.rotation);
|
||||
for (auto& corner : CORNERS)
|
||||
{
|
||||
vec4 world = layerTransform * vec4(corner, 0.0f, 1.0f);
|
||||
minX = std::min(minX, world.x);
|
||||
minY = std::min(minY, world.y);
|
||||
maxX = std::max(maxX, world.x);
|
||||
maxY = std::max(maxY, world.y);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!any) return vec4(-1.0f);
|
||||
return {minX, minY, maxX - minX, maxY - minY};
|
||||
}
|
||||
|
||||
}
|
||||
35
src/anm2/animation.h
Normal file
35
src/anm2/animation.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "item.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FRAME_NUM_MIN = 1;
|
||||
constexpr auto FRAME_NUM_MAX = FRAME_DELAY_MAX;
|
||||
|
||||
class Animation
|
||||
{
|
||||
public:
|
||||
std::string name{"New Animation"};
|
||||
int frameNum{FRAME_NUM_MIN};
|
||||
bool isLoop{true};
|
||||
Item rootAnimation;
|
||||
std::unordered_map<int, Item> layerAnimations{};
|
||||
std::vector<int> layerOrder{};
|
||||
std::map<int, Item> nullAnimations{};
|
||||
Item triggers;
|
||||
|
||||
Animation() = default;
|
||||
Animation(tinyxml2::XMLElement*);
|
||||
Item* item_get(Type, int = -1);
|
||||
void item_remove(Type, int = -1);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
int length();
|
||||
glm::vec4 rect(bool);
|
||||
std::string to_string();
|
||||
};
|
||||
|
||||
}
|
||||
155
src/anm2/animations.cpp
Normal file
155
src/anm2/animations.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "animations.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Animations::Animations(XMLElement* element)
|
||||
{
|
||||
xml::query_string_attribute(element, "DefaultAnimation", &defaultAnimation);
|
||||
|
||||
for (auto child = element->FirstChildElement("Animation"); child; child = child->NextSiblingElement("Animation"))
|
||||
items.push_back(Animation(child));
|
||||
}
|
||||
|
||||
XMLElement* Animations::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("Animations");
|
||||
element->SetAttribute("DefaultAnimation", defaultAnimation.c_str());
|
||||
for (auto& animation : items)
|
||||
animation.serialize(document, element);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Animations::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document));
|
||||
}
|
||||
|
||||
int Animations::length()
|
||||
{
|
||||
int length{};
|
||||
|
||||
for (auto& animation : items)
|
||||
if (int animationLength = animation.length(); animationLength > length) length = animationLength;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
22
src/anm2/animations.h
Normal file
22
src/anm2/animations.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "animation.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto MERGED_STRING = "(Merged)";
|
||||
|
||||
struct Animations
|
||||
{
|
||||
std::string defaultAnimation{};
|
||||
std::vector<Animation> items{};
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
376
src/anm2/anm2.cpp
Normal file
376
src/anm2/anm2.cpp
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "anm2.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "map_.h"
|
||||
#include "time_.h"
|
||||
#include "unordered_map_.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Anm2::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;
|
||||
|
||||
if (document.LoadFile(path.c_str()) != XML_SUCCESS)
|
||||
{
|
||||
if (errorString) *errorString = document.ErrorStr();
|
||||
return;
|
||||
}
|
||||
|
||||
filesystem::WorkingDirectory workingDirectory(path, true);
|
||||
|
||||
const XMLElement* element = document.RootElement();
|
||||
|
||||
if (auto infoElement = element->FirstChildElement("Info")) info = Info((XMLElement*)infoElement);
|
||||
if (auto contentElement = element->FirstChildElement("Content")) content = Content((XMLElement*)contentElement);
|
||||
if (auto animationsElement = element->FirstChildElement("Animations"))
|
||||
animations = Animations((XMLElement*)animationsElement);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/anm2/anm2.h
Normal file
72
src/anm2/anm2.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "animations.h"
|
||||
#include "content.h"
|
||||
#include "info.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto NO_PATH = "[No Path]";
|
||||
|
||||
struct Reference
|
||||
{
|
||||
int animationIndex{-1};
|
||||
Type itemType{NONE};
|
||||
int itemID{-1};
|
||||
int frameIndex{-1};
|
||||
int frameTime{-1};
|
||||
|
||||
auto operator<=>(const Reference&) const = default;
|
||||
};
|
||||
|
||||
constexpr anm2::Reference REFERENCE_DEFAULT = {-1, anm2::NONE, -1, -1, -1};
|
||||
|
||||
class Anm2
|
||||
{
|
||||
public:
|
||||
Info info{};
|
||||
Content content{};
|
||||
Animations animations{};
|
||||
|
||||
Anm2();
|
||||
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);
|
||||
void spritesheet_remove(int);
|
||||
std::set<int> spritesheets_unused();
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
|
||||
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);
|
||||
|
||||
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 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);
|
||||
};
|
||||
}
|
||||
256
src/anm2/content.cpp
Normal file
256
src/anm2/content.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#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
|
||||
{
|
||||
Content::Content(XMLElement* element)
|
||||
{
|
||||
int id{};
|
||||
|
||||
if (auto spritesheetsElement = element->FirstChildElement("Spritesheets"))
|
||||
for (auto child = spritesheetsElement->FirstChildElement("Spritesheet"); child;
|
||||
child = child->NextSiblingElement("Spritesheet"))
|
||||
spritesheets[id] = Spritesheet(child, id);
|
||||
|
||||
if (auto layersElement = element->FirstChildElement("Layers"))
|
||||
for (auto child = layersElement->FirstChildElement("Layer"); child; child = child->NextSiblingElement("Layer"))
|
||||
layers[id] = Layer(child, id);
|
||||
|
||||
if (auto nullsElement = element->FirstChildElement("Nulls"))
|
||||
for (auto child = nullsElement->FirstChildElement("Null"); child; child = child->NextSiblingElement("Null"))
|
||||
nulls[id] = Null(child, id);
|
||||
|
||||
if (auto eventsElement = element->FirstChildElement("Events"))
|
||||
for (auto child = eventsElement->FirstChildElement("Event"); child; child = child->NextSiblingElement("Event"))
|
||||
events[id] = Event(child, id);
|
||||
}
|
||||
|
||||
void Content::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
auto element = document.NewElement("Content");
|
||||
|
||||
auto spritesheetsElement = document.NewElement("Spritesheets");
|
||||
for (auto& [id, spritesheet] : spritesheets)
|
||||
spritesheet.serialize(document, spritesheetsElement, id);
|
||||
element->InsertEndChild(spritesheetsElement);
|
||||
|
||||
auto layersElement = document.NewElement("Layers");
|
||||
for (auto& [id, layer] : layers)
|
||||
layer.serialize(document, layersElement, id);
|
||||
element->InsertEndChild(layersElement);
|
||||
|
||||
auto nullsElement = document.NewElement("Nulls");
|
||||
for (auto& [id, null] : nulls)
|
||||
null.serialize(document, nullsElement, id);
|
||||
element->InsertEndChild(nullsElement);
|
||||
|
||||
auto eventsElement = document.NewElement("Events");
|
||||
for (auto& [id, event] : events)
|
||||
event.serialize(document, eventsElement, id);
|
||||
element->InsertEndChild(eventsElement);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
46
src/anm2/content.h
Normal file
46
src/anm2/content.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "event.h"
|
||||
#include "layer.h"
|
||||
#include "null.h"
|
||||
#include "sound.h"
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
struct Content
|
||||
{
|
||||
std::map<int, Spritesheet> spritesheets{};
|
||||
std::map<int, Layer> layers{};
|
||||
std::map<int, Null> nulls{};
|
||||
std::map<int, Event> events{};
|
||||
std::map<int, Sound> sounds{};
|
||||
|
||||
Content() = default;
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
36
src/anm2/event.cpp
Normal file
36
src/anm2/event.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "event.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Event::Event(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
}
|
||||
|
||||
XMLElement* Event::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Event");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
void Event::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Event::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
20
src/anm2/event.h
Normal file
20
src/anm2/event.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
class Event
|
||||
{
|
||||
public:
|
||||
std::string name{"New Event"};
|
||||
int soundID{};
|
||||
|
||||
Event() = default;
|
||||
Event(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
126
src/anm2/frame.cpp
Normal file
126
src/anm2/frame.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "frame.h"
|
||||
|
||||
#include "math_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Frame::Frame(XMLElement* element, Type type)
|
||||
{
|
||||
if (type != TRIGGER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPosition", &position.x);
|
||||
element->QueryFloatAttribute("YPosition", &position.y);
|
||||
if (type == LAYER)
|
||||
{
|
||||
element->QueryFloatAttribute("XPivot", &pivot.x);
|
||||
element->QueryFloatAttribute("YPivot", &pivot.y);
|
||||
element->QueryFloatAttribute("XCrop", &crop.x);
|
||||
element->QueryFloatAttribute("YCrop", &crop.y);
|
||||
element->QueryFloatAttribute("Width", &size.x);
|
||||
element->QueryFloatAttribute("Height", &size.y);
|
||||
}
|
||||
element->QueryFloatAttribute("XScale", &scale.x);
|
||||
element->QueryFloatAttribute("YScale", &scale.y);
|
||||
element->QueryIntAttribute("Delay", &delay);
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
xml::query_color_attribute(element, "RedTint", tint.r);
|
||||
xml::query_color_attribute(element, "GreenTint", tint.g);
|
||||
xml::query_color_attribute(element, "BlueTint", tint.b);
|
||||
xml::query_color_attribute(element, "AlphaTint", tint.a);
|
||||
xml::query_color_attribute(element, "RedOffset", colorOffset.r);
|
||||
xml::query_color_attribute(element, "GreenOffset", colorOffset.g);
|
||||
xml::query_color_attribute(element, "BlueOffset", colorOffset.b);
|
||||
element->QueryFloatAttribute("Rotation", &rotation);
|
||||
element->QueryBoolAttribute("Interpolated", &isInterpolated);
|
||||
}
|
||||
else
|
||||
{
|
||||
element->QueryIntAttribute("EventId", &eventID);
|
||||
element->QueryIntAttribute("AtFrame", &atFrame);
|
||||
}
|
||||
}
|
||||
|
||||
XMLElement* Frame::to_element(XMLDocument& document, Type type)
|
||||
{
|
||||
auto element = document.NewElement(type == TRIGGER ? "Trigger" : "Frame");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ROOT:
|
||||
case NULL_:
|
||||
element->SetAttribute("XPosition", position.x);
|
||||
element->SetAttribute("YPosition", position.y);
|
||||
element->SetAttribute("Delay", delay);
|
||||
element->SetAttribute("Visible", isVisible);
|
||||
element->SetAttribute("XScale", scale.x);
|
||||
element->SetAttribute("YScale", scale.y);
|
||||
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
|
||||
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
|
||||
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
|
||||
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
|
||||
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
|
||||
element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
|
||||
element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
|
||||
element->SetAttribute("Rotation", rotation);
|
||||
element->SetAttribute("Interpolated", isInterpolated);
|
||||
break;
|
||||
case LAYER:
|
||||
element->SetAttribute("XPosition", position.x);
|
||||
element->SetAttribute("YPosition", position.y);
|
||||
element->SetAttribute("XPivot", pivot.x);
|
||||
element->SetAttribute("YPivot", pivot.y);
|
||||
element->SetAttribute("XCrop", crop.x);
|
||||
element->SetAttribute("YCrop", crop.y);
|
||||
element->SetAttribute("Width", size.x);
|
||||
element->SetAttribute("Height", size.y);
|
||||
element->SetAttribute("XScale", scale.x);
|
||||
element->SetAttribute("YScale", scale.y);
|
||||
element->SetAttribute("Delay", delay);
|
||||
element->SetAttribute("Visible", isVisible);
|
||||
element->SetAttribute("RedTint", math::float_to_uint8(tint.r));
|
||||
element->SetAttribute("GreenTint", math::float_to_uint8(tint.g));
|
||||
element->SetAttribute("BlueTint", math::float_to_uint8(tint.b));
|
||||
element->SetAttribute("AlphaTint", math::float_to_uint8(tint.a));
|
||||
element->SetAttribute("RedOffset", math::float_to_uint8(colorOffset.r));
|
||||
element->SetAttribute("GreenOffset", math::float_to_uint8(colorOffset.g));
|
||||
element->SetAttribute("BlueOffset", math::float_to_uint8(colorOffset.b));
|
||||
element->SetAttribute("Rotation", rotation);
|
||||
element->SetAttribute("Interpolated", isInterpolated);
|
||||
break;
|
||||
case TRIGGER:
|
||||
element->SetAttribute("EventId", eventID);
|
||||
element->SetAttribute("AtFrame", atFrame);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void Frame::serialize(XMLDocument& document, XMLElement* parent, Type type)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, type));
|
||||
}
|
||||
|
||||
std::string Frame::to_string(Type type)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, type));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
void Frame::shorten()
|
||||
{
|
||||
delay = glm::clamp(--delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
||||
}
|
||||
|
||||
void Frame::extend()
|
||||
{
|
||||
delay = glm::clamp(++delay, FRAME_DELAY_MIN, FRAME_DELAY_MAX);
|
||||
}
|
||||
}
|
||||
91
src/anm2/frame.h
Normal file
91
src/anm2/frame.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include <glm/glm/vec2.hpp>
|
||||
#include <glm/glm/vec3.hpp>
|
||||
#include <glm/glm/vec4.hpp>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FRAME_DELAY_MIN = 1;
|
||||
constexpr auto FRAME_DELAY_MAX = 100000;
|
||||
|
||||
#define TYPE_LIST \
|
||||
X(NONE, "None", "None") \
|
||||
X(ROOT, "Root", "RootAnimation") \
|
||||
X(LAYER, "Layer", "LayerAnimation") \
|
||||
X(NULL_, "Null", "NullAnimation") \
|
||||
X(TRIGGER, "Trigger", "Triggers")
|
||||
|
||||
enum Type
|
||||
{
|
||||
#define X(symbol, string, animationString) symbol,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr const char* TYPE_STRINGS[] = {
|
||||
#define X(symbol, string, animationString) string,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr const char* TYPE_ANIMATION_STRINGS[] = {
|
||||
#define X(symbol, string, animationString) animationString,
|
||||
TYPE_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum ChangeType
|
||||
{
|
||||
ADD,
|
||||
SUBTRACT,
|
||||
ADJUST
|
||||
};
|
||||
|
||||
#define MEMBERS \
|
||||
X(isVisible, bool, true) \
|
||||
X(isInterpolated, bool, false) \
|
||||
X(rotation, float, 0.0f) \
|
||||
X(delay, int, FRAME_DELAY_MIN) \
|
||||
X(atFrame, int, -1) \
|
||||
X(eventID, int, -1) \
|
||||
X(pivot, glm::vec2, {}) \
|
||||
X(crop, glm::vec2, {}) \
|
||||
X(position, glm::vec2, {}) \
|
||||
X(size, glm::vec2, {}) \
|
||||
X(scale, glm::vec2, glm::vec2(100.0f)) \
|
||||
X(colorOffset, glm::vec3, types::color::TRANSPARENT) \
|
||||
X(tint, glm::vec4, types::color::WHITE)
|
||||
|
||||
class Frame
|
||||
{
|
||||
public:
|
||||
#define X(name, type, ...) type name = __VA_ARGS__;
|
||||
MEMBERS
|
||||
#undef X
|
||||
|
||||
Frame() = default;
|
||||
Frame(tinyxml2::XMLElement*, Type);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type);
|
||||
std::string to_string(Type type);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type);
|
||||
void shorten();
|
||||
void extend();
|
||||
};
|
||||
|
||||
struct FrameChange
|
||||
{
|
||||
#define X(name, type, ...) std::optional<type> name{};
|
||||
MEMBERS
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef MEMBERS
|
||||
|
||||
}
|
||||
40
src/anm2/info.cpp
Normal file
40
src/anm2/info.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "info.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Info::Info(XMLElement* element)
|
||||
{
|
||||
if (!element) return;
|
||||
xml::query_string_attribute(element, "CreatedBy", &createdBy);
|
||||
xml::query_string_attribute(element, "CreatedOn", &createdOn);
|
||||
element->QueryIntAttribute("Fps", &fps);
|
||||
element->QueryIntAttribute("Version", &version);
|
||||
}
|
||||
|
||||
XMLElement* Info::to_element(XMLDocument& document)
|
||||
{
|
||||
auto element = document.NewElement("Info");
|
||||
element->SetAttribute("CreatedBy", createdBy.c_str());
|
||||
element->SetAttribute("CreatedOn", createdOn.c_str());
|
||||
element->SetAttribute("Fps", fps);
|
||||
element->SetAttribute("Version", version);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Info::serialize(XMLDocument& document, XMLElement* parent)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document));
|
||||
}
|
||||
|
||||
std::string Info::to_string()
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
25
src/anm2/info.h
Normal file
25
src/anm2/info.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto FPS_MIN = 1;
|
||||
constexpr auto FPS_MAX = 120;
|
||||
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
std::string createdBy{"robot"};
|
||||
std::string createdOn{};
|
||||
int fps = 30;
|
||||
int version{};
|
||||
|
||||
Info() = default;
|
||||
Info(tinyxml2::XMLElement*);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument& document);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*);
|
||||
std::string to_string();
|
||||
};
|
||||
}
|
||||
200
src/anm2/item.cpp
Normal file
200
src/anm2/item.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
#include "item.h"
|
||||
#include <ranges>
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Item::Item(XMLElement* element, Type type, int* id)
|
||||
{
|
||||
if (type == LAYER && id) element->QueryIntAttribute("LayerId", id);
|
||||
if (type == NULL_ && id) element->QueryIntAttribute("NullId", id);
|
||||
|
||||
element->QueryBoolAttribute("Visible", &isVisible);
|
||||
|
||||
for (auto child = type == TRIGGER ? element->FirstChildElement("Trigger") : element->FirstChildElement("Frame");
|
||||
child; child = type == TRIGGER ? child->NextSiblingElement("Trigger") : child->NextSiblingElement("Frame"))
|
||||
frames.push_back(Frame(child, type));
|
||||
}
|
||||
|
||||
XMLElement* Item::to_element(XMLDocument& document, Type type, int id)
|
||||
{
|
||||
auto element = document.NewElement(TYPE_ANIMATION_STRINGS[type]);
|
||||
|
||||
if (type == LAYER) element->SetAttribute("LayerId", id);
|
||||
if (type == NULL_) element->SetAttribute("NullId", id);
|
||||
if (type == LAYER || type == NULL_) element->SetAttribute("Visible", isVisible);
|
||||
|
||||
for (auto& frame : frames)
|
||||
frame.serialize(document, element, type);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void Item::serialize(XMLDocument& document, XMLElement* parent, Type type, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, type, id));
|
||||
}
|
||||
|
||||
std::string Item::to_string(Type type, int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, type, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
int Item::length(Type type)
|
||||
{
|
||||
int length{};
|
||||
|
||||
if (type == TRIGGER)
|
||||
for (auto& frame : frames)
|
||||
length = frame.atFrame > length ? frame.atFrame : length;
|
||||
else
|
||||
for (auto& frame : frames)
|
||||
length += frame.delay;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
Frame Item::frame_generate(float time, Type type)
|
||||
{
|
||||
Frame frame{};
|
||||
frame.isVisible = false;
|
||||
|
||||
if (frames.empty()) return frame;
|
||||
|
||||
Frame* frameNext = nullptr;
|
||||
int delayCurrent = 0;
|
||||
int delayNext = 0;
|
||||
|
||||
for (auto [i, iFrame] : std::views::enumerate(frames))
|
||||
{
|
||||
if (type == TRIGGER)
|
||||
{
|
||||
if ((int)time == iFrame.atFrame)
|
||||
{
|
||||
frame = iFrame;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
frame = iFrame;
|
||||
|
||||
delayNext += frame.delay;
|
||||
|
||||
if (time >= delayCurrent && time < delayNext)
|
||||
{
|
||||
if (i + 1 < (int)frames.size())
|
||||
frameNext = &frames[i + 1];
|
||||
else
|
||||
frameNext = nullptr;
|
||||
break;
|
||||
}
|
||||
|
||||
delayCurrent += frame.delay;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != TRIGGER && frame.isInterpolated && frameNext && frame.delay > 1)
|
||||
{
|
||||
auto interpolation = (time - delayCurrent) / (delayNext - delayCurrent);
|
||||
|
||||
frame.rotation = glm::mix(frame.rotation, frameNext->rotation, interpolation);
|
||||
frame.position = glm::mix(frame.position, frameNext->position, interpolation);
|
||||
frame.scale = glm::mix(frame.scale, frameNext->scale, interpolation);
|
||||
frame.colorOffset = glm::mix(frame.colorOffset, frameNext->colorOffset, interpolation);
|
||||
frame.tint = glm::mix(frame.tint, frameNext->tint, interpolation);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Item::frames_change(anm2::FrameChange& change, ChangeType type, int start, int numberFrames)
|
||||
{
|
||||
auto useStart = numberFrames > -1 ? start : 0;
|
||||
auto end = numberFrames > -1 ? start + numberFrames : (int)frames.size();
|
||||
end = glm::clamp(end, start, (int)frames.size());
|
||||
|
||||
for (int i = useStart; i < end; i++)
|
||||
{
|
||||
Frame& frame = frames[i];
|
||||
|
||||
if (change.isVisible) frame.isVisible = *change.isVisible;
|
||||
if (change.isInterpolated) frame.isInterpolated = *change.isInterpolated;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ADJUST:
|
||||
if (change.rotation) frame.rotation = *change.rotation;
|
||||
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, *change.delay);
|
||||
if (change.crop) frame.crop = *change.crop;
|
||||
if (change.pivot) frame.pivot = *change.pivot;
|
||||
if (change.position) frame.position = *change.position;
|
||||
if (change.size) frame.size = *change.size;
|
||||
if (change.scale) frame.scale = *change.scale;
|
||||
if (change.colorOffset) frame.colorOffset = glm::clamp(*change.colorOffset, 0.0f, 1.0f);
|
||||
if (change.tint) frame.tint = glm::clamp(*change.tint, 0.0f, 1.0f);
|
||||
break;
|
||||
|
||||
case ADD:
|
||||
if (change.rotation) frame.rotation += *change.rotation;
|
||||
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay + *change.delay);
|
||||
if (change.crop) frame.crop += *change.crop;
|
||||
if (change.pivot) frame.pivot += *change.pivot;
|
||||
if (change.position) frame.position += *change.position;
|
||||
if (change.size) frame.size += *change.size;
|
||||
if (change.scale) frame.scale += *change.scale;
|
||||
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset + *change.colorOffset, 0.0f, 1.0f);
|
||||
if (change.tint) frame.tint = glm::clamp(frame.tint + *change.tint, 0.0f, 1.0f);
|
||||
break;
|
||||
|
||||
case SUBTRACT:
|
||||
if (change.rotation) frame.rotation -= *change.rotation;
|
||||
if (change.delay) frame.delay = std::max(FRAME_DELAY_MIN, frame.delay - *change.delay);
|
||||
if (change.crop) frame.crop -= *change.crop;
|
||||
if (change.pivot) frame.pivot -= *change.pivot;
|
||||
if (change.position) frame.position -= *change.position;
|
||||
if (change.size) frame.size -= *change.size;
|
||||
if (change.scale) frame.scale -= *change.scale;
|
||||
if (change.colorOffset) frame.colorOffset = glm::clamp(frame.colorOffset - *change.colorOffset, 0.0f, 1.0f);
|
||||
if (change.tint) frame.tint = glm::clamp(frame.tint - *change.tint, 0.0f, 1.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Item::frames_deserialize(const std::string& string, Type type, int start, std::set<int>& indices,
|
||||
std::string* errorString)
|
||||
{
|
||||
XMLDocument document{};
|
||||
|
||||
if (document.Parse(string.c_str()) == XML_SUCCESS)
|
||||
{
|
||||
if (!document.FirstChildElement("Frame"))
|
||||
{
|
||||
if (errorString) *errorString = "No valid frame(s).";
|
||||
return false;
|
||||
}
|
||||
|
||||
int count{};
|
||||
for (auto element = document.FirstChildElement("Frame"); element; element = element->NextSiblingElement("Frame"))
|
||||
{
|
||||
auto index = start + count;
|
||||
frames.insert(frames.begin() + start + count, Frame(element, type));
|
||||
indices.insert(index);
|
||||
count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
*errorString = document.ErrorStr();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
26
src/anm2/item.h
Normal file
26
src/anm2/item.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "frame.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
std::vector<Frame> frames{};
|
||||
bool isVisible{true};
|
||||
|
||||
Item() = default;
|
||||
Item(tinyxml2::XMLElement*, Type, int* = nullptr);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, Type, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, Type, int = -1);
|
||||
std::string to_string(Type, int = -1);
|
||||
int length(Type);
|
||||
Frame frame_generate(float, Type);
|
||||
void frames_change(anm2::FrameChange&, ChangeType, int, int = 0);
|
||||
bool frames_deserialize(const std::string&, Type, int, std::set<int>&, std::string*);
|
||||
};
|
||||
}
|
||||
39
src/anm2/layer.cpp
Normal file
39
src/anm2/layer.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "layer.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Layer::Layer(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryIntAttribute("SpritesheetId", &spritesheetID);
|
||||
}
|
||||
|
||||
XMLElement* Layer::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Layer");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
element->SetAttribute("SpritesheetId", spritesheetID);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Layer::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Layer::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
}
|
||||
22
src/anm2/layer.h
Normal file
22
src/anm2/layer.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
|
||||
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::string name{"New Layer"};
|
||||
int spritesheetID{};
|
||||
|
||||
Layer() = default;
|
||||
Layer(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
38
src/anm2/null.cpp
Normal file
38
src/anm2/null.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "null.h"
|
||||
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Null::Null(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_string_attribute(element, "Name", &name);
|
||||
element->QueryBoolAttribute("ShowRect", &isShowRect);
|
||||
}
|
||||
|
||||
XMLElement* Null::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Null");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Name", name.c_str());
|
||||
if (isShowRect) element->SetAttribute("ShowRect", isShowRect);
|
||||
return element;
|
||||
}
|
||||
|
||||
void Null::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Null::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
22
src/anm2/null.h
Normal file
22
src/anm2/null.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto NULL_FORMAT = "#{} {}";
|
||||
|
||||
class Null
|
||||
{
|
||||
public:
|
||||
std::string name{"New Null"};
|
||||
bool isShowRect{};
|
||||
|
||||
Null() = default;
|
||||
Null(tinyxml2::XMLElement*, int&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument& document, int id);
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
59
src/anm2/sound.cpp
Normal file
59
src/anm2/sound.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "sound.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Sound::Sound(const Sound& other) : path(other.path)
|
||||
{
|
||||
audio = path.empty() ? Audio() : Audio(path.c_str());
|
||||
}
|
||||
|
||||
Sound& Sound::operator=(const Sound& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
path = other.path;
|
||||
audio = path.empty() ? Audio() : Audio(path.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
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);
|
||||
audio = Audio(this->path.c_str());
|
||||
}
|
||||
|
||||
Sound::Sound(XMLElement* element, int& id)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
XMLElement* Sound::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Sound");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Path", path.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
std::string Sound::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
}
|
||||
30
src/anm2/sound.h
Normal file
30
src/anm2/sound.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto SOUND_FORMAT = "#{} {}";
|
||||
constexpr auto SOUND_FORMAT_C = "#%d %s";
|
||||
|
||||
class Sound
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Audio audio{};
|
||||
|
||||
Sound() = default;
|
||||
Sound(Sound&&) noexcept = default;
|
||||
Sound& operator=(Sound&&) noexcept = default;
|
||||
|
||||
Sound(const Sound&);
|
||||
Sound& operator=(const Sound&);
|
||||
Sound(tinyxml2::XMLElement*, int&);
|
||||
Sound(const std::string&, const std::string&);
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int);
|
||||
};
|
||||
}
|
||||
70
src/anm2/spritesheet.cpp
Normal file
70
src/anm2/spritesheet.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "spritesheet.h"
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "string_.h"
|
||||
#include "xml_.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
Spritesheet::Spritesheet(XMLElement* element, int& id)
|
||||
{
|
||||
if (!element) return;
|
||||
element->QueryIntAttribute("Id", &id);
|
||||
xml::query_path_attribute(element, "Path", &path);
|
||||
// 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);
|
||||
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);
|
||||
texture = Texture(this->path);
|
||||
}
|
||||
|
||||
XMLElement* Spritesheet::to_element(XMLDocument& document, int id)
|
||||
{
|
||||
auto element = document.NewElement("Spritesheet");
|
||||
element->SetAttribute("Id", id);
|
||||
element->SetAttribute("Path", path.c_str());
|
||||
return element;
|
||||
}
|
||||
|
||||
void Spritesheet::serialize(XMLDocument& document, XMLElement* parent, int id)
|
||||
{
|
||||
parent->InsertEndChild(to_element(document, id));
|
||||
}
|
||||
|
||||
std::string Spritesheet::to_string(int id)
|
||||
{
|
||||
XMLDocument document{};
|
||||
document.InsertEndChild(to_element(document, id));
|
||||
return xml::document_to_string(document);
|
||||
}
|
||||
|
||||
bool Spritesheet::save(const std::string& directory, const std::string& path)
|
||||
{
|
||||
filesystem::WorkingDirectory workingDirectory(directory);
|
||||
this->path = !path.empty() ? std::filesystem::relative(path).string() : this->path.string();
|
||||
return texture.write_png(this->path);
|
||||
}
|
||||
|
||||
void Spritesheet::reload(const std::string& directory)
|
||||
{
|
||||
*this = Spritesheet(directory, this->path);
|
||||
}
|
||||
|
||||
bool Spritesheet::is_valid()
|
||||
{
|
||||
return texture.is_valid();
|
||||
}
|
||||
|
||||
}
|
||||
30
src/anm2/spritesheet.h
Normal file
30
src/anm2/spritesheet.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <tinyxml2/tinyxml2.h>
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
namespace anm2ed::anm2
|
||||
{
|
||||
constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
|
||||
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
|
||||
|
||||
class Spritesheet
|
||||
{
|
||||
public:
|
||||
std::filesystem::path path{};
|
||||
resource::Texture texture;
|
||||
|
||||
Spritesheet() = default;
|
||||
Spritesheet(tinyxml2::XMLElement*, int&);
|
||||
Spritesheet(const std::string&, const std::string& = {});
|
||||
tinyxml2::XMLElement* to_element(tinyxml2::XMLDocument&, int);
|
||||
std::string to_string(int id);
|
||||
bool save(const std::string&, const std::string& = {});
|
||||
void serialize(tinyxml2::XMLDocument&, tinyxml2::XMLElement*, int);
|
||||
void reload(const std::string&);
|
||||
bool is_valid();
|
||||
};
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
#include "canvas.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "math.h"
|
||||
#include "math_.h"
|
||||
#include "texture.h"
|
||||
|
||||
using namespace glm;
|
||||
using namespace anm2ed::shader;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::canvas;
|
||||
|
||||
namespace anm2ed::canvas
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr float AXIS_VERTICES[] = {-1.0f, 0.0f, 1.0f, 0.0f};
|
||||
constexpr float GRID_VERTICES[] = {-1.f, -1.f, 0.f, 0.f, 3.f, -1.f, 2.f, 0.f, -1.f, 3.f, 0.f, 2.f};
|
||||
@@ -305,7 +306,7 @@ namespace anm2ed::canvas
|
||||
void Canvas::zoom_set(float& zoom, vec2& pan, vec2 focus, float step)
|
||||
{
|
||||
auto zoomFactor = math::percent_to_unit(zoom);
|
||||
float newZoom = glm::clamp(math::round_nearest_multiple(zoom + step, step), canvas::ZOOM_MIN, canvas::ZOOM_MAX);
|
||||
float newZoom = glm::clamp(math::round_nearest_multiple(zoom + step, step), ZOOM_MIN, ZOOM_MAX);
|
||||
if (newZoom != zoom)
|
||||
{
|
||||
float newZoomFactor = math::percent_to_unit(newZoom);
|
||||
@@ -347,4 +348,4 @@ namespace anm2ed::canvas
|
||||
pan = -rectCenter * fitScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/canvas.h
16
src/canvas.h
@@ -20,7 +20,10 @@ namespace anm2ed::canvas
|
||||
|
||||
constexpr auto STEP = 1.0f;
|
||||
constexpr auto STEP_FAST = 5.0f;
|
||||
}
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
class Canvas
|
||||
{
|
||||
public:
|
||||
@@ -48,13 +51,14 @@ namespace anm2ed::canvas
|
||||
void size_set(glm::vec2);
|
||||
glm::vec4 pixel_read(glm::vec2, glm::vec2);
|
||||
glm::mat4 transform_get(float = 100.0f, glm::vec2 = {});
|
||||
void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
|
||||
void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
|
||||
void axes_render(resource::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
|
||||
void grid_render(resource::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
|
||||
glm::vec4 = glm::vec4(1.0f));
|
||||
void texture_render(shader::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
|
||||
float* = (float*)TEXTURE_VERTICES);
|
||||
void rect_render(shader::Shader&, const glm::mat4&, const glm::mat4&, glm::vec4 = glm::vec4(1.0f),
|
||||
float dashLength = DASH_LENGTH, float dashGap = DASH_GAP, float dashOffset = DASH_OFFSET);
|
||||
void texture_render(resource::Shader&, GLuint&, glm::mat4&, glm::vec4 = glm::vec4(1.0f), glm::vec3 = {},
|
||||
float* = (float*)canvas::TEXTURE_VERTICES);
|
||||
void rect_render(resource::Shader&, const glm::mat4&, const glm::mat4&, glm::vec4 = glm::vec4(1.0f),
|
||||
float dashLength = canvas::DASH_LENGTH, float dashGap = canvas::DASH_GAP,
|
||||
float dashOffset = canvas::DASH_OFFSET);
|
||||
void viewport_set();
|
||||
void clear(glm::vec4&);
|
||||
void bind();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace anm2ed::clipboard
|
||||
namespace anm2ed
|
||||
{
|
||||
std::string Clipboard::get()
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace anm2ed::clipboard
|
||||
namespace anm2ed
|
||||
{
|
||||
class Clipboard
|
||||
{
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
namespace anm2ed::dialog
|
||||
{
|
||||
|
||||
constexpr SDL_DialogFileFilter FILE_FILTER_ANM2[] = {{"Anm2 file", "anm2;xml"}};
|
||||
constexpr SDL_DialogFileFilter FILE_FILTER_SPRITESHEET[] = {{"PNG image", "png"}};
|
||||
|
||||
void callback(void* userData, const char* const* filelist, int filter)
|
||||
{
|
||||
auto self = (Dialog*)(userData);
|
||||
@@ -24,8 +20,12 @@ namespace anm2ed::dialog
|
||||
else
|
||||
self->selectedFilter = -1;
|
||||
}
|
||||
}
|
||||
|
||||
Dialog::Dialog() = default;
|
||||
using namespace anm2ed::dialog;
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
|
||||
Dialog::Dialog(SDL_Window* window)
|
||||
{
|
||||
@@ -33,36 +33,24 @@ namespace anm2ed::dialog
|
||||
this->window = window;
|
||||
}
|
||||
|
||||
void Dialog::anm2_new()
|
||||
void Dialog::file_open(dialog::Type type)
|
||||
{
|
||||
SDL_ShowSaveFileDialog(callback, this, window, FILE_FILTER_ANM2, std::size(FILE_FILTER_ANM2), nullptr);
|
||||
type = ANM2_NEW;
|
||||
SDL_ShowOpenFileDialog(callback, this, window, FILTERS[TYPE_FILTERS[type]], std::size(FILTERS[TYPE_FILTERS[type]]),
|
||||
nullptr, false);
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void Dialog::anm2_open()
|
||||
void Dialog::file_save(dialog::Type type)
|
||||
{
|
||||
SDL_ShowOpenFileDialog(callback, this, window, FILE_FILTER_ANM2, std::size(FILE_FILTER_ANM2), nullptr, false);
|
||||
type = ANM2_OPEN;
|
||||
SDL_ShowSaveFileDialog(callback, this, window, FILTERS[TYPE_FILTERS[type]], std::size(FILTERS[TYPE_FILTERS[type]]),
|
||||
nullptr);
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void Dialog::anm2_save()
|
||||
void Dialog::folder_open(dialog::Type type)
|
||||
{
|
||||
SDL_ShowSaveFileDialog(callback, this, window, FILE_FILTER_ANM2, std::size(FILE_FILTER_ANM2), nullptr);
|
||||
type = ANM2_SAVE;
|
||||
}
|
||||
|
||||
void Dialog::spritesheet_open()
|
||||
{
|
||||
SDL_ShowOpenFileDialog(callback, this, window, FILE_FILTER_SPRITESHEET, std::size(FILE_FILTER_SPRITESHEET), nullptr,
|
||||
false);
|
||||
type = SPRITESHEET_OPEN;
|
||||
}
|
||||
|
||||
void Dialog::spritesheet_replace()
|
||||
{
|
||||
SDL_ShowOpenFileDialog(callback, this, window, FILE_FILTER_SPRITESHEET, std::size(FILE_FILTER_SPRITESHEET), nullptr,
|
||||
false);
|
||||
type = SPRITESHEET_REPLACE;
|
||||
SDL_ShowOpenFolderDialog(callback, this, window, nullptr, false);
|
||||
this->type = type;
|
||||
}
|
||||
|
||||
void Dialog::file_explorer_open(const std::string& path)
|
||||
@@ -79,8 +67,16 @@ namespace anm2ed::dialog
|
||||
*this = Dialog(this->window);
|
||||
}
|
||||
|
||||
bool Dialog::is_selected_file(Type type)
|
||||
bool Dialog::is_selected(dialog::Type type)
|
||||
{
|
||||
return this->type == type && !path.empty();
|
||||
}
|
||||
|
||||
void Dialog::set_string_to_selected_path(std::string& string, dialog::Type type)
|
||||
{
|
||||
if (type == NONE) return;
|
||||
if (!is_selected(type)) return;
|
||||
string = path;
|
||||
reset();
|
||||
}
|
||||
};
|
||||
|
||||
86
src/dialog.h
86
src/dialog.h
@@ -6,34 +6,88 @@
|
||||
|
||||
namespace anm2ed::dialog
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
#define EXECUTABLE_FILTER {"Executable", "exe"}
|
||||
#else
|
||||
#define EXECUTABLE_FILTER \
|
||||
{ \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define FILTER_LIST \
|
||||
X(NO_FILTER, {}) \
|
||||
X(ANM2, {"Anm2 file", "anm2;xml"}) \
|
||||
X(PNG, {"PNG image", "png"}) \
|
||||
X(SOUND, {"WAV file;OGG file", "wav;ogg"}) \
|
||||
X(GIF, {"GIF image", "gif"}) \
|
||||
X(WEBM, {"WebM video", "webm"}) \
|
||||
X(MP4, {"MP4 video", "MP4"}) \
|
||||
X(EXECUTABLE, EXECUTABLE_FILTER)
|
||||
|
||||
enum Filter
|
||||
{
|
||||
#define X(symbol, ...) symbol,
|
||||
FILTER_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr SDL_DialogFileFilter FILTERS[][1] = {
|
||||
#define X(symbol, ...) {__VA_ARGS__},
|
||||
FILTER_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef FILTER_LIST
|
||||
|
||||
#define DIALOG_LIST \
|
||||
X(NONE, NO_FILTER) \
|
||||
X(ANM2_NEW, ANM2) \
|
||||
X(ANM2_OPEN, ANM2) \
|
||||
X(ANM2_SAVE, ANM2) \
|
||||
X(SOUND_OPEN, SOUND) \
|
||||
X(SPRITESHEET_OPEN, PNG) \
|
||||
X(SPRITESHEET_REPLACE, PNG) \
|
||||
X(FFMPEG_PATH_SET, EXECUTABLE) \
|
||||
X(PNG_DIRECTORY_SET, NO_FILTER) \
|
||||
X(GIF_PATH_SET, GIF) \
|
||||
X(WEBM_PATH_SET, WEBM) \
|
||||
X(MP4_PATH_SET, MP4)
|
||||
|
||||
enum Type
|
||||
{
|
||||
NONE,
|
||||
ANM2_NEW,
|
||||
ANM2_OPEN,
|
||||
ANM2_SAVE,
|
||||
SPRITESHEET_OPEN,
|
||||
SPRITESHEET_REPLACE
|
||||
#define X(symbol, filter) symbol,
|
||||
DIALOG_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr Filter TYPE_FILTERS[] = {
|
||||
#define X(symbol, filter) filter,
|
||||
DIALOG_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
#undef DIALOG_LIST
|
||||
}
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
|
||||
class Dialog
|
||||
{
|
||||
public:
|
||||
SDL_Window* window{};
|
||||
std::string path{};
|
||||
Type type{NONE};
|
||||
dialog::Type type{dialog::NONE};
|
||||
int selectedFilter{-1};
|
||||
int replaceID{-1};
|
||||
|
||||
Dialog();
|
||||
Dialog() = default;
|
||||
Dialog(SDL_Window*);
|
||||
void anm2_new();
|
||||
void anm2_open();
|
||||
void anm2_save();
|
||||
void spritesheet_open();
|
||||
void spritesheet_replace();
|
||||
void file_explorer_open(const std::string&);
|
||||
void file_open(dialog::Type type);
|
||||
void file_save(dialog::Type type);
|
||||
void folder_open(dialog::Type type);
|
||||
bool is_selected(dialog::Type type);
|
||||
void reset();
|
||||
bool is_selected_file(Type);
|
||||
void file_explorer_open(const std::string&);
|
||||
void set_string_to_selected_path(std::string& set, dialog::Type type);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "animation_preview.h"
|
||||
#include "animations.h"
|
||||
#include "documents.h"
|
||||
#include "events.h"
|
||||
#include "frame_properties.h"
|
||||
#include "layers.h"
|
||||
#include "nulls.h"
|
||||
#include "onionskin.h"
|
||||
#include "spritesheet_editor.h"
|
||||
#include "spritesheets.h"
|
||||
#include "taskbar.h"
|
||||
#include "timeline.h"
|
||||
#include "tools.h"
|
||||
#include "welcome.h"
|
||||
|
||||
namespace anm2ed::dockspace
|
||||
{
|
||||
class Dockspace
|
||||
{
|
||||
animation_preview::AnimationPreview animationPreview;
|
||||
animations::Animations animations;
|
||||
events::Events events;
|
||||
frame_properties::FrameProperties frameProperties;
|
||||
layers::Layers layers;
|
||||
nulls::Nulls nulls;
|
||||
onionskin::Onionskin onionskin;
|
||||
spritesheet_editor::SpritesheetEditor spritesheetEditor;
|
||||
spritesheets::Spritesheets spritesheets;
|
||||
timeline::Timeline timeline;
|
||||
tools::Tools tools;
|
||||
welcome::Welcome welcome;
|
||||
|
||||
public:
|
||||
void update(taskbar::Taskbar&, documents::Documents&, manager::Manager&, settings::Settings&, resources::Resources&,
|
||||
dialog::Dialog&, clipboard::Clipboard&);
|
||||
};
|
||||
}
|
||||
245
src/document.cpp
245
src/document.cpp
@@ -3,26 +3,24 @@
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
|
||||
#include "anm2.h"
|
||||
#include "filesystem.h"
|
||||
#include "filesystem_.h"
|
||||
#include "log.h"
|
||||
#include "map_.h"
|
||||
#include "toast.h"
|
||||
#include "util.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace anm2ed::anm2;
|
||||
using namespace anm2ed::filesystem;
|
||||
using namespace anm2ed::toast;
|
||||
using namespace anm2ed::imgui;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::log;
|
||||
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::document
|
||||
namespace anm2ed
|
||||
{
|
||||
Document::Document(const std::string& path, bool isNew, std::string* errorString)
|
||||
{
|
||||
if (!path_is_exist(path)) return;
|
||||
if (!filesystem::path_is_exist(path)) return;
|
||||
|
||||
if (isNew)
|
||||
anm2 = anm2::Anm2();
|
||||
@@ -34,7 +32,7 @@ namespace anm2ed::document
|
||||
|
||||
this->path = path;
|
||||
clean();
|
||||
change(change::ALL);
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
bool Document::save(const std::string& path, std::string* errorString)
|
||||
@@ -43,12 +41,12 @@ namespace anm2ed::document
|
||||
|
||||
if (anm2.serialize(this->path, errorString))
|
||||
{
|
||||
toasts.info(std::format("Saved document to: {}", path));
|
||||
toasts.info(std::format("Saved document to: {}", this->path.string()));
|
||||
clean();
|
||||
return true;
|
||||
}
|
||||
else if (errorString)
|
||||
toasts.warning(std::format("Could not save document to: {} ({})", path, *errorString));
|
||||
toasts.warning(std::format("Could not save document to: {} ({})", this->path.string(), *errorString));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -82,43 +80,76 @@ namespace anm2ed::document
|
||||
isForceDirty = false;
|
||||
}
|
||||
|
||||
void Document::change(change::Type type)
|
||||
void Document::change(ChangeType type)
|
||||
{
|
||||
hash_set();
|
||||
|
||||
auto layer_set = [&]() { unusedLayerIDs = anm2.layers_unused(); };
|
||||
auto null_set = [&]() { unusedNullIDs = anm2.nulls_unused(); };
|
||||
auto event_set = [&]() { unusedEventIDs = anm2.events_unused(); };
|
||||
auto spritesheet_set = [&]()
|
||||
auto layers_set = [&]() { unusedLayerIDs = anm2.layers_unused(); };
|
||||
auto nulls_set = [&]() { unusedNullIDs = 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());
|
||||
};
|
||||
|
||||
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 spritesheets_set = [&]()
|
||||
{
|
||||
unusedSpritesheetIDs = anm2.spritesheets_unused();
|
||||
spritesheetNames = anm2.spritesheet_names_get();
|
||||
spritesheetNamesCstr.clear();
|
||||
spritesheetNamesCStr.clear();
|
||||
for (auto& name : spritesheetNames)
|
||||
spritesheetNamesCstr.push_back(name.c_str());
|
||||
spritesheetNamesCStr.push_back(name.c_str());
|
||||
};
|
||||
|
||||
auto sounds_set = [&]()
|
||||
{
|
||||
unusedSoundIDs = anm2.sounds_unused();
|
||||
soundNames = anm2.sound_names_get();
|
||||
soundNamesCStr.clear();
|
||||
for (auto& name : soundNames)
|
||||
soundNamesCStr.push_back(name.c_str());
|
||||
};
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case change::LAYERS:
|
||||
layer_set();
|
||||
case LAYERS:
|
||||
layers_set();
|
||||
break;
|
||||
case change::NULLS:
|
||||
null_set();
|
||||
case NULLS:
|
||||
nulls_set();
|
||||
break;
|
||||
case change::EVENTS:
|
||||
event_set();
|
||||
case EVENTS:
|
||||
events_set();
|
||||
break;
|
||||
case change::SPRITESHEETS:
|
||||
spritesheet_set();
|
||||
case ANIMATIONS:
|
||||
animations_set();
|
||||
break;
|
||||
case change::ITEMS:
|
||||
case SPRITESHEETS:
|
||||
spritesheets_set();
|
||||
break;
|
||||
case change::ALL:
|
||||
layer_set();
|
||||
null_set();
|
||||
event_set();
|
||||
spritesheet_set();
|
||||
case SOUNDS:
|
||||
sounds_set();
|
||||
break;
|
||||
case ITEMS:
|
||||
break;
|
||||
case ALL:
|
||||
layers_set();
|
||||
nulls_set();
|
||||
events_set();
|
||||
animations_set();
|
||||
spritesheets_set();
|
||||
sounds_set();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -159,7 +190,7 @@ namespace anm2ed::document
|
||||
{
|
||||
snapshot("Bake Frames");
|
||||
anm2.bake(reference, interval, isRoundScale, isRoundRotation);
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frames_add(anm2::Item* item)
|
||||
@@ -188,7 +219,7 @@ namespace anm2ed::document
|
||||
snapshot("Delete Frames");
|
||||
item->frames.erase(item->frames.begin() + reference.frameIndex);
|
||||
reference.frameIndex = glm::max(-1, --reference.frameIndex);
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_crop_set(anm2::Frame* frame, vec2 crop)
|
||||
@@ -196,7 +227,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Crop");
|
||||
frame->crop = crop;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_size_set(anm2::Frame* frame, vec2 size)
|
||||
@@ -204,7 +235,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Size");
|
||||
frame->size = size;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_position_set(anm2::Frame* frame, vec2 position)
|
||||
@@ -212,7 +243,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Position");
|
||||
frame->position = position;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_pivot_set(anm2::Frame* frame, vec2 pivot)
|
||||
@@ -220,7 +251,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Pivot");
|
||||
frame->pivot = pivot;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_scale_set(anm2::Frame* frame, vec2 scale)
|
||||
@@ -228,7 +259,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Scale");
|
||||
frame->scale = scale;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_rotation_set(anm2::Frame* frame, float rotation)
|
||||
@@ -236,7 +267,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Rotation");
|
||||
frame->rotation = rotation;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_delay_set(anm2::Frame* frame, int delay)
|
||||
@@ -244,7 +275,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Delay");
|
||||
frame->delay = delay;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_tint_set(anm2::Frame* frame, vec4 tint)
|
||||
@@ -252,7 +283,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Tint");
|
||||
frame->tint = tint;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_color_offset_set(anm2::Frame* frame, vec3 colorOffset)
|
||||
@@ -260,7 +291,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Color Offset");
|
||||
frame->colorOffset = colorOffset;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_is_visible_set(anm2::Frame* frame, bool isVisible)
|
||||
@@ -268,7 +299,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Visibility");
|
||||
frame->isVisible = isVisible;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_is_interpolated_set(anm2::Frame* frame, bool isInterpolated)
|
||||
@@ -276,7 +307,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Interpolation");
|
||||
frame->isInterpolated = isInterpolated;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_flip_x(anm2::Frame* frame)
|
||||
@@ -284,7 +315,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Flip X");
|
||||
frame->scale.x = -frame->scale.x;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_flip_y(anm2::Frame* frame)
|
||||
@@ -292,7 +323,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Frame Flip Y");
|
||||
frame->scale.y = -frame->scale.y;
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_shorten()
|
||||
@@ -301,7 +332,7 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Shorten Frame");
|
||||
frame->shorten();
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frame_extend()
|
||||
@@ -310,10 +341,10 @@ namespace anm2ed::document
|
||||
if (!frame) return;
|
||||
snapshot("Extend Frame");
|
||||
frame->extend();
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frames_change(anm2::FrameChange& frameChange, frame_change::Type type, bool isFromSelectedFrame,
|
||||
void Document::frames_change(anm2::FrameChange& frameChange, anm2::ChangeType type, bool isFromSelectedFrame,
|
||||
int numberFrames)
|
||||
{
|
||||
auto item = item_get();
|
||||
@@ -321,7 +352,7 @@ namespace anm2ed::document
|
||||
snapshot("Change All Frame Properties");
|
||||
item->frames_change(frameChange, type, isFromSelectedFrame && frame_get() ? reference.frameIndex : 0,
|
||||
isFromSelectedFrame ? numberFrames : -1);
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
}
|
||||
|
||||
void Document::frames_deserialize(const std::string& string)
|
||||
@@ -333,7 +364,7 @@ namespace anm2ed::document
|
||||
std::string errorString{};
|
||||
auto start = reference.frameIndex + 1;
|
||||
if (item->frames_deserialize(string, reference.itemType, start, indices, &errorString))
|
||||
change(change::FRAMES);
|
||||
change(Document::FRAMES);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize frame(s): {}", errorString));
|
||||
}
|
||||
@@ -359,7 +390,7 @@ namespace anm2ed::document
|
||||
{
|
||||
spritesheetMultiSelect = {id};
|
||||
toasts.info(std::format("Initialized spritesheet #{}: {}", id, path));
|
||||
change(change::SPRITESHEETS);
|
||||
change(Document::SPRITESHEETS);
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to initialize spritesheet: {}", path));
|
||||
@@ -370,7 +401,7 @@ namespace anm2ed::document
|
||||
snapshot("Paste Spritesheet(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.spritesheets_deserialize(string, directory_get(), type, &errorString))
|
||||
change(change::SPRITESHEETS);
|
||||
change(Document::SPRITESHEETS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize spritesheet(s): {}", errorString));
|
||||
}
|
||||
@@ -380,7 +411,7 @@ namespace anm2ed::document
|
||||
snapshot("Paste Layer(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.layers_deserialize(string, type, &errorString))
|
||||
change(change::NULLS);
|
||||
change(Document::NULLS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize layer(s): {}", errorString));
|
||||
}
|
||||
@@ -400,7 +431,7 @@ namespace anm2ed::document
|
||||
anm2.content.layers[id] = layer;
|
||||
layersMultiSelect = {id};
|
||||
}
|
||||
change(change::LAYERS);
|
||||
change(Document::LAYERS);
|
||||
}
|
||||
|
||||
void Document::layers_remove_unused()
|
||||
@@ -408,7 +439,7 @@ namespace anm2ed::document
|
||||
snapshot("Remove Unused Layers");
|
||||
for (auto& id : unusedLayerIDs)
|
||||
anm2.content.layers.erase(id);
|
||||
change(change::LAYERS);
|
||||
change(Document::LAYERS);
|
||||
unusedLayerIDs.clear();
|
||||
}
|
||||
|
||||
@@ -427,14 +458,14 @@ namespace anm2ed::document
|
||||
anm2.content.nulls[id] = null;
|
||||
nullMultiSelect = {id};
|
||||
}
|
||||
change(change::NULLS);
|
||||
change(Document::NULLS);
|
||||
}
|
||||
|
||||
void Document::null_rect_toggle(anm2::Null& null)
|
||||
{
|
||||
snapshot("Null Rect");
|
||||
null.isShowRect = !null.isShowRect;
|
||||
change(change::NULLS);
|
||||
change(Document::NULLS);
|
||||
}
|
||||
|
||||
void Document::nulls_remove_unused()
|
||||
@@ -442,7 +473,7 @@ namespace anm2ed::document
|
||||
snapshot("Remove Unused Nulls");
|
||||
for (auto& id : unusedNullIDs)
|
||||
anm2.content.nulls.erase(id);
|
||||
change(change::NULLS);
|
||||
change(Document::NULLS);
|
||||
unusedNullIDs.clear();
|
||||
}
|
||||
|
||||
@@ -451,18 +482,27 @@ namespace anm2ed::document
|
||||
snapshot("Paste Null(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.nulls_deserialize(string, type, &errorString))
|
||||
change(change::NULLS);
|
||||
change(Document::NULLS);
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize null(s): {}", errorString));
|
||||
}
|
||||
|
||||
void Document::event_add()
|
||||
void Document::event_set(anm2::Event& event)
|
||||
{
|
||||
snapshot("Add Event");
|
||||
int id{};
|
||||
anm2.event_add(id);
|
||||
eventMultiSelect = {id};
|
||||
change(change::EVENTS);
|
||||
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()
|
||||
@@ -470,7 +510,7 @@ namespace anm2ed::document
|
||||
snapshot("Remove Unused Events");
|
||||
for (auto& id : unusedEventIDs)
|
||||
anm2.content.events.erase(id);
|
||||
change(change::EVENTS);
|
||||
change(Document::EVENTS);
|
||||
unusedEventIDs.clear();
|
||||
}
|
||||
|
||||
@@ -479,7 +519,40 @@ namespace anm2ed::document
|
||||
snapshot("Paste Event(s)");
|
||||
std::string errorString{};
|
||||
if (anm2.content.events_deserialize(string, type, &errorString))
|
||||
change(change::EVENTS);
|
||||
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));
|
||||
}
|
||||
@@ -498,7 +571,7 @@ namespace anm2ed::document
|
||||
|
||||
reference = addReference;
|
||||
|
||||
change(change::ITEMS);
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
void Document::item_remove(anm2::Animation* animation)
|
||||
@@ -507,7 +580,7 @@ namespace anm2ed::document
|
||||
snapshot("Remove Item");
|
||||
animation->item_remove(reference.itemType, reference.itemID);
|
||||
reference = {reference.animationIndex};
|
||||
change(change::ITEMS);
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
void Document::item_visible_toggle(anm2::Item* item)
|
||||
@@ -515,7 +588,7 @@ namespace anm2ed::document
|
||||
if (!item) return;
|
||||
snapshot("Item Visibility");
|
||||
item->isVisible = !item->isVisible;
|
||||
change(change::ITEMS);
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
anm2::Animation* Document::animation_get()
|
||||
@@ -527,7 +600,7 @@ namespace anm2ed::document
|
||||
{
|
||||
snapshot("Select Animation");
|
||||
reference = {index};
|
||||
change(change::ITEMS);
|
||||
change(Document::ITEMS);
|
||||
}
|
||||
|
||||
void Document::animation_add()
|
||||
@@ -549,7 +622,7 @@ namespace anm2ed::document
|
||||
anm2.animations.items.insert(anm2.animations.items.begin() + index, animation);
|
||||
animationMultiSelect = {index};
|
||||
reference = {index};
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animation_duplicate()
|
||||
@@ -563,14 +636,14 @@ namespace anm2ed::document
|
||||
animationMultiSelect.insert(++duplicatedEnd);
|
||||
animationMultiSelect.erase(id);
|
||||
}
|
||||
change(change::ANIMATIONS);
|
||||
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(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animations_remove()
|
||||
@@ -589,14 +662,14 @@ namespace anm2ed::document
|
||||
hoveredAnimation = -1;
|
||||
}
|
||||
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animation_default()
|
||||
{
|
||||
snapshot("Default Animation");
|
||||
anm2.animations.defaultAnimation = anm2.animations.items[*animationMultiSelect.begin()].name;
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animations_deserialize(const std::string& string)
|
||||
@@ -609,7 +682,7 @@ namespace anm2ed::document
|
||||
if (anm2.animations.animations_deserialize(string, start, indices, &errorString))
|
||||
{
|
||||
multiSelect = indices;
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
else
|
||||
toasts.error(std::format("Failed to deserialize animation(s): {}", errorString));
|
||||
@@ -624,7 +697,7 @@ namespace anm2ed::document
|
||||
|
||||
if (auto animation = animation_get()) animation->frameNum = animation->length();
|
||||
|
||||
change(change::ALL);
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
void Document::animations_merge_quick()
|
||||
@@ -648,7 +721,7 @@ namespace anm2ed::document
|
||||
|
||||
animationMultiSelect = {merged};
|
||||
reference = {merged};
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::animations_merge(merge::Type type, bool isDeleteAnimationsAfter)
|
||||
@@ -657,7 +730,7 @@ namespace anm2ed::document
|
||||
auto merged = anm2.animations.merge(mergeTarget, animationMergeMultiSelect, type, isDeleteAnimationsAfter);
|
||||
animationMultiSelect = {merged};
|
||||
reference = {merged};
|
||||
change(change::ANIMATIONS);
|
||||
change(Document::ANIMATIONS);
|
||||
}
|
||||
|
||||
void Document::snapshot(const std::string& message)
|
||||
@@ -669,22 +742,22 @@ namespace anm2ed::document
|
||||
{
|
||||
snapshots.undo(anm2, reference, message);
|
||||
toasts.info(std::format("Undo: {}", message));
|
||||
change(change::ALL);
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
void Document::redo()
|
||||
{
|
||||
toasts.info(std::format("Redo: {}", message));
|
||||
snapshots.redo(anm2, reference, message);
|
||||
change(change::ALL);
|
||||
change(Document::ALL);
|
||||
}
|
||||
|
||||
bool Document::is_undo()
|
||||
bool Document::is_able_to_undo()
|
||||
{
|
||||
return !snapshots.undoStack.is_empty();
|
||||
}
|
||||
|
||||
bool Document::is_redo()
|
||||
bool Document::is_able_to_redo()
|
||||
{
|
||||
return !snapshots.redoStack.is_empty();
|
||||
}
|
||||
|
||||
@@ -3,24 +3,38 @@
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include "anm2.h"
|
||||
#include "imgui.h"
|
||||
#include "anm2/anm2.h"
|
||||
#include "imgui_.h"
|
||||
#include "playback.h"
|
||||
#include "snapshots.h"
|
||||
#include "types.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace anm2ed::document
|
||||
namespace anm2ed
|
||||
{
|
||||
class Document
|
||||
{
|
||||
public:
|
||||
enum ChangeType
|
||||
{
|
||||
LAYERS,
|
||||
NULLS,
|
||||
SPRITESHEETS,
|
||||
EVENTS,
|
||||
ANIMATIONS,
|
||||
ITEMS,
|
||||
FRAMES,
|
||||
SOUNDS,
|
||||
ALL,
|
||||
COUNT
|
||||
};
|
||||
|
||||
std::filesystem::path path{};
|
||||
anm2::Anm2 anm2{};
|
||||
std::string message{};
|
||||
playback::Playback playback{};
|
||||
snapshots::Snapshots snapshots{};
|
||||
Playback playback{};
|
||||
Snapshots snapshots{};
|
||||
|
||||
float previewZoom{200};
|
||||
glm::vec2 previewPan{};
|
||||
@@ -34,6 +48,8 @@ namespace anm2ed::document
|
||||
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};
|
||||
|
||||
@@ -41,7 +57,7 @@ namespace anm2ed::document
|
||||
int hoveredSpritesheet{-1};
|
||||
std::set<int> unusedSpritesheetIDs{};
|
||||
std::vector<std::string> spritesheetNames{};
|
||||
std::vector<const char*> spritesheetNamesCstr{};
|
||||
std::vector<const char*> spritesheetNamesCStr{};
|
||||
imgui::MultiSelectStorage spritesheetMultiSelect;
|
||||
|
||||
int referenceLayer{-1};
|
||||
@@ -58,6 +74,15 @@ namespace anm2ed::document
|
||||
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{};
|
||||
|
||||
uint64_t hash{};
|
||||
uint64_t saveHash{};
|
||||
@@ -72,7 +97,7 @@ namespace anm2ed::document
|
||||
void hash_set();
|
||||
void clean();
|
||||
void on_change();
|
||||
void change(types::change::Type);
|
||||
void change(ChangeType);
|
||||
bool is_dirty();
|
||||
bool is_autosave_dirty();
|
||||
std::filesystem::path directory_get();
|
||||
@@ -81,7 +106,6 @@ namespace anm2ed::document
|
||||
|
||||
anm2::Frame* frame_get();
|
||||
void frames_add(anm2::Item* item);
|
||||
void frames_change();
|
||||
void frames_delete(anm2::Item* item);
|
||||
void frames_bake(int, bool, bool);
|
||||
void frame_crop_set(anm2::Frame*, glm::vec2);
|
||||
@@ -99,31 +123,35 @@ namespace anm2ed::document
|
||||
void frame_flip_y(anm2::Frame* frame);
|
||||
void frame_shorten();
|
||||
void frame_extend();
|
||||
void frames_change(anm2::FrameChange&, types::frame_change::Type, bool, int = -1);
|
||||
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* animation);
|
||||
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& layer);
|
||||
void layer_set(anm2::Layer&);
|
||||
void layers_remove_unused();
|
||||
void layers_deserialize(const std::string&, types::merge::Type);
|
||||
|
||||
void null_set(anm2::Null& null);
|
||||
void null_rect_toggle(anm2::Null& null);
|
||||
void 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_add();
|
||||
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();
|
||||
@@ -141,7 +169,19 @@ namespace anm2ed::document
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
bool is_undo();
|
||||
bool is_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) \
|
||||
body; \
|
||||
DOCUMENT_CHANGE(document, changeType) \
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
16
src/events.h
16
src/events.h
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::events
|
||||
{
|
||||
class Events
|
||||
{
|
||||
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
|
||||
};
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#include "frame_properties.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "math.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::math;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::frame_properties
|
||||
{
|
||||
|
||||
void FrameProperties::update(Manager& manager, Settings& settings)
|
||||
{
|
||||
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& type = reference.itemType;
|
||||
auto frame = document.frame_get();
|
||||
auto useFrame = frame ? *frame : anm2::Frame();
|
||||
|
||||
ImGui::BeginDisabled(!frame);
|
||||
{
|
||||
if (type == anm2::TRIGGER)
|
||||
{
|
||||
std::vector<std::string> eventNames{};
|
||||
for (auto& event : anm2.content.events | std::views::values)
|
||||
eventNames.emplace_back(event.name);
|
||||
|
||||
imgui::combo_strings("Event", frame ? &frame->eventID : &dummy_value<int>(), eventNames);
|
||||
ImGui::SetItemTooltip("%s", "Change the event this trigger uses.");
|
||||
ImGui::InputInt("At Frame", frame ? &frame->atFrame : &dummy_value<int>(), step::NORMAL, step::FAST,
|
||||
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0);
|
||||
ImGui::SetItemTooltip("%s", "Change the frame the trigger will be activated at.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Crop", frame ? value_ptr(useFrame.crop) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.crop) : ""))
|
||||
document.frame_crop_set(frame, useFrame.crop);
|
||||
ImGui::SetItemTooltip("%s", "Change the crop position the frame uses.");
|
||||
|
||||
if (ImGui::InputFloat2("Size", frame ? value_ptr(useFrame.size) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.size) : ""))
|
||||
document.frame_size_set(frame, useFrame.size);
|
||||
ImGui::SetItemTooltip("%s", "Change the size of the crop the frame uses.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Position", frame ? value_ptr(useFrame.position) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.position) : ""))
|
||||
document.frame_position_set(frame, useFrame.position);
|
||||
ImGui::SetItemTooltip("%s", "Change the position of the frame.");
|
||||
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Pivot", frame ? value_ptr(useFrame.pivot) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.pivot) : ""))
|
||||
document.frame_pivot_set(frame, useFrame.pivot);
|
||||
ImGui::SetItemTooltip("%s", "Change the pivot of the frame; i.e., where it is centered.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Scale", frame ? value_ptr(useFrame.scale) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.scale) : ""))
|
||||
document.frame_scale_set(frame, useFrame.scale);
|
||||
ImGui::SetItemTooltip("%s", "Change the scale of the frame, in percent.");
|
||||
|
||||
if (ImGui::InputFloat("Rotation", frame ? &useFrame.rotation : &dummy_value<float>(), step::NORMAL,
|
||||
step::FAST, frame ? float_format_get(useFrame.rotation) : ""))
|
||||
document.frame_rotation_set(frame, useFrame.rotation);
|
||||
ImGui::SetItemTooltip("%s", "Change the rotation of the frame.");
|
||||
|
||||
if (ImGui::InputInt("Duration", frame ? &useFrame.delay : &dummy_value<int>(), step::NORMAL, step::FAST,
|
||||
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
document.frame_delay_set(frame, useFrame.delay);
|
||||
ImGui::SetItemTooltip("%s", "Change how long the frame lasts.");
|
||||
|
||||
if (ImGui::ColorEdit4("Tint", frame ? value_ptr(useFrame.tint) : &dummy_value<float>()))
|
||||
document.frame_tint_set(frame, useFrame.tint);
|
||||
ImGui::SetItemTooltip("%s", "Change the tint of the frame.");
|
||||
|
||||
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
|
||||
document.frame_color_offset_set(frame, useFrame.colorOffset);
|
||||
ImGui::SetItemTooltip("%s", "Change the color added onto the frame.");
|
||||
|
||||
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
|
||||
document.frame_is_visible_set(frame, useFrame.isVisible);
|
||||
ImGui::SetItemTooltip("%s", "Toggle the frame's visibility.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Checkbox("Interpolated", frame ? &useFrame.isInterpolated : &dummy_value<bool>()))
|
||||
document.frame_is_interpolated_set(frame, useFrame.isInterpolated);
|
||||
ImGui::SetItemTooltip(
|
||||
"%s", "Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Flip X", widgetSize)) document.frame_flip_x(frame);
|
||||
ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame "
|
||||
"horizontally.\n(Note: the format does not support mirroring.)");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Flip Y", widgetSize)) document.frame_flip_y(frame);
|
||||
ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame "
|
||||
"vertically.\n(Note: the format does not support mirroring.)");
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,13 @@
|
||||
#include "dockspace.h"
|
||||
|
||||
#include "animations.h"
|
||||
#include "onionskin.h"
|
||||
#include "tools.h"
|
||||
|
||||
using namespace anm2ed::animations;
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::documents;
|
||||
using namespace anm2ed::playback;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::taskbar;
|
||||
using namespace anm2ed::welcome;
|
||||
|
||||
namespace anm2ed::dockspace
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Dockspace::tick(Manager& manager, Settings& settings)
|
||||
{
|
||||
if (auto document = manager.get(); document)
|
||||
if (settings.windowIsAnimationPreview) animationPreview.tick(manager, *document, settings);
|
||||
}
|
||||
|
||||
void Dockspace::update(Taskbar& taskbar, Documents& documents, Manager& manager, Settings& settings,
|
||||
Resources& resources, Dialog& dialog, Clipboard& clipboard)
|
||||
{
|
||||
@@ -44,6 +35,7 @@ namespace anm2ed::dockspace
|
||||
if (settings.windowIsLayers) layers.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsNulls) nulls.update(manager, settings, resources, clipboard);
|
||||
if (settings.windowIsOnionskin) onionskin.update(settings);
|
||||
if (settings.windowIsSounds) sounds.update(manager, settings, resources, dialog, clipboard);
|
||||
if (settings.windowIsSpritesheetEditor) spritesheetEditor.update(manager, settings, resources);
|
||||
if (settings.windowIsSpritesheets) spritesheets.update(manager, settings, resources, dialog, clipboard);
|
||||
if (settings.windowIsTimeline) timeline.update(manager, settings, resources, clipboard);
|
||||
41
src/imgui/dockspace.h
Normal file
41
src/imgui/dockspace.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "documents.h"
|
||||
#include "taskbar.h"
|
||||
#include "window/animation_preview.h"
|
||||
#include "window/animations.h"
|
||||
#include "window/events.h"
|
||||
#include "window/frame_properties.h"
|
||||
#include "window/layers.h"
|
||||
#include "window/nulls.h"
|
||||
#include "window/onionskin.h"
|
||||
#include "window/sounds.h"
|
||||
#include "window/spritesheet_editor.h"
|
||||
#include "window/spritesheets.h"
|
||||
#include "window/timeline.h"
|
||||
#include "window/tools.h"
|
||||
#include "window/welcome.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Dockspace
|
||||
{
|
||||
AnimationPreview animationPreview;
|
||||
Animations animations;
|
||||
Events events;
|
||||
FrameProperties frameProperties;
|
||||
Layers layers;
|
||||
Nulls nulls;
|
||||
Onionskin onionskin;
|
||||
SpritesheetEditor spritesheetEditor;
|
||||
Spritesheets spritesheets;
|
||||
Sounds sounds;
|
||||
Timeline timeline;
|
||||
Tools tools;
|
||||
Welcome welcome;
|
||||
|
||||
public:
|
||||
void tick(Manager&, Settings&);
|
||||
void update(Taskbar&, Documents&, Manager&, Settings&, Resources&, Dialog&, Clipboard&);
|
||||
};
|
||||
}
|
||||
@@ -2,16 +2,13 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "util.h"
|
||||
#include "time_.h"
|
||||
|
||||
using namespace anm2ed::taskbar;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::documents
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Documents::update(Taskbar& taskbar, Manager& manager, Settings& settings, Resources& resources, bool& isQuitting)
|
||||
{
|
||||
@@ -27,7 +24,7 @@ namespace anm2ed::documents
|
||||
auto isDirty = document.is_dirty() && document.is_autosave_dirty();
|
||||
document.lastAutosaveTime += ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (isDirty && document.lastAutosaveTime > time::SECOND_S) manager.autosave(document);
|
||||
if (isDirty && document.lastAutosaveTime > settings.fileAutosaveTime * time::SECOND_M) manager.autosave(document);
|
||||
}
|
||||
|
||||
if (ImGui::Begin("##Documents", nullptr,
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "settings.h"
|
||||
#include "taskbar.h"
|
||||
|
||||
namespace anm2ed::documents
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Documents
|
||||
{
|
||||
@@ -16,6 +16,6 @@ namespace anm2ed::documents
|
||||
public:
|
||||
float height{};
|
||||
|
||||
void update(taskbar::Taskbar&, manager::Manager&, settings::Settings&, resources::Resources&, bool&);
|
||||
void update(Taskbar&, Manager&, Settings&, Resources&, bool&);
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "imgui.h"
|
||||
#include "imgui_.h"
|
||||
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
@@ -11,6 +11,97 @@ using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
int input_text_callback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||
{
|
||||
auto* string = (std::string*)(data->UserData);
|
||||
string->resize(data->BufTextLen);
|
||||
data->Buf = string->data();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool input_text_string(const char* label, std::string* string, ImGuiInputTextFlags flags)
|
||||
{
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
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)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
bool input_int_range(const char* label, int& value, int min, int max, int step, int stepFast,
|
||||
ImGuiInputTextFlags flags)
|
||||
{
|
||||
auto isActivated = ImGui::InputInt(label, &value, step, stepFast, 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)
|
||||
{
|
||||
static std::string editID{};
|
||||
static bool isJustEdit{};
|
||||
const bool isEditing = editID == id;
|
||||
bool isActivated{};
|
||||
|
||||
if (isEditing)
|
||||
{
|
||||
if (isJustEdit)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
isJustEdit = false;
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (input_text_string("##Edit", &text, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
|
||||
{
|
||||
editID.clear();
|
||||
isActivated = true;
|
||||
if (isRenamed) *isRenamed = true;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit() || ImGui::IsKeyPressed(ImGuiKey_Escape)) editID.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true;
|
||||
|
||||
if ((ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2) && isSelected) ||
|
||||
(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))
|
||||
{
|
||||
editID = id;
|
||||
isJustEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
void set_item_tooltip_shortcut(const char* tooltip, const std::string& shortcut)
|
||||
{
|
||||
ImGui::SetItemTooltip("%s\n(Shortcut: %s)", tooltip, shortcut.c_str());
|
||||
}
|
||||
|
||||
void external_storage_set(ImGuiSelectionExternalStorage* self, int id, bool isSelected)
|
||||
{
|
||||
auto* set = (std::set<int>*)self->UserData;
|
||||
if (isSelected)
|
||||
set->insert(id);
|
||||
else
|
||||
set->erase(id);
|
||||
};
|
||||
|
||||
std::string chord_to_string(ImGuiKeyChord chord)
|
||||
{
|
||||
@@ -91,90 +182,6 @@ namespace anm2ed::imgui
|
||||
(ImGui::GetFrameHeightWithSpacing() * rowCount) + (ImGui::GetStyle().WindowPadding.y * 2.0f));
|
||||
}
|
||||
|
||||
int input_text_callback(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
|
||||
{
|
||||
auto* string = (std::string*)(data->UserData);
|
||||
string->resize(data->BufTextLen);
|
||||
data->Buf = string->data();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool input_text_string(const char* label, std::string* string, ImGuiInputTextFlags flags)
|
||||
{
|
||||
flags |= ImGuiInputTextFlags_CallbackResize;
|
||||
return ImGui::InputText(label, string->data(), string->capacity() + 1, flags, input_text_callback, string);
|
||||
}
|
||||
|
||||
void combo_strings(const std::string& label, int* index, std::vector<std::string>& strings)
|
||||
{
|
||||
std::vector<const char*> items{};
|
||||
for (auto& string : strings)
|
||||
items.push_back(string.c_str());
|
||||
ImGui::Combo(label.c_str(), index, items.data(), (int)items.size());
|
||||
}
|
||||
|
||||
void combo_strings(const std::string& label, int* index, std::vector<const char*>& strings)
|
||||
{
|
||||
ImGui::Combo(label.c_str(), index, strings.data(), (int)strings.size());
|
||||
}
|
||||
|
||||
bool selectable_input_text(const std::string& label, const std::string& id, std::string& text, bool isSelected,
|
||||
ImGuiSelectableFlags flags, bool* isRenamed)
|
||||
{
|
||||
static std::string editID{};
|
||||
static bool isJustEdit{};
|
||||
const bool isEditing = editID == id;
|
||||
bool isActivated{};
|
||||
|
||||
if (isEditing)
|
||||
{
|
||||
if (isJustEdit)
|
||||
{
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
isJustEdit = false;
|
||||
}
|
||||
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (input_text_string("##Edit", &text, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll))
|
||||
{
|
||||
editID.clear();
|
||||
isActivated = true;
|
||||
if (isRenamed) *isRenamed = true;
|
||||
}
|
||||
if (ImGui::IsItemDeactivatedAfterEdit() || ImGui::IsKeyPressed(ImGuiKey_Escape)) editID.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui::Selectable(label.c_str(), isSelected, flags)) isActivated = true;
|
||||
|
||||
if ((ImGui::IsWindowFocused() && ImGui::IsKeyPressed(ImGuiKey_F2) && isSelected) ||
|
||||
(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)))
|
||||
{
|
||||
editID = id;
|
||||
isJustEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
return isActivated;
|
||||
}
|
||||
|
||||
void set_item_tooltip_shortcut(const char* tooltip, const std::string& shortcut)
|
||||
{
|
||||
ImGui::SetItemTooltip("%s\n(Shortcut: %s)", tooltip, shortcut.c_str());
|
||||
}
|
||||
|
||||
void external_storage_set(ImGuiSelectionExternalStorage* self, int id, bool isSelected)
|
||||
{
|
||||
auto* set = (std::set<int>*)self->UserData;
|
||||
if (isSelected)
|
||||
set->insert(id);
|
||||
else
|
||||
set->erase(id);
|
||||
};
|
||||
|
||||
ImVec2 icon_size_get()
|
||||
{
|
||||
return ImVec2(ImGui::GetTextLineHeightWithSpacing(), ImGui::GetTextLineHeightWithSpacing());
|
||||
@@ -266,11 +273,11 @@ namespace anm2ed::imgui
|
||||
internal.ApplyRequests(io);
|
||||
}
|
||||
|
||||
PopupHelper::PopupHelper(const char* label, float percent, bool isNoHeight)
|
||||
PopupHelper::PopupHelper(const char* label, PopupType type, PopupPosition position)
|
||||
{
|
||||
this->label = label;
|
||||
this->percent = percent;
|
||||
this->isNoHeight = isNoHeight;
|
||||
this->type = type;
|
||||
this->position = position;
|
||||
}
|
||||
|
||||
void PopupHelper::open()
|
||||
@@ -291,11 +298,16 @@ namespace anm2ed::imgui
|
||||
isTriggered = false;
|
||||
|
||||
auto viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_None, to_imvec2(vec2(0.5f)));
|
||||
if (isNoHeight)
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * percent, 0));
|
||||
|
||||
if (position == POPUP_CENTER)
|
||||
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_None, to_imvec2(vec2(0.5f)));
|
||||
else
|
||||
ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * percent));
|
||||
ImGui::SetNextWindowPos(ImGui::GetItemRectMin(), ImGuiCond_None);
|
||||
|
||||
if (POPUP_IS_HEIGHT_SET[type])
|
||||
ImGui::SetNextWindowSize(to_imvec2(to_vec2(viewport->Size) * POPUP_MULTIPLIERS[type]));
|
||||
else
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x * POPUP_MULTIPLIERS[type], 0));
|
||||
}
|
||||
|
||||
void PopupHelper::end()
|
||||
@@ -10,9 +10,41 @@
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto POPUP_TO_CONTENT = 0.0f;
|
||||
constexpr auto POPUP_SMALL = 0.25f;
|
||||
constexpr auto POPUP_NORMAL = 0.5f;
|
||||
constexpr auto DRAG_SPEED = 1.0f;
|
||||
constexpr auto STEP = 1.0f;
|
||||
constexpr auto STEP_FAST = 5.0f;
|
||||
|
||||
#define POPUP_LIST \
|
||||
X(POPUP_SMALL, 0.25f, true) \
|
||||
X(POPUP_NORMAL, 0.5f, true) \
|
||||
X(POPUP_TO_CONTENT, 0.0f, true) \
|
||||
X(POPUP_SMALL_NO_HEIGHT, 0.25f, false) \
|
||||
X(POPUP_NORMAL_NO_HEIGHT, 0.5f, false)
|
||||
|
||||
enum PopupType
|
||||
{
|
||||
#define X(name, multiplier, isHeightSet) name,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
enum PopupPosition
|
||||
{
|
||||
POPUP_CENTER,
|
||||
POPUP_BY_ITEM
|
||||
};
|
||||
|
||||
constexpr float POPUP_MULTIPLIERS[] = {
|
||||
#define X(name, multiplier, isHeightSet) multiplier,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
constexpr bool POPUP_IS_HEIGHT_SET[] = {
|
||||
#define X(name, multiplier, isHeightSet) isHeightSet,
|
||||
POPUP_LIST
|
||||
#undef X
|
||||
};
|
||||
|
||||
const std::unordered_map<std::string, ImGuiKey> KEY_MAP = {{"A", ImGuiKey_A},
|
||||
{"B", ImGuiKey_B},
|
||||
@@ -134,8 +166,9 @@ namespace anm2ed::imgui
|
||||
ImVec2 child_size_get(int = 1);
|
||||
int input_text_callback(ImGuiInputTextCallbackData*);
|
||||
bool input_text_string(const char*, std::string*, ImGuiInputTextFlags = 0);
|
||||
void combo_strings(const std::string&, int*, std::vector<std::string>&);
|
||||
void combo_strings(const std::string&, int*, std::vector<const char*>&);
|
||||
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 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& = {});
|
||||
@@ -167,13 +200,13 @@ namespace anm2ed::imgui
|
||||
{
|
||||
public:
|
||||
const char* label{};
|
||||
PopupType type{};
|
||||
PopupPosition position{};
|
||||
bool isOpen{};
|
||||
bool isTriggered{};
|
||||
bool isJustOpened{};
|
||||
bool isNoHeight{};
|
||||
float percent{};
|
||||
|
||||
PopupHelper(const char*, float = POPUP_NORMAL, bool = false);
|
||||
PopupHelper(const char*, PopupType = POPUP_NORMAL, PopupPosition = POPUP_CENTER);
|
||||
bool is_open();
|
||||
void open();
|
||||
void trigger();
|
||||
@@ -3,18 +3,18 @@
|
||||
#include <imgui/imgui.h>
|
||||
#include <ranges>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "math.h"
|
||||
#include "math_.h"
|
||||
#include "render.h"
|
||||
#include "shader.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::taskbar
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
Taskbar::Taskbar() : generate(vec2())
|
||||
{
|
||||
@@ -30,9 +30,9 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::BeginMenu("File"))
|
||||
{
|
||||
if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.anm2_new();
|
||||
if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.anm2_open();
|
||||
if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.file_open(dialog::ANM2_NEW);
|
||||
|
||||
if (manager.recentFiles.empty())
|
||||
{
|
||||
@@ -63,7 +63,7 @@ namespace anm2ed::taskbar
|
||||
ImGui::BeginDisabled(!document);
|
||||
{
|
||||
if (ImGui::MenuItem("Save", settings.shortcutSave.c_str())) manager.save();
|
||||
if (ImGui::MenuItem("Save As", settings.shortcutSaveAs.c_str())) dialog.anm2_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();
|
||||
@@ -72,19 +72,19 @@ namespace anm2ed::taskbar
|
||||
if (ImGui::MenuItem("Exit", settings.shortcutExit.c_str())) isQuitting = true;
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (dialog.is_selected_file(dialog::ANM2_NEW))
|
||||
if (dialog.is_selected(dialog::ANM2_NEW))
|
||||
{
|
||||
manager.new_(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
if (dialog.is_selected_file(dialog::ANM2_OPEN))
|
||||
if (dialog.is_selected(dialog::ANM2_OPEN))
|
||||
{
|
||||
manager.open(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
|
||||
if (dialog.is_selected_file(dialog::ANM2_SAVE))
|
||||
if (dialog.is_selected(dialog::ANM2_SAVE))
|
||||
{
|
||||
manager.save(dialog.path);
|
||||
dialog.reset();
|
||||
@@ -92,11 +92,16 @@ namespace anm2ed::taskbar
|
||||
|
||||
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();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
@@ -160,13 +165,13 @@ namespace anm2ed::taskbar
|
||||
ImGui::InputInt2("Start Position", value_ptr(startPosition));
|
||||
ImGui::InputInt2("Frame Size", value_ptr(size));
|
||||
ImGui::InputInt2("Pivot", value_ptr(pivot));
|
||||
ImGui::InputInt("Rows", &rows, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt("Columns", &columns, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt("Rows", &rows, imgui::STEP, imgui::STEP_FAST);
|
||||
ImGui::InputInt("Columns", &columns, imgui::STEP, imgui::STEP_FAST);
|
||||
|
||||
ImGui::InputInt("Count", &count, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt("Count", &count, imgui::STEP, imgui::STEP_FAST);
|
||||
count = glm::min(count, rows * columns);
|
||||
|
||||
ImGui::InputInt("Delay", &delay, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt("Delay", &delay, imgui::STEP, imgui::STEP_FAST);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
@@ -176,7 +181,7 @@ namespace anm2ed::taskbar
|
||||
{
|
||||
auto& backgroundColor = settings.previewBackgroundColor;
|
||||
auto& time = generateTime;
|
||||
auto& shaderTexture = resources.shaders[shader::TEXTURE];
|
||||
auto& shaderTexture = resources.shaders[resource::shader::TEXTURE];
|
||||
|
||||
auto previewSize = ImVec2(ImGui::GetContentRegionAvail().x, imgui::size_without_footer_get(2).y);
|
||||
|
||||
@@ -191,7 +196,7 @@ namespace anm2ed::taskbar
|
||||
.spritesheets[document->anm2.content.layers[document->reference.itemID].spritesheetID]
|
||||
.texture;
|
||||
|
||||
auto index = std::clamp((int)(time * count), 0, count);
|
||||
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);
|
||||
@@ -212,7 +217,7 @@ namespace anm2ed::taskbar
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputFloat("##Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
|
||||
zoom = glm::clamp(zoom, canvas::ZOOM_MIN, canvas::ZOOM_MAX);
|
||||
zoom = glm::clamp(zoom, ZOOM_MIN, ZOOM_MAX);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
@@ -305,14 +310,14 @@ namespace anm2ed::taskbar
|
||||
auto float_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, float& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputFloat(valueLabel, &value, step::NORMAL, step::FAST, math::float_format_get(value));
|
||||
ImGui::InputFloat(valueLabel, &value, imgui::STEP, imgui::STEP_FAST, math::float_format_get(value));
|
||||
end();
|
||||
};
|
||||
|
||||
auto int_value = [&](const char* checkboxLabel, const char* valueLabel, bool& isEnabled, int& value)
|
||||
{
|
||||
start(checkboxLabel, isEnabled);
|
||||
ImGui::InputInt(valueLabel, &value, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt(valueLabel, &value, imgui::STEP, imgui::STEP_FAST);
|
||||
end();
|
||||
};
|
||||
|
||||
@@ -340,7 +345,7 @@ namespace anm2ed::taskbar
|
||||
"off, will use all frames.");
|
||||
|
||||
ImGui::BeginDisabled(!isFromSelectedFrame);
|
||||
ImGui::InputInt("Number of Frames", &numberFrames, step::NORMAL, step::FAST);
|
||||
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);
|
||||
ImGui::SetItemTooltip("Set the number of frames that will be changed.");
|
||||
@@ -350,7 +355,7 @@ namespace anm2ed::taskbar
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(4);
|
||||
|
||||
auto frame_change = [&](frame_change::Type type)
|
||||
auto frame_change = [&](anm2::ChangeType type)
|
||||
{
|
||||
anm2::FrameChange frameChange;
|
||||
frameChange.crop = isCrop ? std::make_optional(crop) : std::nullopt;
|
||||
@@ -370,7 +375,7 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
frame_change(frame_change::ADD);
|
||||
frame_change(anm2::ADD);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
@@ -378,7 +383,7 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::Button("Subtract", widgetSize))
|
||||
{
|
||||
frame_change(frame_change::SUBTRACT);
|
||||
frame_change(anm2::SUBTRACT);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
@@ -386,7 +391,7 @@ namespace anm2ed::taskbar
|
||||
|
||||
if (ImGui::Button("Adjust", widgetSize))
|
||||
{
|
||||
frame_change(frame_change::ADJUST);
|
||||
frame_change(anm2::ADJUST);
|
||||
changePopup.close();
|
||||
}
|
||||
|
||||
@@ -415,7 +420,7 @@ namespace anm2ed::taskbar
|
||||
ImGui::SetItemTooltip("Enables autosaving of documents.");
|
||||
|
||||
ImGui::BeginDisabled(!editSettings.fileIsAutosave);
|
||||
ImGui::InputInt("Autosave Time (minutes", &editSettings.fileAutosaveTime, step::NORMAL, step::FAST);
|
||||
ImGui::InputInt("Autosave Time (minutes", &editSettings.fileAutosaveTime, imgui::STEP, imgui::STEP_FAST);
|
||||
editSettings.fileAutosaveTime = glm::clamp(editSettings.fileAutosaveTime, 0, 10);
|
||||
ImGui::SetItemTooltip("If changed, will autosave documents using this interval.");
|
||||
ImGui::EndDisabled();
|
||||
@@ -521,12 +526,79 @@ namespace anm2ed::taskbar
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Close", widgetSize)) ImGui::CloseCurrentPopup();
|
||||
if (ImGui::Button("Close", widgetSize)) configurePopup.close();
|
||||
ImGui::SetItemTooltip("Close without updating settings.");
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
renderPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(renderPopup.label, &renderPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto animation = document ? document->animation_get() : nullptr;
|
||||
if (!animation) renderPopup.close();
|
||||
|
||||
auto& playback = document->playback;
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
auto& format = settings.renderFormat;
|
||||
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 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()))
|
||||
dialog.file_open(dialog::FFMPEG_PATH_SET);
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string("FFmpeg Path", &ffmpegPath);
|
||||
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 (dialogType == dialog::PNG_DIRECTORY_SET)
|
||||
dialog.folder_open(dialogType);
|
||||
else
|
||||
dialog.file_open(dialogType);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
imgui::input_text_string(type == render::PNGS ? "Directory" : "Path", &path);
|
||||
dialog.set_string_to_selected_path(path, dialogType);
|
||||
|
||||
ImGui::Combo("Type", &type, render::STRINGS, render::COUNT);
|
||||
|
||||
ImGui::BeginDisabled(type != render::PNGS);
|
||||
imgui::input_text_string("Format", &format);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(!isRange);
|
||||
imgui::input_int_range("Start", start, 0, animation->frameNum - 1);
|
||||
ImGui::InputInt("End", &end, start, animation->frameNum);
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Checkbox("Custom Range", &isRange);
|
||||
|
||||
if (ImGui::Button("Render", widgetSize))
|
||||
{
|
||||
manager.isRecording = true;
|
||||
playback.time = start;
|
||||
playback.isPlaying = true;
|
||||
renderPopup.close();
|
||||
manager.progressPopup.open();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) renderPopup.close();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
aboutPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(aboutPopup.label, &aboutPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
@@ -535,10 +607,10 @@ namespace anm2ed::taskbar
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (imgui::shortcut(settings.shortcutNew, shortcut::GLOBAL)) dialog.anm2_new();
|
||||
if (imgui::shortcut(settings.shortcutOpen, shortcut::GLOBAL)) dialog.anm2_open();
|
||||
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.anm2_save();
|
||||
if (imgui::shortcut(settings.shortcutSaveAs, shortcut::GLOBAL)) dialog.file_save(dialog::ANM2_SAVE);
|
||||
if (imgui::shortcut(settings.shortcutExit, shortcut::GLOBAL)) isQuitting = true;
|
||||
}
|
||||
}
|
||||
31
src/imgui/taskbar.h
Normal file
31
src/imgui/taskbar.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "dialog.h"
|
||||
#include "imgui_.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Taskbar
|
||||
{
|
||||
Canvas generate;
|
||||
float generateTime{};
|
||||
PopupHelper generatePopup{PopupHelper("Generate Animation from Grid")};
|
||||
PopupHelper changePopup{PopupHelper("Change All Frame Properties", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper renderPopup{PopupHelper("Render Animation", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
PopupHelper configurePopup{PopupHelper("Configure")};
|
||||
PopupHelper aboutPopup{PopupHelper("About")};
|
||||
Settings editSettings{};
|
||||
int selectedShortcut{-1};
|
||||
bool isQuittingMode{};
|
||||
|
||||
public:
|
||||
float height{};
|
||||
|
||||
Taskbar();
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, bool&);
|
||||
};
|
||||
};
|
||||
@@ -5,10 +5,9 @@
|
||||
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::log;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::toast
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto LIFETIME = 3.0f;
|
||||
|
||||
@@ -82,5 +81,6 @@ namespace anm2ed::toast
|
||||
logger.warning(message);
|
||||
}
|
||||
|
||||
Toasts toasts;
|
||||
}
|
||||
|
||||
anm2ed::imgui::Toasts toasts;
|
||||
@@ -3,9 +3,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace anm2ed::toast
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
|
||||
class Toast
|
||||
{
|
||||
public:
|
||||
@@ -26,5 +25,6 @@ namespace anm2ed::toast
|
||||
void warning(const std::string&);
|
||||
};
|
||||
|
||||
extern Toasts toasts;
|
||||
}
|
||||
|
||||
extern anm2ed::imgui::Toasts toasts;
|
||||
@@ -1,21 +1,22 @@
|
||||
#include "animation_preview.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "math.h"
|
||||
#include "log.h"
|
||||
#include "math_.h"
|
||||
#include "toast.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::playback;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::animation_preview
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
|
||||
constexpr auto TARGET_SIZE = vec2(32, 32);
|
||||
@@ -27,6 +28,75 @@ namespace anm2ed::animation_preview
|
||||
{
|
||||
}
|
||||
|
||||
void AnimationPreview::tick(Manager& manager, Document& document, Settings& settings)
|
||||
{
|
||||
auto& anm2 = document.anm2;
|
||||
auto& playback = document.playback;
|
||||
|
||||
if (playback.isPlaying)
|
||||
{
|
||||
auto& isSound = settings.timelineIsSound;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
|
||||
if (isSound && !anm2.content.sounds.empty())
|
||||
if (auto animation = document.animation_get(); animation)
|
||||
if (animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); trigger.isVisible)
|
||||
anm2.content.sounds[anm2.content.events[trigger.eventID].soundID].audio.play();
|
||||
|
||||
document.reference.frameTime = playback.time;
|
||||
}
|
||||
|
||||
if (manager.isRecording)
|
||||
{
|
||||
auto pixels = pixels_get();
|
||||
renderFrames.push_back(Texture(pixels.data(), size));
|
||||
|
||||
if (playback.isFinished)
|
||||
{
|
||||
auto& ffmpegPath = settings.renderFFmpegPath;
|
||||
auto& path = settings.renderPath;
|
||||
auto& type = settings.renderType;
|
||||
|
||||
if (type == render::PNGS)
|
||||
{
|
||||
auto& format = settings.renderFormat;
|
||||
bool isSuccess{true};
|
||||
for (auto [i, frame] : std::views::enumerate(renderFrames))
|
||||
{
|
||||
std::filesystem::path outputPath =
|
||||
std::filesystem::path(path) / std::vformat(format, std::make_format_args(i));
|
||||
|
||||
if (!frame.write_png(outputPath))
|
||||
{
|
||||
isSuccess = false;
|
||||
break;
|
||||
}
|
||||
logger.info(std::format("Saved frame to PNG: {}", outputPath.string()));
|
||||
}
|
||||
|
||||
if (isSuccess)
|
||||
toasts.info(std::format("Exported rendered frames to: {}", path));
|
||||
else
|
||||
toasts.warning(std::format("Could not export frames to: {}", path));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animation_render(ffmpegPath, path, renderFrames, (render::Type)type, size, anm2.info.fps))
|
||||
toasts.info(std::format("Exported rendered animation to: {}", path));
|
||||
else
|
||||
toasts.warning(std::format("Could not output rendered animation: {}", path));
|
||||
}
|
||||
|
||||
renderFrames.clear();
|
||||
playback.isPlaying = false;
|
||||
playback.isFinished = false;
|
||||
manager.isRecording = false;
|
||||
manager.progressPopup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
@@ -62,7 +132,6 @@ namespace anm2ed::animation_preview
|
||||
|
||||
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
|
||||
{
|
||||
|
||||
auto childSize = ImVec2(imgui::row_widget_width_get(4),
|
||||
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
|
||||
|
||||
@@ -110,12 +179,7 @@ namespace anm2ed::animation_preview
|
||||
ImGui::SameLine();
|
||||
ImGui::ColorEdit4("Color", value_ptr(axesColor), ImGuiColorEditFlags_NoInputs);
|
||||
|
||||
std::vector<std::string> animationNames{};
|
||||
animationNames.emplace_back("None");
|
||||
for (auto& animation : anm2.animations.items)
|
||||
animationNames.emplace_back(animation.name);
|
||||
|
||||
imgui::combo_strings("Overlay", &overlayIndex, animationNames);
|
||||
imgui::combo_strings("Overlay", &overlayIndex, document.animationNamesCStr);
|
||||
|
||||
ImGui::InputFloat("Alpha", &overlayTransparency, 0, 0, "%.0f");
|
||||
}
|
||||
@@ -253,6 +317,8 @@ namespace anm2ed::animation_preview
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
float useTime = time + (float)(direction * i);
|
||||
if (useTime < 0.0f || useTime > animation->frameNum) continue;
|
||||
|
||||
float alphaOffset = (1.0f / (count + 1)) * i;
|
||||
render(animation, useTime, color, alphaOffset, true);
|
||||
}
|
||||
@@ -272,10 +338,13 @@ namespace anm2ed::animation_preview
|
||||
auto& isEnabled = settings.onionskinIsEnabled;
|
||||
|
||||
if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime);
|
||||
|
||||
render(animation, frameTime);
|
||||
|
||||
if (overlayIndex > 0)
|
||||
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
|
||||
1.0f - math::uint8_to_float(overlayTransparency));
|
||||
|
||||
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
|
||||
}
|
||||
|
||||
@@ -285,7 +354,7 @@ namespace anm2ed::animation_preview
|
||||
|
||||
isPreviewHovered = ImGui::IsItemHovered();
|
||||
|
||||
if (animation && animation->triggers.isVisible)
|
||||
if (animation && animation->triggers.isVisible && !isOnlyShowLayers)
|
||||
{
|
||||
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER); trigger.isVisible)
|
||||
{
|
||||
@@ -333,7 +402,7 @@ namespace anm2ed::animation_preview
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
auto frame = document.frame_get();
|
||||
auto useTool = tool;
|
||||
auto step = isMod ? step::FAST : step::NORMAL;
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
|
||||
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
|
||||
auto isBegin = isMouseClick || isKeyPressed;
|
||||
@@ -343,6 +412,8 @@ namespace anm2ed::animation_preview
|
||||
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
|
||||
if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE;
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
case tool::PAN:
|
||||
@@ -356,7 +427,7 @@ namespace anm2ed::animation_preview
|
||||
if (isRight) frame->position.x += step;
|
||||
if (isUp) frame->position.y -= step;
|
||||
if (isDown) frame->position.y += step;
|
||||
if (isEnd) document.change(change::FRAMES);
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::SCALE:
|
||||
if (!frame) break;
|
||||
@@ -366,7 +437,7 @@ namespace anm2ed::animation_preview
|
||||
if (isRight) frame->scale.x += step;
|
||||
if (isUp) frame->scale.y -= step;
|
||||
if (isDown) frame->scale.y += step;
|
||||
if (isEnd) document.change(change::FRAMES);
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::ROTATE:
|
||||
if (!frame) break;
|
||||
@@ -374,7 +445,7 @@ namespace anm2ed::animation_preview
|
||||
if (isMouseDown) frame->rotation += mouseDelta.y;
|
||||
if (isLeft || isDown) frame->rotation -= step;
|
||||
if (isUp || isRight) frame->rotation += step;
|
||||
if (isEnd) document.change(change::FRAMES);
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -385,5 +456,27 @@ namespace anm2ed::animation_preview
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
manager.progressPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(manager.progressPopup.label, &manager.progressPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
if (!animation) return;
|
||||
|
||||
auto& start = manager.recordingStart;
|
||||
auto& end = manager.recordingEnd;
|
||||
auto progress = (playback.time - start) / (end - start);
|
||||
|
||||
ImGui::ProgressBar(progress);
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
|
||||
{
|
||||
playback.isPlaying = false;
|
||||
manager.isRecording = false;
|
||||
manager.progressPopup.close();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/imgui/window/animation_preview.h
Normal file
21
src/imgui/window/animation_preview.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "canvas.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class AnimationPreview : public Canvas
|
||||
{
|
||||
bool isPreviewHovered{};
|
||||
glm::ivec2 mousePos{};
|
||||
std::vector<resource::Texture> renderFrames{};
|
||||
|
||||
public:
|
||||
AnimationPreview();
|
||||
void tick(Manager&, Document&, Settings&);
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::animations
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Animations::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
@@ -24,7 +21,7 @@ namespace anm2ed::animations
|
||||
|
||||
if (ImGui::Begin("Animations", &settings.windowIsAnimations))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Animations Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
@@ -44,9 +41,8 @@ namespace anm2ed::animations
|
||||
|
||||
ImGui::PushFont(resources.fonts[font].get(), font::SIZE);
|
||||
ImGui::SetNextItemSelectionUserData(i);
|
||||
if (imgui::selectable_input_text(animation.name,
|
||||
std::format("###Document #{} Animation #{}", manager.selected, i),
|
||||
animation.name, multiSelect.contains(i)))
|
||||
if (selectable_input_text(animation.name, std::format("###Document #{} Animation #{}", manager.selected, i),
|
||||
animation.name, multiSelect.contains(i)))
|
||||
document.animation_set(i);
|
||||
if (ImGui::IsItemHovered()) hovered = i;
|
||||
ImGui::PopFont();
|
||||
@@ -124,9 +120,9 @@ namespace anm2ed::animations
|
||||
document.animations_deserialize(clipboardText);
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCut, shortcut::FOCUSED)) cut();
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (imgui::shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste();
|
||||
if (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))
|
||||
{
|
||||
@@ -144,23 +140,23 @@ namespace anm2ed::animations
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(5);
|
||||
auto widgetSize = widget_size_with_row_get(5);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) document.animation_add();
|
||||
imgui::set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
|
||||
set_item_tooltip_shortcut("Add a new animation.", settings.shortcutAdd);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty());
|
||||
{
|
||||
imgui::shortcut(settings.shortcutDuplicate);
|
||||
shortcut(settings.shortcutDuplicate);
|
||||
if (ImGui::Button("Duplicate", widgetSize)) document.animation_duplicate();
|
||||
imgui::set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
|
||||
set_item_tooltip_shortcut("Duplicate the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (imgui::shortcut(settings.shortcutMerge, shortcut::FOCUSED))
|
||||
if (shortcut(settings.shortcutMerge, shortcut::FOCUSED))
|
||||
if (multiSelect.size() > 0) document.animations_merge_quick();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
@@ -174,24 +170,23 @@ namespace anm2ed::animations
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
imgui::set_item_tooltip_shortcut(
|
||||
"Open the merge popup.\nUsing the shortcut will merge the animations with\nthe last "
|
||||
"configured merge settings.",
|
||||
settings.shortcutMerge);
|
||||
set_item_tooltip_shortcut("Open the merge popup.\nUsing the shortcut will merge the animations with\nthe last "
|
||||
"configured merge settings.",
|
||||
settings.shortcutMerge);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
shortcut(settings.shortcutRemove);
|
||||
if (ImGui::Button("Remove", widgetSize)) document.animations_remove();
|
||||
imgui::set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
|
||||
set_item_tooltip_shortcut("Remove the selected animation(s).", settings.shortcutDuplicate);
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutDefault);
|
||||
shortcut(settings.shortcutDefault);
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
if (ImGui::Button("Default", widgetSize)) document.animation_default();
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
|
||||
set_item_tooltip_shortcut("Set the selected animation as the default.", settings.shortcutDefault);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -208,9 +203,9 @@ namespace anm2ed::animations
|
||||
auto& type = settings.mergeType;
|
||||
auto& isDeleteAnimationsAfter = settings.mergeIsDeleteAnimationsAfter;
|
||||
|
||||
auto footerSize = imgui::footer_size_get();
|
||||
auto optionsSize = imgui::child_size_get(2);
|
||||
auto deleteAfterSize = imgui::child_size_get();
|
||||
auto footerSize = footer_size_get();
|
||||
auto optionsSize = child_size_get(2);
|
||||
auto deleteAfterSize = child_size_get();
|
||||
auto animationsSize =
|
||||
ImVec2(0, ImGui::GetContentRegionAvail().y -
|
||||
(optionsSize.y + deleteAfterSize.y + footerSize.y + ImGui::GetStyle().ItemSpacing.y * 3));
|
||||
@@ -259,7 +254,7 @@ namespace anm2ed::animations
|
||||
ImGui::Checkbox("Delete Animations After", &isDeleteAnimationsAfter);
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Merge", widgetSize))
|
||||
{
|
||||
17
src/imgui/window/animations.h
Normal file
17
src/imgui/window/animations.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Animations
|
||||
{
|
||||
PopupHelper mergePopup{PopupHelper("Merge Animations")};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
160
src/imgui/window/events.cpp
Normal file
160
src/imgui/window/events.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "events.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& unused = document.unusedEventIDs;
|
||||
auto& hovered = document.hoveredEvent;
|
||||
auto& reference = document.referenceEvent;
|
||||
auto& multiSelect = document.eventMultiSelect;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Events", &settings.windowIsEvents))
|
||||
{
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Events Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.events.size());
|
||||
|
||||
for (auto& [id, event] : anm2.content.events)
|
||||
{
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
ImGui::Selectable(event.name.c_str(), multiSelect.contains(id));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
{
|
||||
reference = id;
|
||||
editEvent = document.anm2.content.events[reference];
|
||||
propertiesPopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(event.name.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
multiSelect.finish();
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (!multiSelect.empty())
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.events[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.events[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.events_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
if (shortcut(settings.shortcutPaste, shortcut::FOCUSED)) paste(merge::APPEND);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::MenuItem("Cut", settings.shortcutCut.c_str());
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.empty() && hovered == -1);
|
||||
if (ImGui::MenuItem("Copy", settings.shortcutCopy.c_str())) copy();
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::BeginDisabled(clipboard.is_empty());
|
||||
{
|
||||
if (ImGui::BeginMenu("Paste"))
|
||||
{
|
||||
if (ImGui::MenuItem("Append", settings.shortcutPaste.c_str())) paste(merge::APPEND);
|
||||
if (ImGui::MenuItem("Replace")) paste(merge::REPLACE);
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize))
|
||||
{
|
||||
reference = -1;
|
||||
editEvent = anm2::Event();
|
||||
propertiesPopup.open();
|
||||
}
|
||||
set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
propertiesPopup.trigger();
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = child_size_get(2);
|
||||
auto& event = editEvent;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
input_text_string("Name", &event.name);
|
||||
ImGui::SetItemTooltip("Set the event's name.");
|
||||
combo_strings("Sound", &event.soundID, document.soundNames);
|
||||
ImGui::SetItemTooltip("Set the event sound; it will play when a trigger associated with this event activates.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
document.event_set(event);
|
||||
propertiesPopup.close();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", widgetSize)) propertiesPopup.close();
|
||||
|
||||
propertiesPopup.end();
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/imgui/window/events.h
Normal file
18
src/imgui/window/events.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Events
|
||||
{
|
||||
anm2::Event editEvent{};
|
||||
PopupHelper propertiesPopup{PopupHelper("Event Properties", POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
125
src/imgui/window/frame_properties.cpp
Normal file
125
src/imgui/window/frame_properties.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "frame_properties.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "math_.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::util::math;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void FrameProperties::update(Manager& manager, Settings& settings)
|
||||
{
|
||||
if (ImGui::Begin("Frame Properties", &settings.windowIsFrameProperties))
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto& type = reference.itemType;
|
||||
auto frame = document.frame_get();
|
||||
auto useFrame = frame ? *frame : anm2::Frame();
|
||||
|
||||
ImGui::BeginDisabled(!frame);
|
||||
{
|
||||
if (type == anm2::TRIGGER)
|
||||
{
|
||||
std::vector<std::string> eventNames{};
|
||||
for (auto& event : anm2.content.events | std::views::values)
|
||||
eventNames.emplace_back(event.name);
|
||||
|
||||
if (imgui::combo_strings("Event", frame ? &useFrame.eventID : &dummy_value<int>(), eventNames))
|
||||
DOCUMENT_EDIT(document, "Trigger Event", Document::FRAMES, frame->eventID = useFrame.eventID);
|
||||
ImGui::SetItemTooltip("Change the event this trigger uses.");
|
||||
if (ImGui::InputInt("At Frame", frame ? &useFrame.atFrame : &dummy_value<int>(), imgui::STEP,
|
||||
imgui::STEP_FAST, !frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
DOCUMENT_EDIT(document, "Trigger At Frame", Document::FRAMES, frame->atFrame = useFrame.atFrame);
|
||||
ImGui::SetItemTooltip("Change the frame the trigger will be activated at.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Crop", frame ? value_ptr(useFrame.crop) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.crop) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Crop", Document::FRAMES, frame->crop = useFrame.crop);
|
||||
ImGui::SetItemTooltip("Change the crop position the frame uses.");
|
||||
|
||||
if (ImGui::InputFloat2("Size", frame ? value_ptr(useFrame.size) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.size) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Size", Document::FRAMES, frame->size = useFrame.size);
|
||||
ImGui::SetItemTooltip("Change the size of the crop the frame uses.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Position", frame ? value_ptr(useFrame.position) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.position) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Position", Document::FRAMES, frame->position = useFrame.position);
|
||||
ImGui::SetItemTooltip("Change the position of the frame.");
|
||||
|
||||
ImGui::BeginDisabled(type == anm2::ROOT || type == anm2::NULL_);
|
||||
{
|
||||
if (ImGui::InputFloat2("Pivot", frame ? value_ptr(useFrame.pivot) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.pivot) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Pivot", Document::FRAMES, frame->pivot = useFrame.pivot);
|
||||
ImGui::SetItemTooltip("Change the pivot of the frame; i.e., where it is centered.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (ImGui::InputFloat2("Scale", frame ? value_ptr(useFrame.scale) : &dummy_value<float>(),
|
||||
frame ? vec2_format_get(useFrame.scale) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Scale", Document::FRAMES, frame->scale = useFrame.scale);
|
||||
ImGui::SetItemTooltip("Change the scale of the frame, in percent.");
|
||||
|
||||
if (ImGui::InputFloat("Rotation", frame ? &useFrame.rotation : &dummy_value<float>(), imgui::STEP,
|
||||
imgui::STEP_FAST, frame ? float_format_get(useFrame.rotation) : ""))
|
||||
DOCUMENT_EDIT(document, "Frame Rotation", Document::FRAMES, frame->rotation = useFrame.rotation);
|
||||
ImGui::SetItemTooltip("Change the rotation of the frame.");
|
||||
|
||||
if (ImGui::InputInt("Duration", frame ? &useFrame.delay : &dummy_value<int>(), imgui::STEP, imgui::STEP_FAST,
|
||||
!frame ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0))
|
||||
DOCUMENT_EDIT(document, "Frame Duration", Document::FRAMES, frame->delay = useFrame.delay);
|
||||
ImGui::SetItemTooltip("Change how long the frame lasts.");
|
||||
|
||||
if (ImGui::ColorEdit4("Tint", frame ? value_ptr(useFrame.tint) : &dummy_value<float>()))
|
||||
DOCUMENT_EDIT(document, "Frame Tint", Document::FRAMES, frame->tint = useFrame.tint);
|
||||
ImGui::SetItemTooltip("Change the tint of the frame.");
|
||||
|
||||
if (ImGui::ColorEdit3("Color Offset", frame ? value_ptr(useFrame.colorOffset) : &dummy_value<float>()))
|
||||
DOCUMENT_EDIT(document, "Frame Color Offset", Document::FRAMES, frame->colorOffset = useFrame.colorOffset);
|
||||
ImGui::SetItemTooltip("Change the color added onto the frame.");
|
||||
|
||||
if (ImGui::Checkbox("Visible", frame ? &useFrame.isVisible : &dummy_value<bool>()))
|
||||
DOCUMENT_EDIT(document, "Frame Visibility", Document::FRAMES, frame->isVisible = useFrame.isVisible);
|
||||
ImGui::SetItemTooltip("Toggle the frame's visibility.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Checkbox("Interpolated", frame ? &useFrame.isInterpolated : &dummy_value<bool>()))
|
||||
DOCUMENT_EDIT(document, "Frame Interpolation", Document::FRAMES,
|
||||
frame->isInterpolated = useFrame.isInterpolated);
|
||||
ImGui::SetItemTooltip(
|
||||
"Toggle the frame interpolating; i.e., blending its values into the next frame based on the time.");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Flip X", widgetSize))
|
||||
DOCUMENT_EDIT(document, "Frame Flip X", Document::FRAMES, frame->scale.x = -frame->scale.x);
|
||||
ImGui::SetItemTooltip("%s", "Flip the horizontal scale of the frame, to cheat mirroring the frame "
|
||||
"horizontally.\n(Note: the format does not support mirroring.)");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Flip Y", widgetSize))
|
||||
DOCUMENT_EDIT(document, "Frame Flip Y", Document::FRAMES, frame->scale.y = -frame->scale.y);
|
||||
ImGui::SetItemTooltip("%s", "Flip the vertical scale of the frame, to cheat mirroring the frame "
|
||||
"vertically.\n(Note: the format does not support mirroring.)");
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,11 @@
|
||||
#include "manager.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::frame_properties
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class FrameProperties
|
||||
{
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&);
|
||||
void update(Manager&, Settings&);
|
||||
};
|
||||
}
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::document;
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::layers
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Layers::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
@@ -25,7 +21,7 @@ namespace anm2ed::layers
|
||||
|
||||
if (ImGui::Begin("Layers", &settings.windowIsLayers))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Layers Child", childSize, true))
|
||||
{
|
||||
@@ -80,8 +76,8 @@ namespace anm2ed::layers
|
||||
document.layers_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
@@ -110,19 +106,19 @@ namespace anm2ed::layers
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) manager.layer_properties_open();
|
||||
imgui::set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd);
|
||||
set_item_tooltip_shortcut("Add a layer.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.layers_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
set_item_tooltip_shortcut("Remove unused layers (i.e., ones not used in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
@@ -130,20 +126,20 @@ namespace anm2ed::layers
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = imgui::child_size_get(2);
|
||||
auto childSize = child_size_get(2);
|
||||
auto& layer = manager.editLayer;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
imgui::input_text_string("Name", &layer.name);
|
||||
input_text_string("Name", &layer.name);
|
||||
ImGui::SetItemTooltip("Set the item's name.");
|
||||
imgui::combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
|
||||
combo_strings("Spritesheet", &layer.spritesheetID, document.spritesheetNames);
|
||||
ImGui::SetItemTooltip("Set the layer item's spritesheet.");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
@@ -5,11 +5,11 @@
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::layers
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Layers
|
||||
{
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,10 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
|
||||
namespace anm2ed::nulls
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Nulls::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
{
|
||||
@@ -24,7 +21,7 @@ namespace anm2ed::nulls
|
||||
|
||||
if (ImGui::Begin("Nulls", &settings.windowIsNulls))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
auto childSize = size_without_footer_get();
|
||||
|
||||
if (ImGui::BeginChild("##Nulls Child", childSize, true))
|
||||
{
|
||||
@@ -79,8 +76,8 @@ namespace anm2ed::nulls
|
||||
document.nulls_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("##Context Menu", ImGuiPopupFlags_MouseButtonRight))
|
||||
{
|
||||
@@ -109,19 +106,18 @@ namespace anm2ed::nulls
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) manager.null_properties_open();
|
||||
imgui::set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd);
|
||||
set_item_tooltip_shortcut("Add a null.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.nulls_remove_unused();
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)",
|
||||
settings.shortcutRemove);
|
||||
set_item_tooltip_shortcut("Remove unused nulls (i.e., ones not used in any animation.)", settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
@@ -129,13 +125,13 @@ namespace anm2ed::nulls
|
||||
|
||||
if (ImGui::BeginPopupModal(propertiesPopup.label, &propertiesPopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
auto childSize = imgui::child_size_get(2);
|
||||
auto childSize = child_size_get(2);
|
||||
auto& null = manager.editNull;
|
||||
|
||||
if (ImGui::BeginChild("Child", childSize, ImGuiChildFlags_Borders))
|
||||
{
|
||||
if (propertiesPopup.isJustOpened) ImGui::SetKeyboardFocusHere();
|
||||
imgui::input_text_string("Name", &null.name);
|
||||
input_text_string("Name", &null.name);
|
||||
ImGui::SetItemTooltip("Set the null's name.");
|
||||
|
||||
ImGui::Checkbox("Rect", &null.isShowRect);
|
||||
@@ -143,7 +139,7 @@ namespace anm2ed::nulls
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button(reference == -1 ? "Add" : "Confirm", widgetSize))
|
||||
{
|
||||
@@ -5,11 +5,11 @@
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::nulls
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Nulls
|
||||
{
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&, clipboard::Clipboard&);
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
55
src/imgui/window/onionskin.cpp
Normal file
55
src/imgui/window/onionskin.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "onionskin.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui_.h"
|
||||
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto FRAMES_MAX = 100;
|
||||
|
||||
void Onionskin::update(Settings& settings)
|
||||
{
|
||||
auto& isEnabled = settings.onionskinIsEnabled;
|
||||
auto& beforeCount = settings.onionskinBeforeCount;
|
||||
auto& beforeColor = settings.onionskinBeforeColor;
|
||||
auto& afterCount = settings.onionskinAfterCount;
|
||||
auto& afterColor = settings.onionskinAfterColor;
|
||||
auto& drawOrder = settings.onionskinDrawOrder;
|
||||
|
||||
if (ImGui::Begin("Onionskin", &settings.windowIsOnionskin))
|
||||
{
|
||||
auto configure_widgets = [&](const char* separator, int& frames, vec3& color)
|
||||
{
|
||||
ImGui::PushID(separator);
|
||||
ImGui::SeparatorText(separator);
|
||||
input_int_range("Frames", frames, 0, FRAMES_MAX);
|
||||
ImGui::SetItemTooltip("Change the amount of frames this onionskin will use.");
|
||||
ImGui::ColorEdit3("Color", value_ptr(color));
|
||||
ImGui::SetItemTooltip("Change the color of the frames this onionskin will use.");
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
ImGui::Checkbox("Enabled", &isEnabled);
|
||||
set_item_tooltip_shortcut("Toggle onionskinning.", settings.shortcutOnionskin);
|
||||
|
||||
configure_widgets("Before", beforeCount, beforeColor);
|
||||
configure_widgets("After", afterCount, afterColor);
|
||||
|
||||
ImGui::Text("Draw Order");
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Below", &drawOrder, draw_order::BELOW);
|
||||
ImGui::SetItemTooltip("The onionskin frames will draw below the original frames.");
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Above", &drawOrder, draw_order::ABOVE);
|
||||
ImGui::SetItemTooltip("The onionskin frames will draw above the original frames.");
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (shortcut(settings.shortcutOnionskin, shortcut::GLOBAL)) isEnabled = !isEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::onionskin
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Onionskin
|
||||
{
|
||||
public:
|
||||
void update(settings::Settings&);
|
||||
void update(Settings&);
|
||||
};
|
||||
}
|
||||
@@ -1,47 +1,58 @@
|
||||
#include "events.h"
|
||||
#include "sounds.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
|
||||
namespace anm2ed::events
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Events::update(Manager& manager, Settings& settings, Resources& resources, Clipboard& clipboard)
|
||||
void Sounds::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, Clipboard& clipboard)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& anm2 = document.anm2;
|
||||
auto& unused = document.unusedEventIDs;
|
||||
auto& hovered = document.hoveredEvent;
|
||||
auto& multiSelect = document.eventMultiSelect;
|
||||
auto& reference = document.referenceNull;
|
||||
auto& unused = document.unusedNullIDs;
|
||||
auto& hovered = document.hoveredNull;
|
||||
auto& multiSelect = document.soundMultiSelect;
|
||||
|
||||
hovered = -1;
|
||||
|
||||
if (ImGui::Begin("Events", &settings.windowIsEvents))
|
||||
if (ImGui::Begin("Sounds", &settings.windowIsSounds))
|
||||
{
|
||||
auto childSize = imgui::size_without_footer_get();
|
||||
bool isRenamed{};
|
||||
|
||||
if (ImGui::BeginChild("##Events Child", childSize, true))
|
||||
if (ImGui::BeginChild("##Sounds Child", childSize, true))
|
||||
{
|
||||
multiSelect.start(anm2.content.events.size());
|
||||
multiSelect.start(anm2.content.sounds.size());
|
||||
|
||||
for (auto& [id, event] : anm2.content.events)
|
||||
for (auto& [id, sound] : anm2.content.sounds)
|
||||
{
|
||||
auto isSelected = multiSelect.contains(id);
|
||||
auto isReferenced = reference == id;
|
||||
|
||||
ImGui::PushID(id);
|
||||
ImGui::SetNextItemSelectionUserData(id);
|
||||
if (imgui::selectable_input_text(event.name, std::format("###Document #{} Event #{}", manager.selected, id),
|
||||
event.name, multiSelect.contains(id), 0, &isRenamed))
|
||||
if (ImGui::IsItemHovered()) hovered = id;
|
||||
if (isRenamed) document.change(change::EVENTS);
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
if (ImGui::Selectable(std::format(anm2::SOUND_FORMAT, id, sound.path.string()).c_str(), isSelected))
|
||||
sound.audio.play();
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
hovered = id;
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
|
||||
;
|
||||
}
|
||||
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
if (ImGui::BeginItemTooltip())
|
||||
{
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE);
|
||||
ImGui::TextUnformatted(event.name.c_str());
|
||||
ImGui::TextUnformatted(sound.path.c_str());
|
||||
ImGui::PopFont();
|
||||
ImGui::Text("ID: %d", id);
|
||||
ImGui::Text("Click to play.");
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::PopID();
|
||||
@@ -55,17 +66,17 @@ namespace anm2ed::events
|
||||
{
|
||||
std::string clipboardText{};
|
||||
for (auto& id : multiSelect)
|
||||
clipboardText += anm2.content.events[id].to_string(id);
|
||||
clipboardText += anm2.content.sounds[id].to_string(id);
|
||||
clipboard.set(clipboardText);
|
||||
}
|
||||
else if (hovered > -1)
|
||||
clipboard.set(anm2.content.events[hovered].to_string(hovered));
|
||||
clipboard.set(anm2.content.sounds[hovered].to_string(hovered));
|
||||
};
|
||||
|
||||
auto paste = [&](merge::Type type)
|
||||
{
|
||||
auto clipboardText = clipboard.get();
|
||||
document.events_deserialize(clipboardText, type);
|
||||
document.sounds_deserialize(clipboardText, type);
|
||||
};
|
||||
|
||||
if (imgui::shortcut(settings.shortcutCopy, shortcut::FOCUSED)) copy();
|
||||
@@ -101,17 +112,24 @@ namespace anm2ed::events
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) document.event_add();
|
||||
imgui::set_item_tooltip_shortcut("Add an event.", settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", widgetSize)) dialog.file_open(dialog::SOUND_OPEN);
|
||||
imgui::set_item_tooltip_shortcut("Add a sound.", settings.shortcutAdd);
|
||||
ImGui::SameLine();
|
||||
|
||||
imgui::shortcut(settings.shortcutRemove);
|
||||
ImGui::BeginDisabled(unused.empty());
|
||||
if (ImGui::Button("Remove Unused", widgetSize)) document.events_remove_unused();
|
||||
if (ImGui::Button("Remove Unused", widgetSize))
|
||||
;
|
||||
ImGui::EndDisabled();
|
||||
imgui::set_item_tooltip_shortcut("Remove unused events (i.e., ones not used by any trigger in any animation.)",
|
||||
imgui::set_item_tooltip_shortcut("Remove unused sounds (i.e., ones not used in any trigger.)",
|
||||
settings.shortcutRemove);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (dialog.is_selected(dialog::SOUND_OPEN))
|
||||
{
|
||||
document.sound_add(dialog.path);
|
||||
dialog.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/imgui/window/sounds.h
Normal file
16
src/imgui/window/sounds.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "dialog.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Sounds
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard&);
|
||||
};
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
#include "spritesheet_editor.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "math.h"
|
||||
#include "math_.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::canvas;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::spritesheet_editor
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto PIVOT_COLOR = color::PINK;
|
||||
|
||||
@@ -167,6 +165,8 @@ namespace anm2ed::spritesheet_editor
|
||||
|
||||
if (isMouseMiddleDown) useTool = tool::PAN;
|
||||
|
||||
ImGui::SetMouseCursor(tool::INFO[useTool].cursor);
|
||||
|
||||
switch (useTool)
|
||||
{
|
||||
case tool::PAN:
|
||||
@@ -180,7 +180,7 @@ namespace anm2ed::spritesheet_editor
|
||||
if (isRight) frame->pivot.x += step;
|
||||
if (isUp) frame->pivot.y -= step;
|
||||
if (isDown) frame->pivot.y += step;
|
||||
if (isEnd) document.change(change::FRAMES);
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::CROP:
|
||||
if (!frame) break;
|
||||
@@ -191,7 +191,7 @@ namespace anm2ed::spritesheet_editor
|
||||
if (isRight) isMod ? frame->size.x += step : frame->crop.x += step;
|
||||
if (isUp) isMod ? frame->size.y -= step : frame->crop.y -= step;
|
||||
if (isDown) isMod ? frame->size.y += step : frame->crop.y += step;
|
||||
if (isEnd) document.change(change::FRAMES);
|
||||
if (isEnd) document.change(Document::FRAMES);
|
||||
break;
|
||||
case tool::DRAW:
|
||||
case tool::ERASE:
|
||||
@@ -200,7 +200,7 @@ namespace anm2ed::spritesheet_editor
|
||||
auto color = tool == tool::DRAW ? toolColor : vec4();
|
||||
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
|
||||
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
||||
if (isMouseReleased) document.change(change::FRAMES);
|
||||
if (isMouseReleased) document.change(Document::FRAMES);
|
||||
break;
|
||||
}
|
||||
case tool::COLOR_PICKER:
|
||||
@@ -5,15 +5,15 @@
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::spritesheet_editor
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class SpritesheetEditor : public canvas::Canvas
|
||||
class SpritesheetEditor : public Canvas
|
||||
{
|
||||
glm::vec2 mousePos{};
|
||||
glm::vec2 previousMousePos{};
|
||||
|
||||
public:
|
||||
SpritesheetEditor();
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&);
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
@@ -2,21 +2,13 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "toast.h"
|
||||
|
||||
using namespace anm2ed::anm2;
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::document;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::toast;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::spritesheets
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Spritesheets::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog,
|
||||
Clipboard& clipboard)
|
||||
@@ -175,7 +167,7 @@ namespace anm2ed::spritesheets
|
||||
spritesheetChildSize.y - spritesheetChildSize.y / 2 - ImGui::GetTextLineHeight() / 2));
|
||||
|
||||
if (isReferenced) ImGui::PushFont(resources.fonts[font::ITALICS].get(), font::SIZE);
|
||||
ImGui::Text(SPRITESHEET_FORMAT, id, path);
|
||||
ImGui::Text(anm2::SPRITESHEET_FORMAT_C, id, path);
|
||||
if (isReferenced) ImGui::PopFont();
|
||||
|
||||
context_menu();
|
||||
@@ -196,10 +188,10 @@ namespace anm2ed::spritesheets
|
||||
auto rowOneWidgetSize = imgui::widget_size_with_row_get(4);
|
||||
|
||||
imgui::shortcut(settings.shortcutAdd);
|
||||
if (ImGui::Button("Add", rowOneWidgetSize)) dialog.spritesheet_open();
|
||||
if (ImGui::Button("Add", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_OPEN);
|
||||
imgui::set_item_tooltip_shortcut("Add a new spritesheet.", settings.shortcutAdd);
|
||||
|
||||
if (dialog.is_selected_file(dialog::SPRITESHEET_OPEN))
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_OPEN))
|
||||
{
|
||||
document.spritesheet_add(dialog.path);
|
||||
dialog.reset();
|
||||
@@ -213,7 +205,7 @@ namespace anm2ed::spritesheets
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
{
|
||||
Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet.reload(document.directory_get());
|
||||
toasts.info(std::format("Reloaded spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
}
|
||||
@@ -226,16 +218,16 @@ namespace anm2ed::spritesheets
|
||||
|
||||
ImGui::BeginDisabled(multiSelect.size() != 1);
|
||||
{
|
||||
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.spritesheet_replace();
|
||||
if (ImGui::Button("Replace", rowOneWidgetSize)) dialog.file_open(dialog::SPRITESHEET_REPLACE);
|
||||
ImGui::SetItemTooltip("Replace the selected spritesheet with a new one.");
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (dialog.is_selected_file(dialog::SPRITESHEET_REPLACE))
|
||||
if (dialog.is_selected(dialog::SPRITESHEET_REPLACE))
|
||||
{
|
||||
auto& id = *multiSelect.begin();
|
||||
Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet = Spritesheet(document.directory_get(), dialog.path);
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
spritesheet = anm2::Spritesheet(document.directory_get(), dialog.path);
|
||||
toasts.info(std::format("Replaced spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
dialog.reset();
|
||||
}
|
||||
@@ -249,12 +241,12 @@ namespace anm2ed::spritesheets
|
||||
{
|
||||
for (auto& id : unused)
|
||||
{
|
||||
Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
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(change::SPRITESHEETS);
|
||||
document.change(Document::SPRITESHEETS);
|
||||
}
|
||||
imgui::set_item_tooltip_shortcut("Remove all unused spritesheets (i.e., not used in any layer.).",
|
||||
settings.shortcutRemove);
|
||||
@@ -289,7 +281,7 @@ namespace anm2ed::spritesheets
|
||||
{
|
||||
for (auto& id : multiSelect)
|
||||
{
|
||||
Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
anm2::Spritesheet& spritesheet = anm2.content.spritesheets[id];
|
||||
if (spritesheet.save(document.directory_get()))
|
||||
toasts.info(std::format("Saved spritesheet #{}: {}", id, spritesheet.path.string()));
|
||||
else
|
||||
16
src/imgui/window/spritesheets.h
Normal file
16
src/imgui/window/spritesheets.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "dialog.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Spritesheets
|
||||
{
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Dialog&, Clipboard& clipboard);
|
||||
};
|
||||
}
|
||||
@@ -4,17 +4,11 @@
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::playback;
|
||||
using namespace anm2ed::clipboard;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::timeline
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto ROOT_COLOR = ImVec4(0.140f, 0.310f, 0.560f, 1.000f);
|
||||
constexpr auto ROOT_COLOR_ACTIVE = ImVec4(0.240f, 0.520f, 0.880f, 1.000f);
|
||||
@@ -60,20 +54,16 @@ namespace anm2ed::timeline
|
||||
|
||||
auto copy = [&]()
|
||||
{
|
||||
if (auto frame = document.anm2.frame_get(hoveredFrame)) clipboard.set(frame->to_string(hoveredFrame.itemType));
|
||||
if (auto frame = document.anm2.frame_get(hoveredFrame))
|
||||
{
|
||||
clipboard.set(frame->to_string(hoveredFrame.itemType));
|
||||
}
|
||||
};
|
||||
|
||||
auto cut = [&]()
|
||||
{
|
||||
if (auto frame = document.anm2.frame_get(hoveredFrame))
|
||||
{
|
||||
if (auto item = document.anm2.item_get(hoveredFrame))
|
||||
{
|
||||
clipboard.set(frame->to_string(hoveredFrame.itemType));
|
||||
document.frames_delete(item);
|
||||
hoveredFrame = anm2::REFERENCE_DEFAULT;
|
||||
}
|
||||
}
|
||||
copy();
|
||||
document.frames_delete(document.item_get());
|
||||
};
|
||||
|
||||
auto paste = [&]() { document.frames_deserialize(clipboard.get()); };
|
||||
@@ -104,9 +94,10 @@ namespace anm2ed::timeline
|
||||
{
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
|
||||
auto item = animation ? animation->item_get(type, id) : nullptr;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto isVisible = item ? item->isVisible : false;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
|
||||
auto isActive = reference.itemType == type && reference.itemID == id;
|
||||
std::string label = "##None";
|
||||
@@ -179,6 +170,7 @@ namespace anm2ed::timeline
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
|
||||
anm2::Item* item = animation->item_get(type, id);
|
||||
bool& isVisible = item->isVisible;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
|
||||
@@ -187,7 +179,7 @@ namespace anm2ed::timeline
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
int visibleIcon = item->isVisible ? icon::VISIBLE : icon::INVISIBLE;
|
||||
int visibleIcon = isVisible ? icon::VISIBLE : icon::INVISIBLE;
|
||||
|
||||
if (ImGui::ImageButton("##Visible Toggle", resources.icons[visibleIcon].id, imgui::icon_size_get()))
|
||||
document.item_visible_toggle(item);
|
||||
@@ -214,31 +206,33 @@ namespace anm2ed::timeline
|
||||
else
|
||||
{
|
||||
auto cursorPos = ImGui::GetCursorPos();
|
||||
auto& isShowUnused = settings.timelineIsShowUnused;
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4());
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2());
|
||||
|
||||
ImGui::SetCursorPos(ImVec2(itemSize.x - ImGui::GetTextLineHeightWithSpacing() - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
auto& isShowUnused = settings.timelineIsShowUnused;
|
||||
auto unusedIcon = isShowUnused ? icon::SHOW_UNUSED : icon::HIDE_UNUSED;
|
||||
if (ImGui::ImageButton("##Unused Toggle", resources.icons[unusedIcon].id, imgui::icon_size_get()))
|
||||
isShowUnused = !isShowUnused;
|
||||
ImGui::SetItemTooltip(isShowUnused ? "Unused layers/nulls are shown. Press to hide."
|
||||
: "Unused layers/nulls are hidden. Press to show.");
|
||||
|
||||
auto onlyShowLayersIcon = isOnlyShowLayers ? icon::SHOW_LAYERS : icon::HIDE_LAYERS;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto layersIcon = isOnlyShowLayers ? icon::SHOW_LAYERS : icon::HIDE_LAYERS;
|
||||
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(itemSize.x - (ImGui::GetTextLineHeightWithSpacing() * 2) - ImGui::GetStyle().ItemSpacing.x,
|
||||
(itemSize.y - ImGui::GetTextLineHeightWithSpacing()) / 2));
|
||||
if (ImGui::ImageButton("##Layers Visibility Toggle", resources.icons[onlyShowLayersIcon].id,
|
||||
imgui::icon_size_get()))
|
||||
|
||||
if (ImGui::ImageButton("##Layers Toggle", resources.icons[layersIcon].id, imgui::icon_size_get()))
|
||||
isOnlyShowLayers = !isOnlyShowLayers;
|
||||
ImGui::SetItemTooltip(isOnlyShowLayers
|
||||
? "Only layers are visible. Press to toggle visibility for all items."
|
||||
: "Non-layer items are visible. Press to toggle visiblity only for layers.");
|
||||
ImGui::SetItemTooltip(isOnlyShowLayers ? "Only layers are visible. Press to show all items."
|
||||
: "All items are visible. Press to only show layers.");
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor(3);
|
||||
@@ -368,8 +362,8 @@ namespace anm2ed::timeline
|
||||
auto& reference = document.reference;
|
||||
auto& hoveredFrame = document.hoveredFrame;
|
||||
auto item = animation ? animation->item_get(type, id) : nullptr;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
auto isVisible = item ? item->isVisible : false;
|
||||
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
|
||||
if (isOnlyShowLayers && type != anm2::LAYER) isVisible = false;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing);
|
||||
@@ -521,8 +515,6 @@ namespace anm2ed::timeline
|
||||
auto isSelected = baseReference == frameReference;
|
||||
auto isFrameVisible = isVisible && frame.isVisible;
|
||||
|
||||
frameTime += frame.delay;
|
||||
|
||||
ImGui::PushID(i);
|
||||
auto size = ImVec2(frameSize.x * frame.delay, frameSize.y);
|
||||
|
||||
@@ -564,6 +556,8 @@ namespace anm2ed::timeline
|
||||
|
||||
drawList->AddImage(resources.icons[icon].id, imageMin, imageMax);
|
||||
|
||||
frameTime += frame.delay;
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
@@ -703,7 +697,7 @@ namespace anm2ed::timeline
|
||||
ImGui::SetCursorPos(
|
||||
ImVec2(ImGui::GetStyle().WindowPadding.x, ImGui::GetCursorPos().y + ImGui::GetStyle().ItemSpacing.y));
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(9);
|
||||
auto widgetSize = imgui::widget_size_with_row_get(10);
|
||||
|
||||
ImGui::BeginDisabled(!animation);
|
||||
{
|
||||
@@ -749,8 +743,8 @@ namespace anm2ed::timeline
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetNextItemWidth(widgetSize.x);
|
||||
ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), step::NORMAL,
|
||||
step::FAST, !animation ? ImGuiInputTextFlags_DisplayEmptyRefVal : 0);
|
||||
ImGui::InputInt("Animation Length", animation ? &animation->frameNum : &dummy_value<int>(), imgui::STEP,
|
||||
imgui::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.");
|
||||
|
||||
@@ -775,6 +769,12 @@ namespace anm2ed::timeline
|
||||
imgui::input_text_string("Author", &anm2.info.createdBy);
|
||||
ImGui::SetItemTooltip("Set the author of the document.");
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::SetNextItemWidth(widgetSize.x);
|
||||
ImGui::Checkbox("Sound", &settings.timelineIsSound);
|
||||
ImGui::SetItemTooltip("Toggle sounds playing with triggers.\nBind sounds to events in the Events window.");
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
@@ -961,9 +961,8 @@ namespace anm2ed::timeline
|
||||
|
||||
auto frame = document.frame_get();
|
||||
|
||||
ImGui::InputInt("Interval", &interval, step::NORMAL, step::FAST);
|
||||
imgui::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.");
|
||||
interval = glm::clamp(interval, anm2::FRAME_DELAY_MIN, frame ? frame->delay : anm2::FRAME_DELAY_MIN);
|
||||
|
||||
ImGui::Checkbox("Round Rotation", &isRoundRotation);
|
||||
ImGui::SetItemTooltip("Rotation will be rounded to the nearest whole number.");
|
||||
@@ -993,7 +992,6 @@ namespace anm2ed::timeline
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
auto& playback = document.playback;
|
||||
auto& anm2 = document.anm2;
|
||||
auto& reference = document.reference;
|
||||
auto animation = document.animation_get();
|
||||
|
||||
39
src/imgui/window/timeline.h
Normal file
39
src/imgui/window/timeline.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "document.h"
|
||||
#include "manager.h"
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Timeline
|
||||
{
|
||||
bool isDragging{};
|
||||
bool isWindowHovered{};
|
||||
bool isHorizontalScroll{};
|
||||
PopupHelper propertiesPopup{PopupHelper("Item Properties", POPUP_NORMAL)};
|
||||
PopupHelper bakePopup{PopupHelper("Bake", POPUP_TO_CONTENT)};
|
||||
std::string addItemName{};
|
||||
int addItemSpritesheetID{};
|
||||
bool addItemIsRect{};
|
||||
int addItemID{-1};
|
||||
bool isUnusedItemsSet{};
|
||||
std::set<int> unusedItems{};
|
||||
glm::vec2 scroll{};
|
||||
ImDrawList* pickerLineDrawList{};
|
||||
ImGuiStyle style{};
|
||||
|
||||
void context_menu(Document&, Settings&, Clipboard&);
|
||||
void item_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&);
|
||||
void items_child(Manager&, Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
||||
void frame_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&, anm2::Type, int, int&, float);
|
||||
void frames_child(Document&, anm2::Animation*, Settings&, Resources&, Clipboard&);
|
||||
|
||||
void popups(Document&, anm2::Animation*, Settings&);
|
||||
|
||||
public:
|
||||
void update(Manager&, Settings&, Resources&, Clipboard&);
|
||||
};
|
||||
}
|
||||
@@ -3,20 +3,15 @@
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "tool.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::resource;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::tools
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
constexpr auto COLOR_EDIT_LABEL = "##Color Edit";
|
||||
|
||||
void Tools::update(Manager& manager, Settings& settings, Resources& resources)
|
||||
{
|
||||
auto& document = *manager.get();
|
||||
@@ -29,21 +24,18 @@ namespace anm2ed::tools
|
||||
auto size = vec2(ImGui::GetTextLineHeightWithSpacing() * 1.5f);
|
||||
auto usedWidth = ImGui::GetStyle().WindowPadding.x;
|
||||
|
||||
auto tool_switch = [&](tool::Type type)
|
||||
auto tool_use = [&](tool::Type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case tool::UNDO:
|
||||
if (document.is_undo()) document.undo();
|
||||
if (document.is_able_to_undo()) document.undo();
|
||||
break;
|
||||
case tool::REDO:
|
||||
if (document.is_redo()) document.redo();
|
||||
if (document.is_able_to_redo()) document.redo();
|
||||
break;
|
||||
case tool::COLOR:
|
||||
if (ImGui::IsPopupOpen(COLOR_EDIT_LABEL))
|
||||
ImGui::CloseCurrentPopup();
|
||||
else
|
||||
isOpenColorEdit = true;
|
||||
colorEditPopup.open();
|
||||
break;
|
||||
default:
|
||||
settings.tool = type;
|
||||
@@ -60,23 +52,22 @@ namespace anm2ed::tools
|
||||
|
||||
auto member = SHORTCUT_MEMBERS[info.shortcut];
|
||||
|
||||
if (imgui::shortcut(settings.*member, shortcut::GLOBAL_SET)) tool_switch((tool::Type)i);
|
||||
if (shortcut(settings.*member, shortcut::GLOBAL_SET)) tool_use((tool::Type)i);
|
||||
|
||||
if (i == tool::COLOR)
|
||||
{
|
||||
size += to_vec2(ImGui::GetStyle().FramePadding) * 2.0f;
|
||||
if (ImGui::ColorButton(info.label, to_imvec4(settings.toolColor), ImGuiColorEditFlags_NoTooltip,
|
||||
to_imvec2(size)))
|
||||
tool_switch((tool::Type)i);
|
||||
tool_use((tool::Type)i);
|
||||
|
||||
colorEditPosition = ImGui::GetCursorScreenPos();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i == tool::UNDO) ImGui::BeginDisabled(!document.is_undo());
|
||||
if (i == tool::REDO) ImGui::BeginDisabled(!document.is_redo());
|
||||
if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size)))
|
||||
tool_switch((tool::Type)i);
|
||||
if (i == tool::UNDO) ImGui::BeginDisabled(!document.is_able_to_undo());
|
||||
if (i == tool::REDO) ImGui::BeginDisabled(!document.is_able_to_redo());
|
||||
if (ImGui::ImageButton(info.label, resources.icons[info.icon].id, to_imvec2(size))) tool_use((tool::Type)i);
|
||||
if (i == tool::UNDO) ImGui::EndDisabled();
|
||||
if (i == tool::REDO) ImGui::EndDisabled();
|
||||
}
|
||||
@@ -88,25 +79,18 @@ namespace anm2ed::tools
|
||||
ImGui::SameLine();
|
||||
else
|
||||
usedWidth = ImGui::GetStyle().WindowPadding.x;
|
||||
|
||||
imgui::set_item_tooltip_shortcut(info.tooltip, settings.*SHORTCUT_MEMBERS[info.shortcut]);
|
||||
set_item_tooltip_shortcut(info.tooltip, settings.*SHORTCUT_MEMBERS[info.shortcut]);
|
||||
|
||||
if (isSelected) ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
if (isOpenColorEdit)
|
||||
{
|
||||
ImGui::OpenPopup(COLOR_EDIT_LABEL);
|
||||
isOpenColorEdit = false;
|
||||
}
|
||||
colorEditPopup.trigger();
|
||||
|
||||
ImGui::SetNextWindowPos(colorEditPosition, ImGuiCond_None);
|
||||
|
||||
if (ImGui::BeginPopup(COLOR_EDIT_LABEL))
|
||||
if (ImGui::BeginPopup(colorEditPopup.label))
|
||||
{
|
||||
ImGui::ColorPicker4(COLOR_EDIT_LABEL, value_ptr(settings.toolColor));
|
||||
ImGui::ColorPicker4(colorEditPopup.label, value_ptr(settings.toolColor));
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,16 @@
|
||||
#include "resources.h"
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::tools
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Tools
|
||||
{
|
||||
bool isOpenColorEdit{};
|
||||
ImVec2 colorEditPosition{};
|
||||
|
||||
PopupHelper colorEditPopup{PopupHelper("##Color Edit", POPUP_TO_CONTENT, POPUP_BY_ITEM)};
|
||||
|
||||
public:
|
||||
void update(manager::Manager&, settings::Settings&, resources::Resources&);
|
||||
void update(Manager&, Settings&, Resources&);
|
||||
};
|
||||
}
|
||||
@@ -2,13 +2,9 @@
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::dialog;
|
||||
using namespace anm2ed::taskbar;
|
||||
using namespace anm2ed::documents;
|
||||
using namespace anm2ed::resources;
|
||||
using namespace anm2ed::manager;
|
||||
using namespace anm2ed::resource;
|
||||
|
||||
namespace anm2ed::welcome
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
void Welcome::update(Manager& manager, Resources& resources, Dialog& dialog, Taskbar& taskbar, Documents& documents)
|
||||
{
|
||||
@@ -19,28 +15,28 @@ namespace anm2ed::welcome
|
||||
ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + taskbar.height + documents.height));
|
||||
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, windowHeight));
|
||||
|
||||
if (ImGui::Begin("##Welcome", nullptr,
|
||||
if (ImGui::Begin("Welcome", nullptr,
|
||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoScrollWithMouse))
|
||||
{
|
||||
|
||||
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
|
||||
ImGui::Text("Anm2Ed");
|
||||
ImGui::TextUnformatted("Anm2Ed");
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::Text(
|
||||
"Select a recent file or an option below. You can also drag and drop files into the window to open them.");
|
||||
ImGui::TextUnformatted("Select a recent file or open a new or existing document. You can also drag and drop "
|
||||
"files into the window to open them.");
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("New", widgetSize)) dialog.anm2_new(); // handled in taskbar.cpp
|
||||
if (ImGui::Button("New", widgetSize)) dialog.file_open(dialog::ANM2_NEW); // handled in taskbar.cpp
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Open", widgetSize)) dialog.anm2_open(); // handled in taskbar.cpp
|
||||
if (ImGui::Button("Open", widgetSize)) dialog.file_open(dialog::ANM2_OPEN); // handled in taskbar.cpp
|
||||
|
||||
if (ImGui::BeginChild("##Recent Child", ImVec2(), ImGuiChildFlags_Borders))
|
||||
if (ImGui::BeginChild("##Recent Files Child", ImVec2(), ImGuiChildFlags_Borders))
|
||||
{
|
||||
for (auto [i, file] : std::views::enumerate(manager.recentFiles))
|
||||
{
|
||||
@@ -68,11 +64,11 @@ namespace anm2ed::welcome
|
||||
|
||||
if (ImGui::BeginPopupModal(restorePopup.label, &restorePopup.isOpen, ImGuiWindowFlags_NoResize))
|
||||
{
|
||||
ImGui::Text("Autosaved files detected. Would you like to restore them?");
|
||||
ImGui::TextUnformatted("Autosaved documents detected. Would you like to restore them?");
|
||||
|
||||
auto childSize = imgui::child_size_get(5);
|
||||
auto childSize = child_size_get(5);
|
||||
|
||||
if (ImGui::BeginChild("##Autosave Documents", childSize, ImGuiChildFlags_Borders,
|
||||
if (ImGui::BeginChild("##Restore Files Child", childSize, ImGuiChildFlags_Borders,
|
||||
ImGuiWindowFlags_HorizontalScrollbar))
|
||||
{
|
||||
for (auto& file : manager.autosaveFiles)
|
||||
@@ -83,7 +79,7 @@ namespace anm2ed::welcome
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto widgetSize = imgui::widget_size_with_row_get(2);
|
||||
auto widgetSize = widget_size_with_row_get(2);
|
||||
|
||||
if (ImGui::Button("Yes", widgetSize))
|
||||
{
|
||||
16
src/imgui/window/welcome.h
Normal file
16
src/imgui/window/welcome.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "documents.h"
|
||||
#include "manager.h"
|
||||
#include "taskbar.h"
|
||||
|
||||
namespace anm2ed::imgui
|
||||
{
|
||||
class Welcome
|
||||
{
|
||||
PopupHelper restorePopup{PopupHelper("Restore", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
public:
|
||||
void update(Manager&, Resources&, Dialog&, Taskbar&, Documents&);
|
||||
};
|
||||
};
|
||||
@@ -3,16 +3,17 @@
|
||||
#include <imgui/backends/imgui_impl_opengl3.h>
|
||||
#include <imgui/backends/imgui_impl_sdl3.h>
|
||||
|
||||
#include "filesystem.h"
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
|
||||
#include "filesystem_.h"
|
||||
#include "log.h"
|
||||
|
||||
using namespace anm2ed::log;
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::loader
|
||||
namespace anm2ed
|
||||
{
|
||||
std::string settings_path()
|
||||
std::string Loader::settings_path()
|
||||
{
|
||||
return filesystem::path_preferences_get() + "settings.ini";
|
||||
}
|
||||
@@ -22,6 +23,8 @@ namespace anm2ed::loader
|
||||
for (int i = 1; i < argc; i++)
|
||||
arguments.emplace_back(argv[i]);
|
||||
|
||||
settings = Settings(settings_path());
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||
{
|
||||
logger.fatal(std::format("Could not initialize SDL! {}", SDL_GetError()));
|
||||
@@ -29,7 +32,12 @@ namespace anm2ed::loader
|
||||
return;
|
||||
}
|
||||
|
||||
settings = Settings(settings_path());
|
||||
logger.info("Initialized SDL");
|
||||
|
||||
if (!MIX_Init())
|
||||
logger.warning(std::format("Could not initialize SDL_mixer! {}", SDL_GetError()));
|
||||
else
|
||||
logger.info("Initialized SDL_mixer");
|
||||
|
||||
window = SDL_CreateWindow("Anm2Ed", settings.windowSize.x, settings.windowSize.y,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
@@ -54,7 +62,7 @@ namespace anm2ed::loader
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(std::format("OpenGL {}", (const char*)glGetString(GL_VERSION)));
|
||||
logger.info(std::format("Initialized OpenGL {}", (const char*)glGetString(GL_VERSION)));
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
@@ -65,7 +73,15 @@ namespace anm2ed::loader
|
||||
glClearColor(color::BLACK.r, color::BLACK.g, color::BLACK.b, color::BLACK.a);
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
if (!ImGui::CreateContext())
|
||||
{
|
||||
logger.fatal("Could not initialize Dear ImGui!");
|
||||
isError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Initialized Dear ImGui");
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
ImGui_ImplSDL3_InitForOpenGL(window, glContext);
|
||||
@@ -81,13 +97,22 @@ namespace anm2ed::loader
|
||||
|
||||
Loader::~Loader()
|
||||
{
|
||||
settings.save(settings_path(), ImGui::SaveIniSettingsToMemory(nullptr));
|
||||
if (ImGui::GetCurrentContext())
|
||||
{
|
||||
settings.save(settings_path(), ImGui::SaveIniSettingsToMemory(nullptr));
|
||||
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
SDL_GL_DestroyContext(glContext);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
ImGui_ImplSDL3_Shutdown();
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
MIX_Quit();
|
||||
|
||||
if (SDL_WasInit(0))
|
||||
{
|
||||
SDL_GL_DestroyContext(glContext);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,16 @@
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
namespace anm2ed::loader
|
||||
namespace anm2ed
|
||||
{
|
||||
class Loader
|
||||
{
|
||||
std::string settings_path();
|
||||
|
||||
public:
|
||||
SDL_Window* window{};
|
||||
SDL_GLContext glContext{};
|
||||
settings::Settings settings;
|
||||
Settings settings;
|
||||
std::vector<std::string> arguments;
|
||||
bool isError{};
|
||||
|
||||
|
||||
17
src/log.cpp
17
src/log.cpp
@@ -2,13 +2,12 @@
|
||||
|
||||
#include <print>
|
||||
|
||||
#include "filesystem.h"
|
||||
#include "util.h"
|
||||
#include "filesystem_.h"
|
||||
#include "time_.h"
|
||||
|
||||
using namespace anm2ed::filesystem;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::log
|
||||
namespace anm2ed
|
||||
{
|
||||
void Logger::write(const Level level, const std::string& message)
|
||||
{
|
||||
@@ -37,6 +36,11 @@ namespace anm2ed::log
|
||||
write(FATAL, message);
|
||||
}
|
||||
|
||||
void Logger::command(const std::string& message)
|
||||
{
|
||||
write(COMMAND, message);
|
||||
}
|
||||
|
||||
void Logger::open(const std::filesystem::path& path)
|
||||
{
|
||||
file.open(path, std::ios::out | std::ios::app);
|
||||
@@ -44,7 +48,7 @@ namespace anm2ed::log
|
||||
|
||||
Logger::Logger()
|
||||
{
|
||||
open(path_preferences_get() + "log.txt");
|
||||
open(filesystem::path_preferences_get() + "log.txt");
|
||||
info("Initializing Anm2Ed");
|
||||
}
|
||||
|
||||
@@ -54,5 +58,6 @@ namespace anm2ed::log
|
||||
if (file.is_open()) file.close();
|
||||
}
|
||||
|
||||
Logger logger;
|
||||
}
|
||||
|
||||
anm2ed::Logger logger;
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace anm2ed::log
|
||||
namespace anm2ed
|
||||
{
|
||||
#define LEVELS \
|
||||
X(INFO, "[INFO]") \
|
||||
X(WARNING, "[WARNING]") \
|
||||
X(ERROR, "[ERROR]") \
|
||||
X(FATAL, "[FATAL]")
|
||||
X(FATAL, "[FATAL]") \
|
||||
X(COMMAND, "[COMMAND]")
|
||||
|
||||
enum Level
|
||||
{
|
||||
@@ -35,10 +36,12 @@ namespace anm2ed::log
|
||||
void warning(const std::string&);
|
||||
void error(const std::string&);
|
||||
void fatal(const std::string&);
|
||||
void command(const std::string&);
|
||||
void open(const std::filesystem::path&);
|
||||
Logger();
|
||||
~Logger();
|
||||
};
|
||||
|
||||
extern Logger logger;
|
||||
}
|
||||
|
||||
extern anm2ed::Logger logger;
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#include "loader.h"
|
||||
#include "state.h"
|
||||
|
||||
using namespace anm2ed::loader;
|
||||
using namespace anm2ed::state;
|
||||
|
||||
int main(int argc, const char** argv)
|
||||
{
|
||||
Loader loader(argc, argv);
|
||||
anm2ed::Loader loader(argc, argv);
|
||||
|
||||
if (loader.isError) return EXIT_FAILURE;
|
||||
|
||||
State state(loader.window, loader.arguments);
|
||||
anm2ed::State state(loader.window, loader.arguments);
|
||||
|
||||
while (!state.isQuit)
|
||||
state.loop(loader.window, loader.settings);
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "filesystem.h"
|
||||
#include "filesystem_.h"
|
||||
#include "log.h"
|
||||
#include "toast.h"
|
||||
#include "util.h"
|
||||
#include "vector_.h"
|
||||
|
||||
using namespace anm2ed::log;
|
||||
using namespace anm2ed::toast;
|
||||
using namespace anm2ed::types;
|
||||
using namespace anm2ed::util;
|
||||
|
||||
namespace anm2ed::manager
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr std::size_t RECENT_LIMIT = 10;
|
||||
|
||||
@@ -121,7 +119,7 @@ namespace anm2ed::manager
|
||||
selected = std::clamp(selected, 0, (int)documents.size() - 1);
|
||||
pendingSelected = selected;
|
||||
|
||||
if (selected >= 0 && selected < (int)documents.size()) documents[selected].change(change::ALL);
|
||||
if (selected >= 0 && selected < (int)documents.size()) documents[selected].change(Document::ALL);
|
||||
}
|
||||
|
||||
void Manager::set(int index)
|
||||
@@ -136,7 +134,7 @@ namespace anm2ed::manager
|
||||
index = std::clamp(index, 0, (int)documents.size() - 1);
|
||||
selected = index;
|
||||
|
||||
if (auto document = get()) document->change(change::ALL);
|
||||
if (auto document = get()) document->change(Document::ALL);
|
||||
}
|
||||
|
||||
void Manager::layer_properties_open(int id)
|
||||
@@ -262,7 +260,7 @@ namespace anm2ed::manager
|
||||
{
|
||||
document->isForceDirty = true;
|
||||
document->path = restorePath;
|
||||
document->change(change::ALL);
|
||||
document->change(Document::ALL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
#include <vector>
|
||||
|
||||
#include "document.h"
|
||||
#include "imgui.h"
|
||||
|
||||
using namespace anm2ed::document;
|
||||
|
||||
namespace anm2ed::manager
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr auto FILE_LABEL_FORMAT = "{} [{}]";
|
||||
|
||||
@@ -24,11 +21,17 @@ namespace anm2ed::manager
|
||||
int selected{-1};
|
||||
int pendingSelected{-1};
|
||||
|
||||
bool isRecording{};
|
||||
int recordingStart{};
|
||||
int recordingEnd{};
|
||||
|
||||
anm2::Layer editLayer{};
|
||||
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL, true)};
|
||||
imgui::PopupHelper layerPropertiesPopup{imgui::PopupHelper("Layer Properties", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
anm2::Null editNull{};
|
||||
imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL, true)};
|
||||
imgui::PopupHelper nullPropertiesPopup{imgui::PopupHelper("Null Properties", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
imgui::PopupHelper progressPopup{imgui::PopupHelper("Rendering...", imgui::POPUP_SMALL_NO_HEIGHT)};
|
||||
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "onionskin.h"
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace anm2ed::settings;
|
||||
using namespace anm2ed::types;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::onionskin
|
||||
{
|
||||
void Onionskin::update(Settings& settings)
|
||||
{
|
||||
if (ImGui::Begin("Onionskin", &settings.windowIsOnionskin))
|
||||
{
|
||||
auto order_configure = [&](const std::string& separator, int& frames, vec3& color)
|
||||
{
|
||||
ImGui::PushID(separator.c_str());
|
||||
ImGui::SeparatorText(separator.c_str());
|
||||
ImGui::InputInt("Frames", &frames, 1, 5);
|
||||
frames = glm::clamp(frames, 0, 100);
|
||||
ImGui::ColorEdit3("Color", value_ptr(color));
|
||||
ImGui::PopID();
|
||||
};
|
||||
|
||||
ImGui::Checkbox("Enabled", &settings.onionskinIsEnabled);
|
||||
|
||||
order_configure("Before", settings.onionskinBeforeCount, settings.onionskinBeforeColor);
|
||||
order_configure("After", settings.onionskinAfterCount, settings.onionskinAfterColor);
|
||||
|
||||
ImGui::Text("Order");
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Before", &settings.onionskinDrawOrder, draw_order::BELOW);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("After", &settings.onionskinDrawOrder, draw_order::ABOVE);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (imgui::shortcut(settings.shortcutOnionskin, shortcut::GLOBAL))
|
||||
settings.onionskinIsEnabled = !settings.onionskinIsEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <glm/common.hpp>
|
||||
|
||||
namespace anm2ed::playback
|
||||
namespace anm2ed
|
||||
{
|
||||
void Playback::toggle()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace anm2ed::playback
|
||||
namespace anm2ed
|
||||
{
|
||||
class Playback
|
||||
{
|
||||
|
||||
96
src/render.cpp
Normal file
96
src/render.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "render.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "util.h"
|
||||
#define POPEN _popen
|
||||
#define PCLOSE _pclose
|
||||
#define PWRITE_MODE "wb"
|
||||
#define PREAD_MODE "r"
|
||||
#else
|
||||
#define POPEN popen
|
||||
#define PCLOSE pclose
|
||||
#define PWRITE_MODE "w"
|
||||
#define PREAD_MODE "r"
|
||||
#endif
|
||||
|
||||
#include "log.h"
|
||||
|
||||
using namespace anm2ed::resource;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr auto FFMPEG_POPEN_ERROR = "popen() (for FFmpeg) failed!\n{}";
|
||||
|
||||
constexpr auto GIF_FORMAT = "\"{0}\" -y "
|
||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
||||
"-lavfi \"split[s0][s1];"
|
||||
"[s0]palettegen=stats_mode=full[p];"
|
||||
"[s1][p]paletteuse=dither=floyd_steinberg\" "
|
||||
"-loop 0 \"{4}\"";
|
||||
|
||||
constexpr auto WEBM_FORMAT = "\"{0}\" -y "
|
||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
||||
"-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 "
|
||||
"-auto-alt-ref 0 -an \"{4}\"";
|
||||
|
||||
constexpr auto* MP4_FORMAT = "\"{0}\" -y "
|
||||
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
|
||||
"-vf \"format=yuv420p,scale=trunc(iw/2)*2:trunc(ih/2)*2\" "
|
||||
"-c:v libx265 -crf 20 -preset slow "
|
||||
"-tag:v hvc1 -movflags +faststart -an \"{4}\"";
|
||||
|
||||
bool animation_render(const std::string& ffmpegPath, const std::string& path, std::vector<Texture>& frames,
|
||||
render::Type type, ivec2 size, int fps)
|
||||
{
|
||||
if (frames.empty() || size.x <= 0 || size.y <= 0 || fps <= 0 || ffmpegPath.empty() || path.empty()) return false;
|
||||
|
||||
std::string command{};
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case render::GIF:
|
||||
command = std::format(GIF_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
||||
break;
|
||||
case render::WEBM:
|
||||
command = std::format(WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
||||
break;
|
||||
case render::MP4:
|
||||
command = std::format(MP4_FORMAT, ffmpegPath, size.x, size.y, fps, path);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
command = string::quote(command);
|
||||
#endif
|
||||
|
||||
logger.command(command);
|
||||
|
||||
FILE* fp = POPEN(command.c_str(), PWRITE_MODE);
|
||||
|
||||
if (!fp)
|
||||
{
|
||||
logger.error(std::format(FFMPEG_POPEN_ERROR, strerror(errno)));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& frame : frames)
|
||||
{
|
||||
auto frameSize = frame.pixel_size_get();
|
||||
|
||||
if (fwrite(frame.pixels.data(), 1, frameSize, fp) != frameSize)
|
||||
{
|
||||
PCLOSE(fp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto code = PCLOSE(fp);
|
||||
return (code == 0);
|
||||
}
|
||||
}
|
||||
32
src/render.h
Normal file
32
src/render.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
namespace anm2ed::render
|
||||
{
|
||||
#define RENDER_LIST \
|
||||
X(PNGS, "PNGs") \
|
||||
X(GIF, "GIF") \
|
||||
X(WEBM, "WebM") \
|
||||
X(MP4, "MP4")
|
||||
|
||||
enum Type
|
||||
{
|
||||
#define X(symbol, string) symbol,
|
||||
RENDER_LIST
|
||||
#undef X
|
||||
COUNT
|
||||
};
|
||||
|
||||
constexpr const char* STRINGS[] = {
|
||||
#define X(symbol, string) string,
|
||||
RENDER_LIST
|
||||
#undef X
|
||||
};
|
||||
}
|
||||
|
||||
namespace anm2ed
|
||||
{
|
||||
bool animation_render(const std::string&, const std::string&, std::vector<resource::Texture>&, render::Type,
|
||||
glm::ivec2, int);
|
||||
}
|
||||
49
src/resource/audio.cpp
Normal file
49
src/resource/audio.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
MIX_Mixer* Audio::mixer_get()
|
||||
{
|
||||
static MIX_Mixer* mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, nullptr);
|
||||
return mixer;
|
||||
}
|
||||
|
||||
Audio::Audio(const char* path)
|
||||
{
|
||||
if (path && *path) internal = MIX_LoadAudio(mixer_get(), path, true);
|
||||
}
|
||||
|
||||
void Audio::unload()
|
||||
{
|
||||
if (!internal) return;
|
||||
MIX_DestroyAudio(internal);
|
||||
internal = nullptr;
|
||||
}
|
||||
|
||||
void Audio::play()
|
||||
{
|
||||
MIX_PlayAudio(mixer_get(), internal);
|
||||
}
|
||||
|
||||
Audio::Audio(Audio&& other) noexcept
|
||||
{
|
||||
internal = std::exchange(other.internal, nullptr);
|
||||
}
|
||||
|
||||
Audio& Audio::operator=(Audio&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
unload();
|
||||
internal = std::exchange(other.internal, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Audio::~Audio()
|
||||
{
|
||||
unload();
|
||||
}
|
||||
}
|
||||
24
src/resource/audio.h
Normal file
24
src/resource/audio.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
class Audio
|
||||
{
|
||||
MIX_Audio* internal{nullptr};
|
||||
MIX_Mixer* mixer_get();
|
||||
void unload();
|
||||
|
||||
public:
|
||||
Audio(const char*);
|
||||
~Audio();
|
||||
Audio() = default;
|
||||
Audio(Audio&&) noexcept;
|
||||
Audio& operator=(Audio&&) noexcept;
|
||||
Audio(const Audio&) = delete;
|
||||
Audio& operator=(const Audio&) = delete;
|
||||
|
||||
void play();
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "font.h"
|
||||
|
||||
namespace anm2ed::font
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
Font::Font() = default;
|
||||
|
||||
@@ -99,10 +99,10 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
#include <imgui/imgui.h>
|
||||
#include <iterator>
|
||||
|
||||
namespace anm2ed::font
|
||||
namespace anm2ed::resource::font
|
||||
{
|
||||
constexpr int SIZE = 16;
|
||||
constexpr int SIZE_LARGE = 32;
|
||||
constexpr auto SIZE = 16;
|
||||
constexpr auto SIZE_LARGE = 32;
|
||||
|
||||
enum Type
|
||||
{
|
||||
@@ -5224,6 +5224,10 @@ namespace anm2ed::font
|
||||
{.data = BOLD_DATA, .length = std::size(BOLD_DATA)},
|
||||
{.data = BOLD_ITALICS_DATA, .length = std::size(BOLD_ITALICS_DATA)}};
|
||||
|
||||
};
|
||||
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
class Font
|
||||
{
|
||||
ImFont* pointer{};
|
||||
@@ -5235,4 +5239,4 @@ namespace anm2ed::font
|
||||
ImFont* get();
|
||||
Font& operator=(Font&&) noexcept;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <cstring>
|
||||
#include <glm/ext/vector_int2.hpp>
|
||||
|
||||
namespace icon
|
||||
namespace anm2ed::resource::icon
|
||||
{
|
||||
constexpr auto SIZE_SMALL = glm::ivec2(64, 64);
|
||||
constexpr auto SIZE_NORMAL = glm::ivec2(128, 128);
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include "log.h"
|
||||
|
||||
using namespace anm2ed::log;
|
||||
|
||||
namespace anm2ed::shader
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
Shader::Shader() = default;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace anm2ed::shader
|
||||
namespace anm2ed::resource::shader
|
||||
{
|
||||
struct Info
|
||||
{
|
||||
@@ -202,7 +202,7 @@ namespace anm2ed::shader
|
||||
constexpr auto UNIFORM_DASH_GAP = "u_dash_gap";
|
||||
constexpr auto UNIFORM_DASH_OFFSET = "u_dash_offset";
|
||||
|
||||
enum Type
|
||||
enum ShaderType
|
||||
{
|
||||
LINE,
|
||||
DASHED,
|
||||
@@ -217,7 +217,10 @@ namespace anm2ed::shader
|
||||
{VERTEX, TEXTURE_FRAGMENT},
|
||||
{AXIS_VERTEX, FRAGMENT},
|
||||
{GRID_VERTEX, GRID_FRAGMENT}};
|
||||
}
|
||||
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
class Shader
|
||||
{
|
||||
public:
|
||||
@@ -20,49 +20,32 @@
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "math.h"
|
||||
#include "math_.h"
|
||||
|
||||
using namespace anm2ed::math;
|
||||
using namespace anm2ed::resource::texture;
|
||||
using namespace anm2ed::util::math;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::texture
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
bool Texture::is_valid()
|
||||
{
|
||||
return id != 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Texture::pixels_get()
|
||||
size_t Texture::pixel_size_get()
|
||||
{
|
||||
ensure_pixels();
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void Texture::download()
|
||||
{
|
||||
if (size.x <= 0 || size.y <= 0 || !is_valid()) return;
|
||||
pixels.resize(static_cast<size_t>(size.x) * static_cast<size_t>(size.y) * CHANNELS);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
isPixelsDirty = false;
|
||||
return size.x * size.y * CHANNELS;
|
||||
}
|
||||
|
||||
void Texture::upload(const uint8_t* data)
|
||||
{
|
||||
if (!data || size.x <= 0 || size.y <= 0) return;
|
||||
|
||||
const size_t pixelCount = static_cast<size_t>(size.x) * static_cast<size_t>(size.y) * CHANNELS;
|
||||
pixels.assign(data, data + pixelCount);
|
||||
upload();
|
||||
}
|
||||
|
||||
void Texture::upload()
|
||||
{
|
||||
if (pixels.empty() || size.x <= 0 || size.y <= 0) return;
|
||||
|
||||
if (!is_valid()) glGenTextures(1, &id);
|
||||
|
||||
pixels.assign(data, data + pixel_size_get());
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
@@ -74,7 +57,6 @@ namespace anm2ed::texture
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
isPixelsDirty = false;
|
||||
}
|
||||
|
||||
Texture::Texture() = default;
|
||||
@@ -94,35 +76,31 @@ namespace anm2ed::texture
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(const Texture& other)
|
||||
Texture& Texture::operator=(const Texture& other) // Copy
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (is_valid()) glDeleteTextures(1, &id);
|
||||
id = 0;
|
||||
other.ensure_pixels();
|
||||
size = other.size;
|
||||
filter = other.filter;
|
||||
channels = other.channels;
|
||||
pixels = other.pixels;
|
||||
if (!pixels.empty()) upload();
|
||||
upload(pixels.data());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(Texture&& other)
|
||||
Texture& Texture::operator=(Texture&& other) // Move
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (is_valid()) glDeleteTextures(1, &id);
|
||||
id = std::exchange(other.id, 0);
|
||||
size = std::exchange(other.size, {});
|
||||
id = other.id;
|
||||
size = other.size;
|
||||
filter = other.filter;
|
||||
channels = other.channels;
|
||||
pixels = std::move(other.pixels);
|
||||
isPixelsDirty = other.isPixelsDirty;
|
||||
other.isPixelsDirty = true;
|
||||
if (!pixels.empty()) upload();
|
||||
other.id = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -159,7 +137,6 @@ namespace anm2ed::texture
|
||||
|
||||
bool Texture::write_png(const std::string& path)
|
||||
{
|
||||
ensure_pixels();
|
||||
return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS);
|
||||
}
|
||||
|
||||
@@ -167,30 +144,26 @@ namespace anm2ed::texture
|
||||
{
|
||||
if (position.x < 0 || position.y < 0 || position.x >= size.x || position.y >= size.y) return;
|
||||
|
||||
ensure_pixels();
|
||||
uint8 rgba8[4] = {(uint8)float_to_uint8(color.r), (uint8)float_to_uint8(color.g), (uint8)float_to_uint8(color.b),
|
||||
(uint8)float_to_uint8(color.a)};
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
if (!pixels.empty())
|
||||
if (is_valid())
|
||||
{
|
||||
auto index = (position.y * size.x + position.x) * CHANNELS;
|
||||
pixels[index + 0] = rgba8[0];
|
||||
pixels[index + 1] = rgba8[1];
|
||||
pixels[index + 2] = rgba8[2];
|
||||
pixels[index + 3] = rgba8[3];
|
||||
isPixelsDirty = false;
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
if (pixels.size() == pixel_size_get())
|
||||
{
|
||||
size_t idx = (position.y * size.x + position.x) * CHANNELS;
|
||||
memcpy(&pixels[idx], rgba8, 4);
|
||||
}
|
||||
else
|
||||
isPixelsDirty = true;
|
||||
}
|
||||
|
||||
void Texture::pixel_line(ivec2 start, ivec2 end, vec4 color)
|
||||
{
|
||||
ensure_pixels();
|
||||
auto plot = [&](ivec2 pos) { pixel_set(pos, color); };
|
||||
|
||||
int x0 = start.x;
|
||||
@@ -221,23 +194,4 @@ namespace anm2ed::texture
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::ensure_pixels() const
|
||||
{
|
||||
if (size.x <= 0 || size.y <= 0) return;
|
||||
if (!pixels.empty() && !isPixelsDirty) return;
|
||||
const_cast<Texture*>(this)->download();
|
||||
}
|
||||
|
||||
void Texture::bind(GLuint unit)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
}
|
||||
|
||||
void Texture::unbind(GLuint unit)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,13 @@
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace anm2ed::texture
|
||||
namespace anm2ed::resource::texture
|
||||
{
|
||||
constexpr auto CHANNELS = 4;
|
||||
}
|
||||
|
||||
namespace anm2ed::resource
|
||||
{
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
@@ -17,13 +20,12 @@ namespace anm2ed::texture
|
||||
glm::ivec2 size{};
|
||||
GLint filter = GL_NEAREST;
|
||||
int channels{};
|
||||
mutable std::vector<uint8_t> pixels{};
|
||||
mutable bool isPixelsDirty{true};
|
||||
std::vector<uint8_t> pixels{};
|
||||
|
||||
bool is_valid();
|
||||
void download();
|
||||
void upload(const uint8_t*);
|
||||
size_t pixel_size_get();
|
||||
void upload();
|
||||
void upload(const uint8_t*);
|
||||
Texture();
|
||||
|
||||
~Texture();
|
||||
@@ -36,10 +38,6 @@ namespace anm2ed::texture
|
||||
Texture(const std::string&);
|
||||
bool write_png(const std::string&);
|
||||
void pixel_set(glm::ivec2, glm::vec4);
|
||||
void ensure_pixels() const;
|
||||
std::vector<uint8_t> pixels_get();
|
||||
void pixel_line(glm::ivec2, glm::ivec2, glm::vec4);
|
||||
void bind(GLuint = 0);
|
||||
void unbind(GLuint = 0);
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
#include "resources.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
using namespace anm2ed::texture;
|
||||
using namespace anm2ed::shader;
|
||||
using namespace anm2ed::font;
|
||||
using namespace anm2ed::resource;
|
||||
|
||||
namespace anm2ed::resources
|
||||
namespace anm2ed
|
||||
{
|
||||
Resources::Resources()
|
||||
{
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
#include "shader.h"
|
||||
#include "texture.h"
|
||||
|
||||
namespace anm2ed::resources
|
||||
namespace anm2ed
|
||||
{
|
||||
class Resources
|
||||
{
|
||||
public:
|
||||
font::Font fonts[font::COUNT]{};
|
||||
texture::Texture icons[icon::COUNT]{};
|
||||
shader::Shader shaders[shader::COUNT]{};
|
||||
resource::Font fonts[resource::font::COUNT]{};
|
||||
resource::Texture icons[resource::icon::COUNT]{};
|
||||
resource::Shader shaders[resource::shader::COUNT]{};
|
||||
|
||||
Resources();
|
||||
};
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include "filesystem.h"
|
||||
#include "filesystem_.h"
|
||||
#include "log.h"
|
||||
|
||||
using namespace anm2ed::filesystem;
|
||||
using namespace anm2ed::log;
|
||||
using namespace anm2ed::util;
|
||||
using namespace glm;
|
||||
|
||||
namespace anm2ed::settings
|
||||
namespace anm2ed
|
||||
{
|
||||
constexpr auto IMGUI_DEFAULT = R"(
|
||||
# Dear ImGui
|
||||
@@ -104,11 +103,9 @@ DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1584,852 Split=Y
|
||||
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,334 Selected=0x4F89F0DC
|
||||
)";
|
||||
|
||||
Settings::Settings() = default;
|
||||
|
||||
Settings::Settings(const std::string& path)
|
||||
{
|
||||
if (path_is_exist(path))
|
||||
if (filesystem::path_is_exist(path))
|
||||
logger.info(std::format("Using settings from: {}", path));
|
||||
else
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user