diff --git a/CMakeLists.txt b/CMakeLists.txt index 09e09cd..2c0d633 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ add_executable(${PROJECT_NAME} if(WIN32) enable_language(RC) - target_sources(${PROJECT_NAME} PRIVATE assets/Icon.rc) + target_sources(${PROJECT_NAME} PRIVATE Icon.rc) set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE) endif() @@ -72,4 +72,4 @@ target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL SDL3::SDL3) message("System: ${CMAKE_SYSTEM_NAME}") message("Project: ${PROJECT_NAME}") -message("Build: ${CMAKE_BUILD_TYPE}") \ No newline at end of file +message("Build: ${CMAKE_BUILD_TYPE}") diff --git a/Icon.ico b/Icon.ico new file mode 100644 index 0000000..c9105c0 Binary files /dev/null and b/Icon.ico differ diff --git a/Icon.rc b/Icon.rc new file mode 100644 index 0000000..0599d31 --- /dev/null +++ b/Icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "Icon.ico" diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/src/COMMON.h b/src/COMMON.h index 9c8948c..2c7999a 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -1,55 +1,39 @@ #pragma once +#define GLAD_GL_IMPLEMENTATION #include #include -#include #include -#include #include +#include #include -#include -#include +#include +#include #include -#include +#include #include -#include -#include +#include +#include #include -#include +#include #include -#include -#include -#include -#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; - -typedef int8_t s8; -typedef int16_t s16; -typedef int32_t s32; -typedef int64_t s64; - -typedef float f32; -typedef double f64; - -#define PI (GLM_PI) -#define TAU (PI * 2) - -using namespace glm; +using namespace glm; #define PREFERENCES_DIRECTORY "anm2ed" #define ROUND_NEAREST_MULTIPLE(value, multiple) (roundf((value) / (multiple)) * (multiple)) -#define FLOAT_TO_U8(x) (static_cast((x) * 255.0f)) -#define U8_TO_FLOAT(x) ((x) / 255.0f) +#define FLOAT_TO_UINT8(x) (static_cast((x) * 255.0f)) +#define UINT8_TO_FLOAT(x) ((x) / 255.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f) #define UNIT_TO_PERCENT(x) (x * 100.0f) #define SECOND 1000.0f @@ -62,15 +46,15 @@ using namespace glm; #define GL_ID_NONE 0 #ifdef _WIN32 - #define POPEN _popen - #define PCLOSE _pclose - #define PWRITE_MODE "wb" - #define PREAD_MODE "r" +#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" +#define POPEN popen +#define PCLOSE pclose +#define PWRITE_MODE "w" +#define PREAD_MODE "r" #endif static const GLuint GL_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0}; @@ -82,299 +66,254 @@ static const vec4 COLOR_OPAQUE = {1.0f, 1.0f, 1.0f, 1.0f}; static const vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 0.0f}; static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f}; -static inline std::string preferences_path_get(void) -{ - char* preferencesPath = SDL_GetPrefPath("", PREFERENCES_DIRECTORY); - std::string preferencesPathString = preferencesPath; - SDL_free(preferencesPath); - return preferencesPathString; +static inline std::string preferences_path_get(void) { + char* preferencesPath = SDL_GetPrefPath("", PREFERENCES_DIRECTORY); + std::string preferencesPathString = preferencesPath; + SDL_free(preferencesPath); + return preferencesPathString; } -static inline bool string_to_bool(const std::string& string) -{ - if (string == "1") return true; +static inline bool string_to_bool(const std::string& string) { + if (string == "1") + return true; - std::string lower = string; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - - return lower == "true"; + std::string lower = string; + std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + + return lower == "true"; } -static inline std::string string_quote(const std::string& string) -{ - return "\"" + string + "\""; -} +static inline std::string string_quote(const std::string& string) { return "\"" + string + "\""; } static inline std::string string_to_lowercase(std::string string) { - std::transform - ( - string.begin(), string.end(), string.begin(), - [](u8 character){ return std::tolower(character); } - ); - return string; + std::transform(string.begin(), string.end(), string.begin(), [](char character) { return std::tolower(character); }); + return string; } -static inline std::string string_backslash_replace(std::string string) -{ - for (char& character : string) - if (character == '\\') - character = '/'; - return string; +static inline std::string string_backslash_replace(std::string string) { + for (char& character : string) + if (character == '\\') + character = '/'; + return string; } #define FLOAT_FORMAT_MAX_DECIMALS 5 #define FLOAT_FORMAT_EPSILON 1e-5f -static constexpr f32 FLOAT_FORMAT_POW10[] = { - 1.f, - 10.f, - 100.f, - 1000.f, - 10000.f, - 100000.f +static constexpr float FLOAT_FORMAT_POW10[] = {1.f, 10.f, 100.f, 1000.f, 10000.f, 100000.f}; + +static inline int float_decimals_needed(float value) { + for (int decimalCount = 0; decimalCount <= FLOAT_FORMAT_MAX_DECIMALS; ++decimalCount) { + float scale = FLOAT_FORMAT_POW10[decimalCount]; + float rounded = roundf(value * scale) / scale; + if (fabsf(value - rounded) < FLOAT_FORMAT_EPSILON) + return decimalCount; + } + return FLOAT_FORMAT_MAX_DECIMALS; +} + +static inline const char* float_format_get(float value) { + static std::string formatString; + const int decimalCount = float_decimals_needed(value); + formatString = (decimalCount == 0) ? "%.0f" : ("%." + std::to_string(decimalCount) + "f"); + return formatString.c_str(); +} + +static inline const char* vec2_format_get(const vec2& value) { + static std::string formatString; + const int decimalCountX = float_decimals_needed(value.x); + const int decimalCountY = float_decimals_needed(value.y); + const int decimalCount = (decimalCountX > decimalCountY) ? decimalCountX : decimalCountY; + formatString = (decimalCount == 0) ? "%.0f" : ("%." + std::to_string(decimalCount) + "f"); + return formatString.c_str(); +} + +static inline std::string working_directory_from_file_set(const std::string& path) { + std::filesystem::path filePath = path; + std::filesystem::path parentPath = filePath.parent_path(); + std::filesystem::current_path(parentPath); + return parentPath.string(); }; -static inline s32 f32_decimals_needed(f32 value) -{ - for (s32 decimalCount = 0; decimalCount <= FLOAT_FORMAT_MAX_DECIMALS; ++decimalCount) - { - f32 scale = FLOAT_FORMAT_POW10[decimalCount]; - f32 rounded = roundf(value * scale) / scale; - if (fabsf(value - rounded) < FLOAT_FORMAT_EPSILON) - return decimalCount; - } - return FLOAT_FORMAT_MAX_DECIMALS; +static inline std::string path_extension_change(const std::string& path, const std::string& extension) { + std::filesystem::path filePath(path); + filePath.replace_extension(extension); + return filePath.string(); } -static inline const char* f32_format_get(f32 value) -{ - static std::string formatString; - const s32 decimalCount = f32_decimals_needed(value); - formatString = (decimalCount == 0) - ? "%.0f" - : ("%." + std::to_string(decimalCount) + "f"); - return formatString.c_str(); +static inline bool path_is_extension(const std::string& path, const std::string& extension) { + auto e = std::filesystem::path(path).extension().string(); + std::transform(e.begin(), e.end(), e.begin(), ::tolower); + return e == ("." + extension); } -static inline const char* vec2_format_get(const vec2& value) -{ - static std::string formatString; - const s32 decimalCountX = f32_decimals_needed(value.x); - const s32 decimalCountY = f32_decimals_needed(value.y); - const s32 decimalCount = (decimalCountX > decimalCountY) ? decimalCountX : decimalCountY; - formatString = (decimalCount == 0) - ? "%.0f" - : ("%." + std::to_string(decimalCount) + "f"); - return formatString.c_str(); +static inline bool path_exists(const std::filesystem::path& pathCheck) { + std::error_code errorCode; + return std::filesystem::exists(pathCheck, errorCode) && ((void)std::filesystem::status(pathCheck, errorCode), !errorCode); } -static inline std::string working_directory_from_file_set(const std::string& path) -{ - std::filesystem::path filePath = path; - std::filesystem::path parentPath = filePath.parent_path(); - std::filesystem::current_path(parentPath); - return parentPath.string(); +static inline bool path_is_valid(const std::filesystem::path& pathCheck) { + namespace fs = std::filesystem; + std::error_code ec; + + if (fs::is_directory(pathCheck, ec)) + return false; + + fs::path parentDir = pathCheck.has_parent_path() ? pathCheck.parent_path() : fs::path("."); + if (!fs::is_directory(parentDir, ec)) + return false; + + bool existedBefore = fs::exists(pathCheck, ec); + std::ofstream testStream(pathCheck, std::ios::app | std::ios::binary); + bool isValid = testStream.is_open(); + testStream.close(); + + if (!existedBefore && isValid) + fs::remove(pathCheck, ec); + + return isValid; +} + +static inline int string_to_enum(const std::string& string, const char* const* array, int n) { + for (int i = 0; i < n; i++) + if (string == array[i]) + return i; + return -1; }; -static inline std::string path_extension_change(const std::string& path, const std::string& extension) -{ - std::filesystem::path filePath(path); - filePath.replace_extension(extension); - return filePath.string(); +template T& dummy_value() { + static T value{}; + return value; } -static inline bool path_is_extension(const std::string& path, const std::string& extension) -{ - auto e = std::filesystem::path(path).extension().string(); - std::transform(e.begin(), e.end(), e.begin(), ::tolower); - return e == ("." + extension); +template static inline int map_next_id_get(const std::map& map) { + int id = 0; + + for (const auto& [key, value] : map) { + if (key != id) + break; + ++id; + } + + return id; } -static inline bool path_exists(const std::filesystem::path& pathCheck) -{ - std::error_code errorCode; - return std::filesystem::exists(pathCheck, errorCode) && ((void)std::filesystem::status(pathCheck, errorCode), !errorCode); +template static inline auto map_find(Map& map, typename Map::key_type id) -> typename Map::mapped_type* { + if (auto it = map.find(id); it != map.end()) + return &it->second; + return nullptr; } -static inline bool path_is_valid(const std::filesystem::path& pathCheck) -{ - namespace fs = std::filesystem; - std::error_code ec; +template static inline void map_swap(Map& map, const Key& key1, const Key& key2) { + if (key1 == key2) + return; - if (fs::is_directory(pathCheck, ec)) return false; + auto it1 = map.find(key1); + auto it2 = map.find(key2); - fs::path parentDir = pathCheck.has_parent_path() ? pathCheck.parent_path() : fs::path("."); - if (!fs::is_directory(parentDir, ec)) return false; - - bool existedBefore = fs::exists(pathCheck, ec); - std::ofstream testStream(pathCheck, std::ios::app | std::ios::binary); - bool isValid = testStream.is_open(); - testStream.close(); - - if (!existedBefore && isValid) - fs::remove(pathCheck, ec); - - return isValid; -} - -static inline s32 string_to_enum(const std::string& string, const char* const* array, s32 n) -{ - for (s32 i = 0; i < n; i++) - if (string == array[i]) - return i; - return -1; + if (it1 != map.end() && it2 != map.end()) { + using std::swap; + swap(it1->second, it2->second); + } else if (it1 != map.end()) { + map[key2] = std::move(it1->second); + map.erase(it1); + } else if (it2 != map.end()) { + map[key1] = std::move(it2->second); + map.erase(it2); + } }; -template -T& dummy_value() -{ - static T value{}; - return value; +template static inline void map_insert_shift(std::map& map, int index, const T& value) { + const int insertIndex = index + 1; + + std::vector> toShift; + for (auto it = map.rbegin(); it != map.rend(); ++it) { + if (it->first < insertIndex) + break; + toShift.emplace_back(it->first + 1, std::move(it->second)); + } + + for (const auto& [newKey, _] : toShift) + map.erase(newKey - 1); + + for (auto& [newKey, val] : toShift) + map[newKey] = std::move(val); + + map[insertIndex] = value; } -template -static inline s32 map_next_id_get(const std::map& map) -{ - s32 id = 0; - - for (const auto& [key, value] : map) - { - if (key != id) - break; - ++id; - } - - return id; +template static inline T* vector_find(std::vector& v, const T& value) { + auto it = std::find(v.begin(), v.end(), value); + return (it != v.end()) ? &(*it) : nullptr; } -template -static inline auto map_find(Map& map, typename Map::key_type id) - -> typename Map::mapped_type* -{ - if (auto it = map.find(id); it != map.end()) - return &it->second; - return nullptr; +template static inline void vector_value_erase(std::vector& v, const T& value) { v.erase(std::remove(v.begin(), v.end(), value), v.end()); } + +template static inline void vector_value_swap(std::vector& v, const T& a, const T& b) { + for (auto& element : v) { + if (element == a) + element = b; + else if (element == b) + element = a; + } } -template -static inline void map_swap(Map& map, const Key& key1, const Key& key2) -{ - if (key1 == key2) - return; - - auto it1 = map.find(key1); - auto it2 = map.find(key2); - - if (it1 != map.end() && it2 != map.end()) - { - using std::swap; - swap(it1->second, it2->second); - } - else if (it1 != map.end()) - { - map[key2] = std::move(it1->second); - map.erase(it1); - } - else if (it2 != map.end()) - { - map[key1] = std::move(it2->second); - map.erase(it2); - } -}; - -template -static inline void map_insert_shift(std::map& map, s32 index, const T& value) -{ - const s32 insertIndex = index + 1; - - std::vector> toShift; - for (auto it = map.rbegin(); it != map.rend(); ++it) - { - if (it->first < insertIndex) - break; - toShift.emplace_back(it->first + 1, std::move(it->second)); - } - - for (const auto& [newKey, _] : toShift) - map.erase(newKey - 1); - - for (auto& [newKey, val] : toShift) - map[newKey] = std::move(val); - - map[insertIndex] = value; +static inline void unordered_set_id_toggle(std::unordered_set& set, int id) { + if (auto it = set.find(id); it != set.end()) + set.erase(it); + else + set.insert(id); } -template -void vector_value_erase(std::vector& v, const T& value) -{ - v.erase(std::remove(v.begin(), v.end(), value), v.end()); +static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) { + vec2 scaleAbsolute = glm::abs(scale); + vec2 scaleSign = glm::sign(scale); + vec2 pivotScaled = pivot * scaleAbsolute; + vec2 sizeScaled = size * scaleAbsolute; + + mat4 model(1.0f); + model = glm::translate(model, vec3(position - pivotScaled, 0.0f)); + model = glm::translate(model, vec3(pivotScaled, 0.0f)); + model = glm::scale(model, vec3(scaleSign, 1.0f)); + model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1)); + model = glm::translate(model, vec3(-pivotScaled, 0.0f)); + model = glm::scale(model, vec3(sizeScaled, 1.0f)); + return model; } -template -void vector_value_swap(std::vector& v, const T& a, const T& b) -{ - for (auto& element : v) - { - if (element == a) element = b; - else if (element == b) element = a; - } +static inline mat4 quad_model_parent_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), float rotation = {}) { + vec2 scaleSign = glm::sign(scale); + vec2 scaleAbsolute = glm::abs(scale); + float handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; + + mat4 local(1.0f); + local = glm::translate(local, vec3(pivot, 0.0f)); + local = glm::scale(local, vec3(scaleSign, 1.0f)); + local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1)); + local = glm::translate(local, vec3(-pivot, 0.0f)); + local = glm::scale(local, vec3(scaleAbsolute, 1.0f)); + + return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; } -static inline mat4 quad_model_get(vec2 size = {}, vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {}) -{ - vec2 scaleAbsolute = glm::abs(scale); - vec2 scaleSign = glm::sign(scale); - vec2 pivotScaled = pivot * scaleAbsolute; - vec2 sizeScaled = size * scaleAbsolute; +#define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \ + static inline enumType function(const std::string& string) { return static_cast(string_to_enum(string, stringArray, count)); }; - mat4 model(1.0f); - model = glm::translate(model, vec3(position - pivotScaled, 0.0f)); - model = glm::translate(model, vec3(pivotScaled, 0.0f)); - model = glm::scale(model, vec3(scaleSign, 1.0f)); - model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1)); - model = glm::translate(model, vec3(-pivotScaled, 0.0f)); - model = glm::scale(model, vec3(sizeScaled, 1.0f)); - return model; -} +#define DATATYPE_LIST \ + X(TYPE_INT, int) \ + X(TYPE_BOOL, bool) \ + X(TYPE_FLOAT, float) \ + X(TYPE_STRING, std::string) \ + X(TYPE_IVEC2, ivec2) \ + X(TYPE_IVEC2_WH, ivec2) \ + X(TYPE_VEC2, vec2) \ + X(TYPE_VEC2_WH, vec2) \ + X(TYPE_VEC3, vec3) \ + X(TYPE_VEC4, vec4) -static inline mat4 quad_model_parent_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {}) -{ - vec2 scaleSign = glm::sign(scale); - vec2 scaleAbsolute = glm::abs(scale); - f32 handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; - - mat4 local(1.0f); - local = glm::translate(local, vec3(pivot, 0.0f)); - local = glm::scale(local, vec3(scaleSign, 1.0f)); - local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1)); - local = glm::translate(local, vec3(-pivot, 0.0f)); - local = glm::scale(local, vec3(scaleAbsolute, 1.0f)); - - return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; -} - -#define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \ - static inline enumType function(const std::string& string) \ - { \ - return static_cast(string_to_enum(string, stringArray, count)); \ - }; - -#define DATATYPE_LIST \ - X(TYPE_INT, s32) \ - X(TYPE_BOOL, bool) \ - X(TYPE_FLOAT, f32) \ - X(TYPE_STRING, std::string) \ - X(TYPE_IVEC2, ivec2) \ - X(TYPE_IVEC2_WH, ivec2) \ - X(TYPE_VEC2, vec2) \ - X(TYPE_VEC2_WH, vec2) \ - X(TYPE_VEC3, vec3) \ - X(TYPE_VEC4, vec4) - -enum DataType -{ - #define X(symbol, ctype) symbol, - DATATYPE_LIST - #undef X +enum DataType { +#define X(symbol, ctype) symbol, + DATATYPE_LIST +#undef X }; #define DATATYPE_TO_CTYPE(dt) DATATYPE_CTYPE_##dt @@ -382,8 +321,13 @@ enum DataType DATATYPE_LIST #undef X -enum OriginType -{ - ORIGIN_TOP_LEFT, - ORIGIN_CENTER +enum OriginType { ORIGIN_TOP_LEFT, ORIGIN_CENTER }; + +struct WorkingDirectory { + std::filesystem::path previous; + WorkingDirectory(const std::string& file) { + previous = std::filesystem::current_path(); + working_directory_from_file_set(file); + } + ~WorkingDirectory() { std::filesystem::current_path(previous); } }; \ No newline at end of file diff --git a/src/PACKED.h b/src/PACKED.h deleted file mode 100644 index d161fb2..0000000 --- a/src/PACKED.h +++ /dev/null @@ -1,347 +0,0 @@ -#pragma once - -#include "COMMON.h" - -const u8 TEXTURE_ATLAS[] = -{ - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, - 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xa0, - 0x04, 0x03, 0x00, 0x00, 0x00, 0x01, 0x5e, 0x74, 0xbf, 0x00, 0x00, 0x00, - 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, - 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c, - 0x54, 0x45, 0x00, 0x00, 0x00, 0x76, 0x76, 0x76, 0xff, 0xff, 0xff, 0x60, - 0x60, 0x60, 0xff, 0xff, 0xff, 0x3e, 0xd5, 0x47, 0x6d, 0x00, 0x00, 0x00, - 0x03, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x00, 0xfa, 0x76, 0xc4, 0xde, - 0x00, 0x00, 0x04, 0x62, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x59, - 0x5b, 0x6e, 0x24, 0x37, 0x0c, 0x2c, 0x80, 0xbc, 0x40, 0xee, 0x90, 0x03, - 0x10, 0x20, 0x0f, 0xc0, 0x80, 0x75, 0xff, 0x33, 0xe5, 0x83, 0xa2, 0x5a, - 0x63, 0xcf, 0xae, 0xa7, 0x37, 0xb1, 0x03, 0x6c, 0xac, 0x0f, 0x77, 0x6b, - 0x5a, 0xd5, 0x7c, 0x15, 0x49, 0xb5, 0x0c, 0xfc, 0xc2, 0x20, 0x9f, 0xff, - 0xae, 0xd9, 0xd7, 0x28, 0x00, 0xe0, 0xb1, 0x9e, 0x04, 0x40, 0x9a, 0x92, - 0x76, 0xac, 0x27, 0xf3, 0x78, 0x21, 0xbd, 0x00, 0x01, 0xa0, 0x04, 0x98, - 0xd0, 0x46, 0x26, 0x00, 0x04, 0x49, 0xe8, 0x9e, 0x36, 0xc0, 0x95, 0x80, - 0x2e, 0x51, 0x64, 0xdf, 0xee, 0x15, 0xa4, 0x92, 0x83, 0x58, 0x2a, 0x29, - 0x4b, 0xd8, 0x00, 0x9d, 0x17, 0xf6, 0x02, 0xc9, 0x56, 0x68, 0xe6, 0x6d, - 0x4b, 0xbf, 0xa2, 0x67, 0x4c, 0x4d, 0x35, 0xec, 0xb5, 0x89, 0x41, 0x24, - 0x9e, 0x01, 0x94, 0xd0, 0xd4, 0xb6, 0x34, 0x35, 0x7b, 0x81, 0x78, 0x90, - 0xe5, 0xf6, 0x08, 0x28, 0x40, 0x13, 0xcc, 0x13, 0x50, 0x08, 0x03, 0x20, - 0xee, 0x51, 0xee, 0x06, 0x48, 0xfb, 0x8e, 0x61, 0x64, 0x02, 0x9a, 0x4a, - 0x1c, 0x00, 0xe1, 0xf2, 0xb8, 0xb8, 0xbb, 0x1b, 0x80, 0x48, 0xad, 0xb6, - 0x75, 0x59, 0xcf, 0x04, 0xda, 0x4b, 0x00, 0x12, 0x03, 0x30, 0x77, 0x37, - 0x33, 0x21, 0x01, 0xd2, 0xae, 0xc0, 0x75, 0xd4, 0x2e, 0x40, 0xb0, 0x00, - 0x40, 0x44, 0xc4, 0x45, 0xa0, 0x4c, 0x40, 0x99, 0x17, 0x60, 0x02, 0x37, - 0x5e, 0xd2, 0x15, 0x5f, 0x98, 0x99, 0xd9, 0x13, 0xc0, 0xa2, 0x86, 0xb4, - 0xdf, 0xed, 0x22, 0x8d, 0x88, 0x00, 0x10, 0x5a, 0xff, 0xe1, 0xc7, 0xe4, - 0x83, 0xe1, 0x8b, 0xc6, 0xd0, 0x5c, 0xde, 0x48, 0x94, 0x07, 0x72, 0x5f, - 0xf3, 0xa1, 0xf9, 0xdb, 0xe7, 0x93, 0x17, 0xc1, 0xa6, 0x80, 0x93, 0xa4, - 0x5f, 0x9c, 0x92, 0xe5, 0x13, 0xbc, 0x71, 0x2f, 0x0a, 0x00, 0x02, 0x08, - 0x02, 0xac, 0xcd, 0x5a, 0x09, 0xba, 0xb3, 0xec, 0x12, 0x45, 0x77, 0x27, - 0x81, 0x26, 0x28, 0x40, 0xeb, 0x3c, 0x19, 0x40, 0x3f, 0xb7, 0x2d, 0x4a, - 0xe8, 0x1e, 0xa4, 0x0d, 0x40, 0x3a, 0x4f, 0x6c, 0xab, 0xd4, 0xcf, 0x01, - 0x68, 0xb9, 0x47, 0x42, 0xcb, 0x9d, 0x15, 0x39, 0x80, 0xc9, 0x93, 0xa1, - 0xf9, 0x7e, 0x2e, 0x15, 0x51, 0x15, 0xa6, 0xe5, 0x41, 0x8f, 0x44, 0x42, - 0xb3, 0x01, 0x4a, 0x68, 0x0e, 0xcd, 0xf7, 0x73, 0x9a, 0x6a, 0xa6, 0x30, - 0xd2, 0x59, 0xae, 0xd5, 0x00, 0xd9, 0x79, 0x32, 0x34, 0xd7, 0x72, 0x96, - 0x47, 0x42, 0x52, 0x4d, 0x52, 0x4d, 0x4b, 0x09, 0x8c, 0x4a, 0x57, 0x9e, - 0xc0, 0xdc, 0x1d, 0x80, 0x66, 0xd0, 0xa1, 0x09, 0x28, 0x48, 0x24, 0x84, - 0x2c, 0xf7, 0x65, 0x34, 0x09, 0x21, 0x13, 0x4e, 0x53, 0x29, 0xb1, 0x9e, - 0x97, 0x39, 0x01, 0x48, 0x01, 0x61, 0x00, 0xe9, 0xee, 0xed, 0x56, 0x8d, - 0x4a, 0x90, 0x10, 0xa7, 0x81, 0x66, 0x6b, 0x6e, 0xe2, 0xb4, 0x0e, 0x6d, - 0x01, 0x50, 0xba, 0xaf, 0xc0, 0x95, 0x66, 0x20, 0x08, 0x67, 0x39, 0x28, - 0xc7, 0x3c, 0xfd, 0x19, 0x35, 0x4c, 0x4d, 0xb2, 0xa9, 0x01, 0x53, 0x24, - 0xd4, 0x24, 0x05, 0x24, 0xf9, 0xc0, 0xbf, 0x83, 0x5c, 0x64, 0xcf, 0xdd, - 0xcf, 0x8c, 0xb9, 0xe6, 0xbf, 0xca, 0xf7, 0xb9, 0xbe, 0xdc, 0x1f, 0xd6, - 0x75, 0xa9, 0xa6, 0x49, 0x4d, 0x5e, 0x57, 0x00, 0x40, 0x57, 0xc2, 0x29, - 0xa1, 0x47, 0xed, 0xc6, 0x94, 0x9d, 0x7d, 0x5d, 0xc5, 0xcd, 0xde, 0xab, - 0xb6, 0xd8, 0xa9, 0xfd, 0x86, 0x5d, 0x11, 0x01, 0x40, 0xcd, 0xed, 0xf4, - 0xce, 0x2a, 0xba, 0xec, 0x6a, 0xd7, 0x45, 0x71, 0xcc, 0x4a, 0x00, 0x28, - 0x0a, 0x80, 0x0c, 0x93, 0x52, 0x6c, 0x96, 0xae, 0xc2, 0xa5, 0x00, 0xa2, - 0x25, 0x6d, 0x0d, 0x82, 0x06, 0x68, 0x16, 0x10, 0x9a, 0x9a, 0x88, 0x82, - 0xa6, 0x76, 0xfa, 0xa5, 0x2e, 0x09, 0x87, 0x4a, 0xc5, 0x4a, 0x80, 0x99, - 0x9a, 0xba, 0x12, 0x91, 0xa4, 0x76, 0x8c, 0xba, 0x09, 0xc5, 0xa9, 0x92, - 0x06, 0x03, 0xcf, 0x01, 0xd9, 0x12, 0x98, 0x38, 0x55, 0x6a, 0x01, 0x4f, - 0x55, 0xa2, 0x32, 0xb5, 0x99, 0xd5, 0x2a, 0x69, 0x26, 0xd0, 0x02, 0xde, - 0x19, 0x2d, 0x4c, 0xb0, 0x7b, 0x76, 0x8e, 0x77, 0x3a, 0x6f, 0xb4, 0xf2, - 0x0d, 0xe9, 0xc6, 0xad, 0xb9, 0xda, 0xf4, 0x14, 0x9c, 0xbe, 0x67, 0xc6, - 0x73, 0x4e, 0x29, 0x93, 0xcc, 0x01, 0xe4, 0x05, 0xe0, 0xdb, 0x2d, 0xc2, - 0xa6, 0xc6, 0x29, 0x61, 0xab, 0x24, 0x91, 0x3f, 0x22, 0x5f, 0xef, 0x08, - 0x94, 0x4c, 0x6a, 0x02, 0xd4, 0xa4, 0xe6, 0x43, 0xe9, 0xbe, 0x4d, 0xef, - 0x7f, 0x3a, 0x0e, 0x39, 0xba, 0xb3, 0xf7, 0xb1, 0x8b, 0x2d, 0xfd, 0xd4, - 0xa2, 0x1e, 0x15, 0x5b, 0xf7, 0xf4, 0x01, 0x58, 0xff, 0xb0, 0xe6, 0x52, - 0x5c, 0x3d, 0xe2, 0xec, 0x19, 0xd0, 0xea, 0x38, 0x09, 0x6b, 0x3d, 0x94, - 0x58, 0x8b, 0x62, 0xf7, 0x83, 0x69, 0x03, 0x4c, 0xb0, 0x37, 0x0c, 0x10, - 0xa6, 0x5a, 0x2f, 0xf4, 0xae, 0x64, 0x50, 0xb0, 0xb0, 0x38, 0x88, 0x7d, - 0x33, 0xb5, 0x27, 0x3c, 0xb5, 0x2b, 0x9f, 0x04, 0xe9, 0x6d, 0xad, 0xda, - 0x74, 0x9c, 0x01, 0x24, 0x9c, 0x4e, 0x27, 0x84, 0x4e, 0x76, 0x4a, 0x1b, - 0xbb, 0x75, 0x69, 0xd7, 0xd9, 0xde, 0xb5, 0xf5, 0x0e, 0x46, 0x89, 0x01, - 0x28, 0x49, 0x46, 0x02, 0x10, 0xea, 0x08, 0xb0, 0x8c, 0xe5, 0x19, 0x4d, - 0x9d, 0xbd, 0x59, 0x1b, 0x0c, 0x05, 0xd9, 0xfc, 0x0e, 0xe6, 0x08, 0xd0, - 0x1d, 0x87, 0x01, 0x00, 0x70, 0x77, 0x77, 0x87, 0x82, 0xac, 0x55, 0x41, - 0x29, 0x23, 0xe0, 0x08, 0xc0, 0x0e, 0x87, 0x01, 0x02, 0x81, 0x92, 0x5e, - 0xab, 0xc4, 0xb6, 0x1f, 0x1e, 0x0a, 0xc4, 0xb2, 0xe1, 0x04, 0xa4, 0x7a, - 0x0d, 0xab, 0x97, 0x26, 0x79, 0xf1, 0x9d, 0xd7, 0x26, 0x76, 0x01, 0xba, - 0x26, 0x74, 0xe0, 0xcb, 0x05, 0x86, 0x38, 0x13, 0xe4, 0x00, 0x2c, 0x1b, - 0x00, 0x19, 0x80, 0xb8, 0xd9, 0x63, 0xc9, 0x9c, 0x48, 0x7f, 0x15, 0xdd, - 0xf1, 0x3e, 0xbd, 0x24, 0x5f, 0x04, 0x0c, 0x68, 0x95, 0xd3, 0xb1, 0xc0, - 0xba, 0xbe, 0x82, 0xf5, 0x03, 0xc0, 0x2a, 0xa7, 0xcc, 0x8f, 0x00, 0x57, - 0x5d, 0x99, 0x52, 0xf1, 0x1a, 0x40, 0xb9, 0xdb, 0x74, 0xfe, 0x04, 0x70, - 0xd4, 0xc6, 0xb2, 0x01, 0x74, 0x33, 0x69, 0x00, 0xd7, 0xde, 0x6f, 0xae, - 0xdb, 0x06, 0xd9, 0x1f, 0x4d, 0x9d, 0x9b, 0x3f, 0x03, 0x6c, 0x22, 0xd4, - 0x00, 0xea, 0xc7, 0x80, 0x63, 0x38, 0x73, 0x01, 0xca, 0x2e, 0x95, 0xdc, - 0x59, 0xee, 0x57, 0x3e, 0x1c, 0x05, 0x79, 0xdc, 0xba, 0x32, 0xf5, 0xb9, - 0xd1, 0xba, 0xea, 0xed, 0xe9, 0xd6, 0x36, 0xfe, 0x63, 0xc0, 0xaa, 0x83, - 0x6b, 0xfd, 0x0b, 0x80, 0xfe, 0xd4, 0xc0, 0xec, 0x0a, 0xc4, 0x6d, 0xed, - 0x10, 0x1e, 0x36, 0x3b, 0xa7, 0x4a, 0xaf, 0x91, 0xf5, 0xb1, 0x0b, 0xdd, - 0x18, 0xfe, 0x66, 0xd8, 0xfb, 0x57, 0x3f, 0x07, 0x8c, 0x05, 0x1f, 0x02, - 0x82, 0x13, 0xe2, 0xaf, 0x02, 0xe0, 0x1b, 0xf0, 0x7b, 0x00, 0xd6, 0xde, - 0xef, 0x65, 0x80, 0xae, 0xae, 0xfd, 0x32, 0x80, 0xa9, 0x7c, 0xd8, 0x7e, - 0x7c, 0x08, 0xe8, 0xcf, 0xbf, 0x97, 0x01, 0xfd, 0xbd, 0x78, 0x9e, 0x07, - 0xfc, 0x7b, 0x00, 0x3e, 0x19, 0xff, 0x31, 0xe0, 0xf3, 0x8d, 0xfe, 0xc5, - 0x38, 0xdc, 0x8f, 0xf4, 0x6d, 0x2e, 0xdd, 0x66, 0xeb, 0xef, 0x99, 0xd3, - 0x73, 0x36, 0xf6, 0x32, 0x60, 0xce, 0x93, 0x5e, 0x07, 0xac, 0xd3, 0x9f, - 0x97, 0xfb, 0xc3, 0x9c, 0x6b, 0xdc, 0x00, 0xf4, 0x97, 0xe2, 0x0d, 0x40, - 0x9f, 0x59, 0xde, 0x01, 0x88, 0xe1, 0x5d, 0x37, 0xfd, 0x29, 0xe0, 0x7b, - 0x7c, 0x8f, 0xaf, 0x19, 0x77, 0x3f, 0xb6, 0xf6, 0x3f, 0x15, 0x5e, 0x1c, - 0xca, 0x9b, 0x04, 0x27, 0x70, 0xeb, 0xc0, 0x63, 0x8e, 0x1d, 0x3e, 0x0f, - 0xb0, 0x56, 0xfe, 0x99, 0x77, 0x00, 0x7f, 0xdc, 0x05, 0xfc, 0xf5, 0xe9, - 0x12, 0x6e, 0x01, 0x3e, 0xdf, 0xad, 0xb7, 0x23, 0x7d, 0x9f, 0x4b, 0xb7, - 0xd9, 0x7a, 0x3f, 0x1f, 0xbe, 0xc7, 0xff, 0x75, 0xfc, 0x0d, 0x3b, 0xd4, - 0xd5, 0x4b, 0x3b, 0xfe, 0xb6, 0x75, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, - 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 -}; - -const u32 TEXTURE_ATLAS_LENGTH = (u32)std::size(TEXTURE_ATLAS); -const vec2 TEXTURE_ATLAS_SIZE = {96, 160}; - -enum AtlasType -{ - ATLAS_NONE, - ATLAS_FOLDER, - ATLAS_ROOT, - ATLAS_LAYER, - ATLAS_NULL, - ATLAS_TRIGGERS, - ATLAS_VISIBLE, - ATLAS_INVISIBLE, - ATLAS_SHOW_RECT, - ATLAS_HIDE_RECT, - ATLAS_SHOW_UNUSED, - ATLAS_HIDE_UNUSED, - ATLAS_PAN, - ATLAS_MOVE, - ATLAS_ROTATE, - ATLAS_SCALE, - ATLAS_CROP, - ATLAS_DRAW, - ATLAS_ERASE, - ATLAS_COLOR_PICKER, - ATLAS_UNDO, - ATLAS_REDO, - ATLAS_ANIMATION, - ATLAS_SPRITESHEET, - ATLAS_EVENT, - ATLAS_PLAY, - ATLAS_PAUSE, - ATLAS_ADD, - ATLAS_REMOVE, - ATLAS_TRIGGER, - ATLAS_PIVOT, - ATLAS_SQUARE, - ATLAS_CIRCLE, - ATLAS_PICKER, - ATLAS_FRAME, - ATLAS_FRAME_ALT, - ATLAS_TARGET, - ATLAS_TARGET_ALT, - ATLAS_COUNT -}; - -struct AtlasEntry -{ - vec2 position; - vec2 size; -}; - -const vec2 ATLAS_SIZE_SMALL = {8, 8}; -const vec2 ATLAS_SIZE_NORMAL = {16, 16}; -const vec2 ATLAS_SIZE_OBLONG = {16, 40}; -const vec2 ATLAS_SIZE_BIG = {40, 40}; - -const inline AtlasEntry ATLAS_ENTRIES[ATLAS_COUNT] = -{ - {{ 0, 0}, ATLAS_SIZE_NORMAL}, - {{ 16, 0}, ATLAS_SIZE_NORMAL}, - {{ 32, 0}, ATLAS_SIZE_NORMAL}, - {{ 48, 0}, ATLAS_SIZE_NORMAL}, - {{ 64, 0}, ATLAS_SIZE_NORMAL}, - {{ 80, 0}, ATLAS_SIZE_NORMAL}, - {{ 0, 16}, ATLAS_SIZE_NORMAL}, - {{ 16, 16}, ATLAS_SIZE_NORMAL}, - {{ 32, 16}, ATLAS_SIZE_NORMAL}, - {{ 48, 16}, ATLAS_SIZE_NORMAL}, - {{ 64, 16}, ATLAS_SIZE_NORMAL}, - {{ 80, 16}, ATLAS_SIZE_NORMAL}, - {{ 0, 32}, ATLAS_SIZE_NORMAL}, - {{ 16, 32}, ATLAS_SIZE_NORMAL}, - {{ 32, 32}, ATLAS_SIZE_NORMAL}, - {{ 48, 32}, ATLAS_SIZE_NORMAL}, - {{ 64, 32}, ATLAS_SIZE_NORMAL}, - {{ 80, 32}, ATLAS_SIZE_NORMAL}, - {{ 0, 48}, ATLAS_SIZE_NORMAL}, - {{ 16, 48}, ATLAS_SIZE_NORMAL}, - {{ 32, 48}, ATLAS_SIZE_NORMAL}, - {{ 48, 48}, ATLAS_SIZE_NORMAL}, - {{ 64, 48}, ATLAS_SIZE_NORMAL}, - {{ 80, 48}, ATLAS_SIZE_NORMAL}, - {{ 0, 64}, ATLAS_SIZE_NORMAL}, - {{ 16, 64}, ATLAS_SIZE_NORMAL}, - {{ 32, 64}, ATLAS_SIZE_NORMAL}, - {{ 48, 64}, ATLAS_SIZE_NORMAL}, - {{ 64, 64}, ATLAS_SIZE_NORMAL}, - {{ 80, 64}, ATLAS_SIZE_SMALL }, - {{ 88, 64}, ATLAS_SIZE_SMALL }, - {{ 80, 72}, ATLAS_SIZE_SMALL }, - {{ 88, 72}, ATLAS_SIZE_SMALL }, - {{ 0, 80}, ATLAS_SIZE_OBLONG}, - {{16, 80}, ATLAS_SIZE_OBLONG}, - {{32, 80}, ATLAS_SIZE_OBLONG}, - {{48, 80}, ATLAS_SIZE_BIG}, - {{48, 120}, ATLAS_SIZE_BIG} -}; - -#define ATLAS_POSITION(type) ATLAS_ENTRIES[type].position -#define ATLAS_SIZE(type) ATLAS_ENTRIES[type].size -#define ATLAS_UV_MIN(type) (ATLAS_POSITION(type) / TEXTURE_ATLAS_SIZE) -#define ATLAS_UV_MAX(type) ((ATLAS_POSITION(type) + ATLAS_SIZE(type)) / TEXTURE_ATLAS_SIZE) -#define ATLAS_UV_ARGS(type) ATLAS_UV_MIN(type), ATLAS_UV_MAX(type) -#define ATLAS_UV_VERTICES(type) UV_VERTICES(ATLAS_UV_MIN(type), ATLAS_UV_MAX(type)) - -struct ShaderData -{ - std::string vertex; - std::string fragment; -}; - -enum ShaderType -{ - SHADER_LINE, - SHADER_TEXTURE, - SHADER_AXIS, - SHADER_GRID, - SHADER_COUNT -}; - -const std::string SHADER_VERTEX = R"( -#version 330 core -layout (location = 0) in vec2 i_position; -layout (location = 1) in vec2 i_uv; -out vec2 i_uv_out; -uniform mat4 u_transform; -void main() -{ - i_uv_out = i_uv; - gl_Position = u_transform * vec4(i_position, 0.0, 1.0); -} -)"; - -const std::string SHADER_AXIS_VERTEX = R"( -#version 330 core -layout (location = 0) in vec2 i_position; // full screen line segment: -1..1 -uniform vec2 u_origin_ndc; // world origin in NDC -uniform int u_axis; // 0 = X axis, 1 = Y axis - -void main() -{ - vec2 pos = (u_axis == 0) - ? vec2(i_position.x, u_origin_ndc.y) // horizontal line across screen - : vec2(u_origin_ndc.x, i_position.x); // vertical line across screen; - - gl_Position = vec4(pos, 0.0, 1.0); -} -)"; - -const std::string SHADER_GRID_VERTEX = R"( -#version 330 core -layout ( location = 0 ) in vec2 i_position; -out vec2 clip; - -void main() { - clip = i_position; - gl_Position = vec4(i_position, 0.0, 1.0); -} -)"; - -const std::string SHADER_FRAGMENT = R"( -#version 330 core -out vec4 o_fragColor; -uniform vec4 u_color; -void main() -{ - o_fragColor = u_color; -} -)"; - -const std::string SHADER_TEXTURE_FRAGMENT = R"( -#version 330 core -in vec2 i_uv_out; -uniform sampler2D u_texture; -uniform vec4 u_tint; -uniform vec3 u_color_offset; -out vec4 o_fragColor; -void main() -{ - vec4 texColor = texture(u_texture, i_uv_out); - texColor *= u_tint; - texColor.rgb += u_color_offset; - o_fragColor = texColor; -} -)"; - -const std::string SHADER_GRID_FRAGMENT = R"( -#version 330 core -in vec2 clip; - -uniform mat4 u_model; -uniform vec2 u_size; -uniform vec2 u_offset; -uniform vec4 u_color; - -out vec4 o_fragColor; - -void main() -{ - vec4 w = u_model * vec4(clip, 0.0, 1.0); - w /= w.w; - vec2 world = w.xy; - - vec2 g = (world - u_offset) / u_size; - - vec2 d = abs(fract(g) - 0.5); - float distance = min(d.x, d.y); - - float fw = min(fwidth(g.x), fwidth(g.y)); - float alpha = 1.0 - smoothstep(0.0, fw, distance); - - if (alpha <= 0.0) discard; - o_fragColor = vec4(u_color.rgb, u_color.a * alpha); -} -)"; - -#define SHADER_UNIFORM_AXIS "u_axis" -#define SHADER_UNIFORM_COLOR "u_color" -#define SHADER_UNIFORM_TRANSFORM "u_transform" -#define SHADER_UNIFORM_TINT "u_tint" -#define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset" -#define SHADER_UNIFORM_OFFSET "u_offset" -#define SHADER_UNIFORM_ORIGIN_NDC "u_origin_ndc" -#define SHADER_UNIFORM_SIZE "u_size" -#define SHADER_UNIFORM_MODEL "u_model" -#define SHADER_UNIFORM_TEXTURE "u_texture" - -const ShaderData SHADER_DATA[SHADER_COUNT] = -{ - {SHADER_VERTEX, SHADER_FRAGMENT}, - {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT}, - {SHADER_AXIS_VERTEX, SHADER_FRAGMENT}, - {SHADER_GRID_VERTEX, SHADER_GRID_FRAGMENT} -}; diff --git a/src/RESOURCE.h b/src/RESOURCE.h new file mode 100644 index 0000000..25b0346 --- /dev/null +++ b/src/RESOURCE.h @@ -0,0 +1,197 @@ +#pragma once + +#include "COMMON.h" + +#define ATLAS_PATH "resources/atlas.png" +#define FONT_PATH "resources/font.ttf" +#define FONT_SIZE 16 + +const vec2 TEXTURE_ATLAS_SIZE = {96, 160}; + +enum AtlasType { + ATLAS_NONE, + ATLAS_FOLDER, + ATLAS_ROOT, + ATLAS_LAYER, + ATLAS_NULL, + ATLAS_TRIGGERS, + ATLAS_VISIBLE, + ATLAS_INVISIBLE, + ATLAS_SHOW_RECT, + ATLAS_HIDE_RECT, + ATLAS_SHOW_UNUSED, + ATLAS_HIDE_UNUSED, + ATLAS_PAN, + ATLAS_MOVE, + ATLAS_ROTATE, + ATLAS_SCALE, + ATLAS_CROP, + ATLAS_DRAW, + ATLAS_ERASE, + ATLAS_COLOR_PICKER, + ATLAS_UNDO, + ATLAS_REDO, + ATLAS_ANIMATION, + ATLAS_SPRITESHEET, + ATLAS_EVENT, + ATLAS_PLAY, + ATLAS_PAUSE, + ATLAS_ADD, + ATLAS_REMOVE, + ATLAS_TRIGGER, + ATLAS_PIVOT, + ATLAS_SQUARE, + ATLAS_CIRCLE, + ATLAS_PICKER, + ATLAS_FRAME, + ATLAS_FRAME_ALT, + ATLAS_TARGET, + ATLAS_TARGET_ALT, + ATLAS_COUNT +}; + +struct AtlasEntry { + vec2 position; + vec2 size; +}; + +const vec2 ATLAS_SIZE_SMALL = {8, 8}; +const vec2 ATLAS_SIZE_NORMAL = {16, 16}; +const vec2 ATLAS_SIZE_OBLONG = {16, 40}; +const vec2 ATLAS_SIZE_BIG = {40, 40}; + +const inline AtlasEntry ATLAS_ENTRIES[ATLAS_COUNT] = { + {{0, 0}, ATLAS_SIZE_NORMAL}, {{16, 0}, ATLAS_SIZE_NORMAL}, {{32, 0}, ATLAS_SIZE_NORMAL}, {{48, 0}, ATLAS_SIZE_NORMAL}, {{64, 0}, ATLAS_SIZE_NORMAL}, + {{80, 0}, ATLAS_SIZE_NORMAL}, {{0, 16}, ATLAS_SIZE_NORMAL}, {{16, 16}, ATLAS_SIZE_NORMAL}, {{32, 16}, ATLAS_SIZE_NORMAL}, {{48, 16}, ATLAS_SIZE_NORMAL}, + {{64, 16}, ATLAS_SIZE_NORMAL}, {{80, 16}, ATLAS_SIZE_NORMAL}, {{0, 32}, ATLAS_SIZE_NORMAL}, {{16, 32}, ATLAS_SIZE_NORMAL}, {{32, 32}, ATLAS_SIZE_NORMAL}, + {{48, 32}, ATLAS_SIZE_NORMAL}, {{64, 32}, ATLAS_SIZE_NORMAL}, {{80, 32}, ATLAS_SIZE_NORMAL}, {{0, 48}, ATLAS_SIZE_NORMAL}, {{16, 48}, ATLAS_SIZE_NORMAL}, + {{32, 48}, ATLAS_SIZE_NORMAL}, {{48, 48}, ATLAS_SIZE_NORMAL}, {{64, 48}, ATLAS_SIZE_NORMAL}, {{80, 48}, ATLAS_SIZE_NORMAL}, {{0, 64}, ATLAS_SIZE_NORMAL}, + {{16, 64}, ATLAS_SIZE_NORMAL}, {{32, 64}, ATLAS_SIZE_NORMAL}, {{48, 64}, ATLAS_SIZE_NORMAL}, {{64, 64}, ATLAS_SIZE_NORMAL}, {{80, 64}, ATLAS_SIZE_SMALL}, + {{88, 64}, ATLAS_SIZE_SMALL}, {{80, 72}, ATLAS_SIZE_SMALL}, {{88, 72}, ATLAS_SIZE_SMALL}, {{0, 80}, ATLAS_SIZE_OBLONG}, {{16, 80}, ATLAS_SIZE_OBLONG}, + {{32, 80}, ATLAS_SIZE_OBLONG}, {{48, 80}, ATLAS_SIZE_BIG}, {{48, 120}, ATLAS_SIZE_BIG}}; + +#define ATLAS_POSITION(type) ATLAS_ENTRIES[type].position +#define ATLAS_SIZE(type) ATLAS_ENTRIES[type].size +#define ATLAS_UV_MIN(type) (ATLAS_POSITION(type) / TEXTURE_ATLAS_SIZE) +#define ATLAS_UV_MAX(type) ((ATLAS_POSITION(type) + ATLAS_SIZE(type)) / TEXTURE_ATLAS_SIZE) +#define ATLAS_UV_ARGS(type) ATLAS_UV_MIN(type), ATLAS_UV_MAX(type) +#define ATLAS_UV_VERTICES(type) UV_VERTICES(ATLAS_UV_MIN(type), ATLAS_UV_MAX(type)) + +struct ShaderData { + std::string vertex; + std::string fragment; +}; + +enum ShaderType { SHADER_LINE, SHADER_TEXTURE, SHADER_AXIS, SHADER_GRID, SHADER_COUNT }; + +const std::string SHADER_VERTEX = R"( +#version 330 core +layout (location = 0) in vec2 i_position; +layout (location = 1) in vec2 i_uv; +out vec2 i_uv_out; +uniform mat4 u_transform; +void main() +{ + i_uv_out = i_uv; + gl_Position = u_transform * vec4(i_position, 0.0, 1.0); +} +)"; + +const std::string SHADER_AXIS_VERTEX = R"( +#version 330 core +layout (location = 0) in vec2 i_position; // full screen line segment: -1..1 +uniform vec2 u_origin_ndc; // world origin in NDC +uniform int u_axis; // 0 = X axis, 1 = Y axis + +void main() +{ + vec2 pos = (u_axis == 0) + ? vec2(i_position.x, u_origin_ndc.y) // horizontal line across screen + : vec2(u_origin_ndc.x, i_position.x); // vertical line across screen; + + gl_Position = vec4(pos, 0.0, 1.0); +} +)"; + +const std::string SHADER_GRID_VERTEX = R"( +#version 330 core +layout ( location = 0 ) in vec2 i_position; +out vec2 clip; + +void main() { + clip = i_position; + gl_Position = vec4(i_position, 0.0, 1.0); +} +)"; + +const std::string SHADER_FRAGMENT = R"( +#version 330 core +out vec4 o_fragColor; +uniform vec4 u_color; +void main() +{ + o_fragColor = u_color; +} +)"; + +const std::string SHADER_TEXTURE_FRAGMENT = R"( +#version 330 core +in vec2 i_uv_out; +uniform sampler2D u_texture; +uniform vec4 u_tint; +uniform vec3 u_color_offset; +out vec4 o_fragColor; +void main() +{ + vec4 texColor = texture(u_texture, i_uv_out); + texColor *= u_tint; + texColor.rgb += u_color_offset; + o_fragColor = texColor; +} +)"; + +const std::string SHADER_GRID_FRAGMENT = R"( +#version 330 core +in vec2 clip; + +uniform mat4 u_model; +uniform vec2 u_size; +uniform vec2 u_offset; +uniform vec4 u_color; + +out vec4 o_fragColor; + +void main() +{ + vec4 w = u_model * vec4(clip, 0.0, 1.0); + w /= w.w; + vec2 world = w.xy; + + vec2 g = (world - u_offset) / u_size; + + vec2 d = abs(fract(g) - 0.5); + float distance = min(d.x, d.y); + + float fw = min(fwidth(g.x), fwidth(g.y)); + float alpha = 1.0 - smoothstep(0.0, fw, distance); + + if (alpha <= 0.0) discard; + o_fragColor = vec4(u_color.rgb, u_color.a * alpha); +} +)"; + +#define SHADER_UNIFORM_AXIS "u_axis" +#define SHADER_UNIFORM_COLOR "u_color" +#define SHADER_UNIFORM_TRANSFORM "u_transform" +#define SHADER_UNIFORM_TINT "u_tint" +#define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset" +#define SHADER_UNIFORM_OFFSET "u_offset" +#define SHADER_UNIFORM_ORIGIN_NDC "u_origin_ndc" +#define SHADER_UNIFORM_SIZE "u_size" +#define SHADER_UNIFORM_MODEL "u_model" +#define SHADER_UNIFORM_TEXTURE "u_texture" + +const ShaderData SHADER_DATA[SHADER_COUNT] = {{SHADER_VERTEX, SHADER_FRAGMENT}, + {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT}, + {SHADER_AXIS_VERTEX, SHADER_FRAGMENT}, + {SHADER_GRID_VERTEX, SHADER_GRID_FRAGMENT}}; diff --git a/src/anm2.cpp b/src/anm2.cpp index c65d2ec..5ef807e 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -2,1345 +2,1304 @@ using namespace tinyxml2; -static void _anm2_created_on_set(Anm2* self) -{ - auto now = std::chrono::system_clock::now(); - std::time_t time = std::chrono::system_clock::to_time_t(now); - std::tm localTime = *std::localtime(&time); +static void _anm2_created_on_set(Anm2* self) { + auto now = std::chrono::system_clock::now(); + std::time_t time = std::chrono::system_clock::to_time_t(now); + std::tm localTime = *std::localtime(&time); - std::ostringstream timeString; - timeString << std::put_time(&localTime, ANM2_CREATED_ON_FORMAT); - self->createdOn = timeString.str(); + std::ostringstream timeString; + timeString << std::put_time(&localTime, ANM2_CREATED_ON_FORMAT); + self->createdOn = timeString.str(); } -static void _anm2_frame_serialize(Anm2Frame* frame, Anm2Type type, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, std::string* string = nullptr) -{ - XMLDocument localDocument; - XMLDocument* useDocument = document ? document : &localDocument; - - XMLElement* element = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); - - if (type == ANM2_TRIGGERS) - { - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], frame->eventID); // EventID - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_AT_FRAME], frame->atFrame); // AtFrame - } - else - { - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_POSITION], frame->position.x); // XPosition - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_POSITION], frame->position.y); // YPosition - - if (type == ANM2_LAYER) - { - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_PIVOT], frame->pivot.x); // XPivot - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_PIVOT], frame->pivot.y); // YPivot - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_CROP], frame->crop.x); // XCrop - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_CROP], frame->crop.y); // YCrop - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_WIDTH], frame->size.x); // Width - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_HEIGHT], frame->size.y); // Height - } - - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_SCALE], frame->scale.x); // XScale - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame->scale.y); // YScale - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame->delay); /* Delay */ - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame->isVisible); // Visible - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], FLOAT_TO_U8(frame->tintRGBA.r)); // RedTint - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], FLOAT_TO_U8(frame->tintRGBA.g)); // GreenTint - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], FLOAT_TO_U8(frame->tintRGBA.b)); // BlueTint - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], FLOAT_TO_U8(frame->tintRGBA.a)); // AlphaTint - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], FLOAT_TO_U8(frame->offsetRGB.r)); // RedOffset - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], FLOAT_TO_U8(frame->offsetRGB.g)); // GreenOffset - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], FLOAT_TO_U8(frame->offsetRGB.b)); // BlueOffset - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame->rotation); // Rotation - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame->isInterpolated); // Interpolated - } - - if (addElement) addElement->InsertEndChild(element); - - if (string && !document) - { - useDocument->InsertEndChild(element); - XMLPrinter printer; - useDocument->Print(&printer); - *string = std::string(printer.CStr()); - } -} - -static void _anm2_animation_serialize(Anm2Animation* animation, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, std::string* string = nullptr) -{ - XMLDocument localDocument; - XMLDocument* useDocument = document ? document : &localDocument; - - // Animation - XMLElement* element = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], animation->name.c_str()); // Name - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_FRAME_NUM], animation->frameNum); // FrameNum - element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_LOOP], animation->isLoop); // Loop - - // RootAnimation - XMLElement* rootElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION]); - - for (auto& frame : animation->rootAnimation.frames) - _anm2_frame_serialize(&frame, ANM2_ROOT, useDocument, rootElement); - - element->InsertEndChild(rootElement); - - // LayerAnimations - XMLElement* layersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS]); - - for (auto& id : animation->layerOrder) - { - // LayerAnimation - Anm2Item& layerAnimation = animation->layerAnimations[id]; - XMLElement* layerElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION]); - layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_LAYER_ID], id); // LayerId - layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], layerAnimation.isVisible); // Visible - - for (auto& frame : layerAnimation.frames) - _anm2_frame_serialize(&frame, ANM2_LAYER, useDocument, layerElement); - - layersElement->InsertEndChild(layerElement); - } - - element->InsertEndChild(layersElement); - - // Nulls - XMLElement* nullsElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATIONS]); - - for (auto& [id, null] : animation->nullAnimations) - { - // NullAnimation - XMLElement* nullElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION]); - nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NULL_ID], id); // NullId - nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], null.isVisible); // Visible - - for (auto& frame : null.frames) - _anm2_frame_serialize(&frame, ANM2_NULL, useDocument, nullElement); - - nullsElement->InsertEndChild(nullElement); - } - - element->InsertEndChild(nullsElement); - - // Triggers - XMLElement* triggersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]); - - for (auto& frame : animation->triggers.frames) - _anm2_frame_serialize(&frame, ANM2_TRIGGERS, useDocument, triggersElement); - - element->InsertEndChild(triggersElement); - - if (addElement) addElement->InsertEndChild(element); - - if (string && !document) - { - useDocument->InsertEndChild(element); - XMLPrinter printer; - useDocument->Print(&printer); - *string = std::string(printer.CStr()); - } -} - -bool anm2_serialize(Anm2* self, const std::string& path) -{ - XMLDocument document; - - if (!self || path.empty()) return false; - - if (self->version == 0) _anm2_created_on_set(self); - - self->path = path; - self->version++; - - // AnimatedActor - XMLElement* animatedActorElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); - document.InsertFirstChild(animatedActorElement); - - // Info - XMLElement* infoElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_INFO]); - infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_CREATED_BY], self->createdBy.c_str()); // CreatedBy - infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_CREATED_ON], self->createdOn.c_str()); // CreatedOn - infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VERSION], self->version); // Version - infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_FPS], self->fps); // FPS - animatedActorElement->InsertEndChild(infoElement); - - // Content - XMLElement* contentElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_CONTENT]); - - // Spritesheets - XMLElement* spritesheetsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEETS]); - - for (auto& [id, spritesheet] : self->spritesheets) - { - // Spritesheet - XMLElement* spritesheetElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET]); - spritesheetElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_PATH], spritesheet.path.c_str()); // Path - spritesheetElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID - spritesheetsElement->InsertEndChild(spritesheetElement); - } - - contentElement->InsertEndChild(spritesheetsElement); - - // Layers - XMLElement* layersElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYERS]); - - for (auto& [id, layer] : self->layers) - { - // Layer - XMLElement* layerElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER]); - layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], layer.name.c_str()); // Path - layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID - layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_SPRITESHEET_ID], layer.spritesheetID); // SpritesheetId - layersElement->InsertEndChild(layerElement); - } - - contentElement->InsertEndChild(layersElement); - - // Nulls - XMLElement* nullsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULLS]); - - for (auto& [id, null] : self->nulls) - { - // Null - XMLElement* nullElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL]); - nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], null.name.c_str()); // Name - nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID - if (null.isShowRect) nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_SHOW_RECT], null.isShowRect); // ShowRect - nullsElement->InsertEndChild(nullElement); - } - - contentElement->InsertEndChild(nullsElement); - - // Events - XMLElement* eventsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENTS]); - - for (auto& [id, event] : self->events) - { - // Event - XMLElement* eventElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT]); - eventElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], event.name.c_str()); // Name - eventElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID - eventsElement->InsertEndChild(eventElement); - } - - contentElement->InsertEndChild(eventsElement); - - animatedActorElement->InsertEndChild(contentElement); - - // Animations - XMLElement* animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]); - if (self->defaultAnimationID != ID_NONE) - animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->animations[self->defaultAnimationID].name.c_str()); // DefaultAnimation - - for (auto& [id, animation] : self->animations) - _anm2_animation_serialize(&animation, &document, animationsElement); - - animatedActorElement->InsertEndChild(animationsElement); - - XMLError error = document.SaveFile(path.c_str()); - - if (error != XML_SUCCESS) - { - log_error(std::format(ANM2_WRITE_ERROR, path)); - return false; - } - - log_info(std::format(ANM2_WRITE_INFO, path)); - - return true; -} - -static void _anm2_frame_deserialize(Anm2Frame* frame, const XMLElement* element) -{ - for (const XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_X_POSITION: frame->position.x = std::atof(attribute->Value()); break; // XPosition - case ANM2_ATTRIBUTE_Y_POSITION: frame->position.y = std::atof(attribute->Value()); break; // YPosition - case ANM2_ATTRIBUTE_X_PIVOT: frame->pivot.x = std::atof(attribute->Value()); break; // XPivot - case ANM2_ATTRIBUTE_Y_PIVOT: frame->pivot.y = std::atof(attribute->Value()); break; // YPivot - case ANM2_ATTRIBUTE_X_CROP: frame->crop.x = std::atof(attribute->Value()); break; // XCrop - case ANM2_ATTRIBUTE_Y_CROP: frame->crop.y = std::atof(attribute->Value()); break; // YCrop - case ANM2_ATTRIBUTE_WIDTH: frame->size.x = std::atof(attribute->Value()); break; // Width - case ANM2_ATTRIBUTE_HEIGHT: frame->size.y = std::atof(attribute->Value()); break; // Height - case ANM2_ATTRIBUTE_X_SCALE: frame->scale.x = std::atof(attribute->Value()); break; // XScale - case ANM2_ATTRIBUTE_Y_SCALE: frame->scale.y = std::atof(attribute->Value()); break; // YScale - case ANM2_ATTRIBUTE_RED_TINT: frame->tintRGBA.r = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // RedTint - case ANM2_ATTRIBUTE_GREEN_TINT: frame->tintRGBA.g = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // GreenTint - case ANM2_ATTRIBUTE_BLUE_TINT: frame->tintRGBA.b = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // BlueTint - case ANM2_ATTRIBUTE_ALPHA_TINT: frame->tintRGBA.a = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // AlphaTint - case ANM2_ATTRIBUTE_RED_OFFSET: frame->offsetRGB.r = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // RedOffset - case ANM2_ATTRIBUTE_GREEN_OFFSET: frame->offsetRGB.g = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // GreenOffset - case ANM2_ATTRIBUTE_BLUE_OFFSET: frame->offsetRGB.b = U8_TO_FLOAT(std::atoi(attribute->Value())); break; // BlueOffset - case ANM2_ATTRIBUTE_ROTATION: frame->rotation = std::atof(attribute->Value()); break; // Rotation - case ANM2_ATTRIBUTE_VISIBLE: frame->isVisible = string_to_bool(attribute->Value()); break; // Visible - case ANM2_ATTRIBUTE_INTERPOLATED: frame->isInterpolated = string_to_bool(attribute->Value()); break; // Interpolated - case ANM2_ATTRIBUTE_AT_FRAME: frame->atFrame = std::atoi(attribute->Value()); break; // AtFrame - case ANM2_ATTRIBUTE_DELAY: frame->delay = std::atoi(attribute->Value()); break; // Delay - case ANM2_ATTRIBUTE_EVENT_ID: frame->eventID = std::atoi(attribute->Value()); break; // EventID - default: break; - } - } -} - -static void _anm2_animation_deserialize(Anm2Animation* animation, const XMLElement* element) -{ - auto frames_deserialize = [&](const XMLElement* itemElement, Anm2Item* item) - { - // Frame - for - ( - const XMLElement* frame = itemElement->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); - frame; frame = frame->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) - ) - _anm2_frame_deserialize(&item->frames.emplace_back(Anm2Frame()), frame); - }; - - s32 id{}; - - for (const XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_NAME: animation->name = std::string(attribute->Value()); break; // Name - case ANM2_ATTRIBUTE_FRAME_NUM: animation->frameNum = std::atoi(attribute->Value()); break; // FrameNum - case ANM2_ATTRIBUTE_LOOP: animation->isLoop = string_to_bool(attribute->Value()); break; // Loop - default: break; - } - } - - // RootAnimation - if (const XMLElement* rootAnimation = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION])) - frames_deserialize(rootAnimation, &animation->rootAnimation); - - // LayerAnimations - if (const XMLElement* layerAnimations = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS])) - { - // LayerAnimation - for - ( - const XMLElement* layerAnimation = layerAnimations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION]); - layerAnimation; layerAnimation = layerAnimation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION]) - ) - { - Anm2Item layerAnimationItem; - - for (const XMLAttribute* attribute = layerAnimation->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_LAYER_ID: id = std::atoi(attribute->Value()); break; // LayerID - case ANM2_ATTRIBUTE_VISIBLE: layerAnimationItem.isVisible = string_to_bool(attribute->Value()); break; // Visible - default: break; - } - } - - frames_deserialize(layerAnimation, &layerAnimationItem); - animation->layerAnimations[id] = layerAnimationItem; - animation->layerOrder.push_back(id); - } - } - - // NullAnimations - if (const XMLElement* nullAnimations = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATIONS])) - { - // NullAnimation - for - ( - const XMLElement* nullAnimation = nullAnimations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION]); - nullAnimation; nullAnimation = nullAnimation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION]) - ) - { - Anm2Item nullAnimationItem; - - for (const XMLAttribute* attribute = nullAnimation->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_NULL_ID: id = std::atoi(attribute->Value()); break; - case ANM2_ATTRIBUTE_VISIBLE: nullAnimationItem.isVisible = string_to_bool(attribute->Value()); break; - default: break; - } - } - - frames_deserialize(nullAnimation, &nullAnimationItem); - animation->nullAnimations[id] = nullAnimationItem; - } - } - - // Triggers - if (const XMLElement* triggers = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS])) - { - // Trigger - for - ( - const XMLElement* trigger = triggers->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]); - trigger; trigger = trigger->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]) - ) - _anm2_frame_deserialize(&animation->triggers.frames.emplace_back(Anm2Frame()), trigger); - } -} - -bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) -{ - if (!self) return false; - - if (path.empty()) - { - log_error(ANM2_EMPTY_ERROR); - return false; - } - - XMLDocument document; - if (document.LoadFile(path.c_str()) != XML_SUCCESS) - { - log_error(std::format(ANM2_PARSE_ERROR, path, document.ErrorStr())); - return false; - } - - anm2_new(self); - self->path = path; - std::string defaultAnimation{}; - s32 id{}; - - // Save old working directory and then use anm2's path as directory - // (used for loading textures from anm2 correctly which are relative) - std::filesystem::path workingPath = std::filesystem::current_path(); - working_directory_from_file_set(path); - - const XMLElement* root = document.RootElement(); - - // Info - if (const XMLElement* info = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_INFO])) - { - for (const XMLAttribute* attribute = info->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_CREATED_BY: self->createdBy = std::string(attribute->Value()); break; // CreatedBy - case ANM2_ATTRIBUTE_CREATED_ON: self->createdOn = std::string(attribute->Value()); break; // CreatedOn - case ANM2_ATTRIBUTE_VERSION: self->version = std::atoi(attribute->Value()); break; // Version - case ANM2_ATTRIBUTE_FPS: self->fps = std::atoi(attribute->Value()); break; // FPS - default: break; - } - } - } - - // Content - if (const XMLElement* content = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_CONTENT])) - { - // Spritesheets - if (const XMLElement* spritesheets = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEETS])) - { - for - ( - const XMLElement* spritesheet = spritesheets->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET]); - spritesheet; spritesheet = spritesheet->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET]) - ) - { - Anm2Spritesheet addSpritesheet; - - for (const XMLAttribute* attribute = spritesheet->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_PATH: - // Spritesheet paths from Isaac Rebirth are made with the assumption that - // the paths are case-insensitive (as the game was developed on Windows) - // However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyways) - // If the check doesn't work, set the spritesheet path to lowercase - // If the check doesn't work, replace backslashes with slashes - // At the minimum this should make all textures be able to be loaded on Linux - // If it doesn't work beyond that then that's on the user :^) - addSpritesheet.path = attribute->Value(); - if (!path_exists(addSpritesheet.path)) addSpritesheet.path = string_to_lowercase(addSpritesheet.path); - if (!path_exists(addSpritesheet.path)) addSpritesheet.path = string_backslash_replace(addSpritesheet.path); - if (isTextures) texture_from_path_init(&addSpritesheet.texture, addSpritesheet.path); - break; - case ANM2_ATTRIBUTE_ID: id = std::atoi(attribute->Value()); break; - default: break; - } - } - - self->spritesheets[id] = addSpritesheet; - } - } - - // Layers - if (const XMLElement* layers = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYERS])) - { - for - ( - const XMLElement* layer = layers->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER]); - layer; layer = layer->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER]) - ) - { - Anm2Layer addLayer; - - for (const XMLAttribute* attribute = layer->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_NAME: addLayer.name = std::string(attribute->Value()); break; // Name - case ANM2_ATTRIBUTE_ID: id = std::atoi(attribute->Value()); break; // ID - case ANM2_ATTRIBUTE_SPRITESHEET_ID: addLayer.spritesheetID = std::atoi(attribute->Value()); break; // ID - default: break; - } - } - - self->layers[id] = addLayer; - } - } - - // Nulls - if (const XMLElement* nulls = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULLS])) - { - for - ( - const XMLElement* null = nulls->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL]); - null; null = null->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL]) - ) - { - Anm2Null addNull; - - for (const XMLAttribute* attribute = null->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_NAME: addNull.name = std::string(attribute->Value()); break; // Name - case ANM2_ATTRIBUTE_ID: id = std::atoi(attribute->Value()); break; // IDs - default: break; - } - } - - self->nulls[id] = addNull; - } - } - - // Events - if (const XMLElement* events = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENTS])) - { - // Event - for - ( - const XMLElement* event = events->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT]); - event; event = event->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT]) - ) - { - Anm2Event addEvent; - - for (const XMLAttribute* attribute = event->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_NAME: addEvent.name = std::string(attribute->Value()); break; // Name - case ANM2_ATTRIBUTE_ID: id = std::atoi(attribute->Value()); break; // ID - default: break; - } - } - - self->events[id] = addEvent; - } - } - } - - // Animations - if (const XMLElement* animations = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS])) - { - for (const XMLAttribute* attribute = animations->FirstAttribute(); attribute; attribute = attribute->Next()) - { - switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) - { - case ANM2_ATTRIBUTE_DEFAULT_ANIMATION: defaultAnimation = std::string(attribute->Value()); break; // DefaultAnimation - default: break; - } - } - - // Animation - for - ( - const XMLElement* animation = animations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); - animation; animation = animation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]) - ) - _anm2_animation_deserialize(&self->animations[map_next_id_get(self->animations)], animation); - } - - for (auto& [id, animation] : self->animations) - if (animation.name == defaultAnimation) - self->defaultAnimationID = id; - - if (isTextures) anm2_spritesheet_texture_pixels_download(self); - - std::filesystem::current_path(workingPath); - - log_info(std::format(ANM2_READ_INFO, path)); - - return true; -} - -void anm2_animation_layer_animation_add(Anm2Animation* animation, s32 id) -{ - animation->layerAnimations[id] = Anm2Item{}; - animation->layerOrder.push_back(id); -} - -void anm2_animation_layer_animation_remove(Anm2Animation* animation, s32 id) -{ - animation->layerAnimations.erase(id); - vector_value_erase(animation->layerOrder, id); -} - -void anm2_animation_null_animation_add(Anm2Animation* animation, s32 id) -{ - animation->nullAnimations[id] = Anm2Item{}; -} - -void anm2_animation_null_animation_remove(Anm2Animation* animation, s32 id) -{ - animation->nullAnimations.erase(id); -} - -s32 anm2_layer_add(Anm2* self) -{ - s32 id = map_next_id_get(self->layers); - self->layers[id] = Anm2Layer{}; - return id; -} - -void anm2_layer_remove(Anm2* self, s32 id) -{ - self->layers.erase(id); - - for (auto& [_, animation] : self->animations) - anm2_animation_layer_animation_remove(&animation, id); -} - -s32 anm2_null_add(Anm2* self) -{ - s32 id = map_next_id_get(self->nulls); - self->nulls[id] = Anm2Null{}; - return id; -} - -void anm2_null_remove(Anm2* self, s32 id) -{ - if (!self->nulls.contains(id)) return; - - self->nulls.erase(id); - - for (auto& [_, animation] : self->animations) - anm2_animation_null_animation_remove(&animation, id); -} - -s32 anm2_animation_add(Anm2* self, Anm2Animation* animation, s32 id) -{ - s32 addID = map_next_id_get(self->animations); - - Anm2Animation localAnimation; - Anm2Animation* addAnimation = animation ? animation : &localAnimation; - - if (!animation) addAnimation->rootAnimation.frames.push_back(Anm2Frame{}); - - if (id != ID_NONE) - { - map_insert_shift(self->animations, id, *addAnimation); - return id + 1; - } - else - self->animations[addID] = *addAnimation; - - return addID; -} - -void anm2_animation_remove(Anm2* self, s32 id) -{ - self->animations.erase(id); -} - -void anm2_new(Anm2* self) -{ - *self = Anm2{}; - _anm2_created_on_set(self); -} - -Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference) -{ - return map_find(self->animations, reference->animationID); -} - -Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference) -{ - if (reference->itemType == ANM2_NONE) return nullptr; - - Anm2Animation* animation = anm2_animation_from_reference(self, reference); - - if (!animation) return nullptr; - - switch (reference->itemType) - { - case ANM2_ROOT: return &animation->rootAnimation; - case ANM2_LAYER: return map_find(animation->layerAnimations, reference->itemID); - case ANM2_NULL: return map_find(animation->nullAnimations, reference->itemID); - case ANM2_TRIGGERS: return &animation->triggers; - default: return nullptr; - } -} - -s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time) -{ - Anm2Animation* animation = anm2_animation_from_reference(self, &reference); - - if (!animation) return INDEX_NONE; - if (time < 0 || time > animation->frameNum) return INDEX_NONE; - - Anm2Item* item = anm2_item_from_reference(self, &reference); - - if (!item) return INDEX_NONE; - - if (reference.itemType == ANM2_TRIGGERS) - for (auto [i, frame] : std::views::enumerate(item->frames)) - if (frame.atFrame == (s32)time) return i; - - s32 delayCurrent = 0; - s32 delayNext = 0; - - for (auto [i, frame] : std::views::enumerate(item->frames)) - { - delayNext += frame.delay; - - if (time >= delayCurrent && time < delayNext) - return i; - - delayCurrent += frame.delay; - } - - return INDEX_NONE; -} - -Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference) -{ - Anm2Item* item = anm2_item_from_reference(self, reference); - - if (!item) return nullptr; - - if (reference->frameIndex <= INDEX_NONE || reference->frameIndex >= (s32)item->frames.size()) - return nullptr; - - return &item->frames[reference->frameIndex]; -} - -void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time) -{ - Anm2Animation* animation = anm2_animation_from_reference(self, &reference); - if (!animation) return; - - time = std::clamp(ROUND_NEAREST_MULTIPLE(time, 1.0f), 0.0f, animation->frameNum - 1.0f); - - Anm2Item* item = anm2_item_from_reference(self, &reference); - - if (!item) return; - - Anm2Frame* frameNext = nullptr; - s32 delayCurrent = 0; - s32 delayNext = 0; - - for (auto [i, iFrame] : std::views::enumerate(item->frames)) - { - if (reference.itemType == ANM2_TRIGGERS) - { - if ((s32)time == iFrame.atFrame) - { - *frame = iFrame; - break; - } - } - else - { - *frame = iFrame; - - delayNext += frame->delay; - - if (time >= delayCurrent && time < delayNext) - { - if (i + 1 < (s32)item->frames.size()) - frameNext = &item->frames[i + 1]; - else - frameNext = nullptr; - break; - } - - delayCurrent += frame->delay; - } - } - - if (reference.itemType == ANM2_TRIGGERS) - return; - - if (frame->isInterpolated && frameNext && frame->delay > 1) - { - f32 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->offsetRGB = glm::mix(frame->offsetRGB, frameNext->offsetRGB, interpolation); - frame->tintRGBA = glm::mix(frame->tintRGBA, frameNext->tintRGBA, interpolation); - } -} - -s32 anm2_animation_length_get(Anm2Animation* self) -{ - s32 length = 0; - - auto accumulate_max_delay = [&](const std::vector& frames) - { - s32 delaySum = 0; - for (const auto& frame : frames) - { - delaySum += frame.delay; - length = std::max(length, delaySum); - } - }; - - accumulate_max_delay(self->rootAnimation.frames); - - for (const auto& [_, item] : self->layerAnimations) - accumulate_max_delay(item.frames); - - for (const auto& [_, item] : self->nullAnimations) - accumulate_max_delay(item.frames); - - for (const auto& frame : self->triggers.frames) - length = std::max(length, frame.atFrame + 1); - - return length; -} - -void anm2_animation_length_set(Anm2Animation* self) -{ - self->frameNum = anm2_animation_length_get(self); -} - -Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference) -{ - Anm2Animation* animation = anm2_animation_from_reference(self, reference); - Anm2Item* item = anm2_item_from_reference(self, reference); - if (!animation || !item) return nullptr; - - Anm2Frame frameAdd = frame ? *frame : Anm2Frame{}; - - if (reference->itemType == ANM2_TRIGGERS) - { - s32 atFrame = reference->time != VALUE_NONE ? (s32)reference->time : 0; - - for (auto& frameCheck : item->frames) - { - if (frameCheck.atFrame == atFrame) - { - atFrame++; - break; - } - } - - frameAdd.delay = 1; - frameAdd.atFrame = atFrame; - return &item->frames.emplace_back(frameAdd); - } - else - { - s32 index = reference->frameIndex; - - if (index < 0 || index >= (s32)item->frames.size()) - { - item->frames.push_back(frameAdd); - return &item->frames.back(); - } - else - { - item->frames.insert(item->frames.begin() + index, frameAdd); - return &item->frames[(size_t)(index)]; - } - } -} - -void anm2_frame_remove(Anm2* self, Anm2Reference* reference) -{ - Anm2Item* item = anm2_item_from_reference(self, reference); - if (!item) return; - item->frames.erase(item->frames.begin() + reference->frameIndex); -} - -void anm2_reference_clear(Anm2Reference* self) -{ - *self = Anm2Reference{}; -} - -void anm2_reference_item_clear(Anm2Reference* self) -{ - *self = {self->animationID}; -} - -void anm2_reference_frame_clear(Anm2Reference* self) -{ - self->frameIndex = INDEX_NONE; -} - -void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count) -{ - Anm2Item* item = anm2_item_from_reference(self, reference); - if (!item) return; - - if (start < 0 || count <= 0) return; - const s32 size = (s32)item->frames.size(); - if (size == 0 || start >= size) return; - - const s32 end = std::min(start + count, size); - - for (s32 i = start; i < end; ++i) - { - Anm2Frame& dest = item->frames[i]; - - // Booleans always just set if provided - if (change.isVisible) dest.isVisible = *change.isVisible; - if (change.isInterpolated) dest.isInterpolated = *change.isInterpolated; - - switch (type) - { - case ANM2_CHANGE_SET: - if (change.rotation) dest.rotation = *change.rotation; - if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, *change.delay); - if (change.crop) dest.crop = *change.crop; - if (change.pivot) dest.pivot = *change.pivot; - if (change.position) dest.position = *change.position; - if (change.size) dest.size = *change.size; - if (change.scale) dest.scale = *change.scale; - if (change.offsetRGB) dest.offsetRGB = glm::clamp(*change.offsetRGB, 0.0f, 1.0f); - if (change.tintRGBA) dest.tintRGBA = glm::clamp(*change.tintRGBA, 0.0f, 1.0f); - break; - - case ANM2_CHANGE_ADD: - if (change.rotation) dest.rotation += *change.rotation; - if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay + *change.delay); - if (change.crop) dest.crop += *change.crop; - if (change.pivot) dest.pivot += *change.pivot; - if (change.position) dest.position += *change.position; - if (change.size) dest.size += *change.size; - if (change.scale) dest.scale += *change.scale; - if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB + *change.offsetRGB, 0.0f, 1.0f); - if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA + *change.tintRGBA, 0.0f, 1.0f); - break; - - case ANM2_CHANGE_SUBTRACT: - if (change.rotation) dest.rotation -= *change.rotation; - if (change.delay) dest.delay = std::max(ANM2_FRAME_DELAY_MIN, dest.delay - *change.delay); - if (change.crop) dest.crop -= *change.crop; - if (change.pivot) dest.pivot -= *change.pivot; - if (change.position) dest.position -= *change.position; - if (change.size) dest.size -= *change.size; - if (change.scale) dest.scale -= *change.scale; - if (change.offsetRGB) dest.offsetRGB = glm::clamp(dest.offsetRGB - *change.offsetRGB, 0.0f, 1.0f); - if (change.tintRGBA) dest.tintRGBA = glm::clamp(dest.tintRGBA - *change.tintRGBA, 0.0f, 1.0f); - break; - } - } -} - -void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type) -{ - Anm2Animation newAnimation = self->animations[animationID]; - - auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) - { - switch (type) - { - case ANM2_MERGE_APPEND: - destinationItem.frames.insert(destinationItem.frames.end(), sourceItem.frames.begin(), sourceItem.frames.end()); - break; - case ANM2_MERGE_PREPEND: - destinationItem.frames.insert(destinationItem.frames.begin(), sourceItem.frames.begin(), sourceItem.frames.end()); - break; - case ANM2_MERGE_REPLACE: - if (destinationItem.frames.size() < sourceItem.frames.size()) - destinationItem.frames.resize(sourceItem.frames.size()); - for (s32 i = 0; i < (s32)sourceItem.frames.size(); i++) - destinationItem.frames[i] = sourceItem.frames[i]; - break; - case ANM2_MERGE_IGNORE: - break; - } - }; - - for (auto mergeID : mergeIDs) - { - if (animationID == mergeID) continue; - - const Anm2Animation& mergeAnimation = self->animations[mergeID]; - - merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); - - for (const auto& [id, layerAnimation] : mergeAnimation.layerAnimations) - merge_item(newAnimation.layerAnimations[id], layerAnimation); - - for (const auto& [id, nullAnimation] : mergeAnimation.nullAnimations) - merge_item(newAnimation.nullAnimations[id], nullAnimation); - - merge_item(newAnimation.triggers, mergeAnimation.triggers); - } - - self->animations[animationID] = newAnimation; - - anm2_animation_length_set(&self->animations[animationID]); -} - -void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation) -{ - Anm2Item* item = anm2_item_from_reference(self, reference); - if (!item) return; - - Anm2Frame* frame = anm2_frame_from_reference(self, reference); - if (!frame) return; - - Anm2Reference referenceNext = *reference; - referenceNext.frameIndex = reference->frameIndex + 1; - - Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext); - if (!frameNext) frameNext = frame; - - const Anm2Frame baseFrame = *frame; - const Anm2Frame baseFrameNext = *frameNext; - - s32 delay = 0; - s32 insertIndex = reference->frameIndex; - - while (delay < baseFrame.delay) - { - f32 interpolation = (f32)delay / baseFrame.delay; - - Anm2Frame baked = *frame; - baked.delay = std::min(interval, baseFrame.delay - delay); - baked.isInterpolated = (insertIndex == 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.offsetRGB = glm::mix(baseFrame.offsetRGB, baseFrameNext.offsetRGB, interpolation); - baked.tintRGBA = glm::mix(baseFrame.tintRGBA, baseFrameNext.tintRGBA, interpolation); - - if (isRoundScale) baked.scale = vec2((s32)baked.scale.x, (s32)baked.scale.y); - if (isRoundRotation) baked.rotation = (s32)baked.rotation; - - if (insertIndex == reference->frameIndex) - item->frames[insertIndex] = baked; - else - item->frames.insert(item->frames.begin() + insertIndex, baked); - insertIndex++; - - delay += baked.delay; - } -} - -void anm2_scale(Anm2* self, f32 scale) -{ - auto frame_scale = [&](Anm2Frame& frame) - { - frame.position = vec2((s32)(frame.position.x * scale), (s32)(frame.position.y * scale)); - frame.size = vec2((s32)(frame.size.x * scale), (s32)(frame.size.y * scale)); - frame.crop = vec2((s32)(frame.crop.x * scale), (s32)(frame.crop.y * scale)); - frame.pivot = vec2((s32)(frame.pivot.x * scale), (s32)(frame.pivot.y * scale)); - }; - - for (auto& [_, animation] : self->animations) - { - for (auto& frame : animation.rootAnimation.frames) - frame_scale(frame); - - for (auto& [_, layerAnimation] : animation.layerAnimations) - for (auto& frame : layerAnimation.frames) - frame_scale(frame); - - for (auto& [_, nullAnimation] : animation.nullAnimations) - for (auto& frame : nullAnimation.frames) - frame_scale(frame); - } -} - -void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay) -{ - Anm2Item* item = anm2_item_from_reference(self, reference); - if (!item) return; - - Anm2Reference frameReference = *reference; - - for (s32 i = 0; i < count; i++) - { - const s32 row = i / columns; - const s32 column = i % columns; - - Anm2Frame frame{}; - - frame.delay = delay; - frame.pivot = pivot; - frame.size = size; - frame.crop = startPosition + vec2(size.x * column, size.y * row); - - anm2_frame_add(self, &frame, &frameReference); - frameReference.frameIndex++; - } -} - -void anm2_free(Anm2* self) -{ - for (auto& [id, spritesheet] : self->spritesheets) - texture_free(&spritesheet.texture); -} - -void anm2_spritesheet_texture_pixels_upload(Anm2* self) -{ - for (auto& [_, spritesheet] : self->spritesheets) - { - Texture& texture = spritesheet.texture; - - if (texture.id != GL_ID_NONE && !texture.isInvalid) - { - assert(!spritesheet.pixels.empty()); - glBindTexture(GL_TEXTURE_2D, texture.id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.size.x, texture.size.y, GL_RGBA, GL_UNSIGNED_BYTE, spritesheet.pixels.data()); - } - } -} - -void anm2_spritesheet_texture_pixels_download(Anm2* self) -{ - for (auto& [_, spritesheet] : self->spritesheets) - { - Texture& texture = spritesheet.texture; - - if (texture.id != GL_ID_NONE && !texture.isInvalid) - { - size_t bufferSize = (size_t)texture.size.x * (size_t)texture.size.y * TEXTURE_CHANNELS; - spritesheet.pixels.resize(bufferSize); - glBindTexture(GL_TEXTURE_2D, texture.id); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, spritesheet.pixels.data()); - } - } -} - -vec4 anm2_animation_rect_get(Anm2* self, Anm2Reference* reference, bool isRootTransform) -{ - f32 minX = std::numeric_limits::infinity(); - f32 minY = std::numeric_limits::infinity(); - f32 maxX = -std::numeric_limits::infinity(); - f32 maxY = -std::numeric_limits::infinity(); - - bool any = false; - - Anm2Frame frame; - Anm2Frame root; - - Anm2Animation* animation = anm2_animation_from_reference(self, reference); - if (!animation) return vec4(-1.0f); - - for (f32 t = 0.0f; t <= animation->frameNum; t += 1.0f) - { - for (const auto& [id, _] : animation->layerAnimations) - { - anm2_frame_from_time(self, &frame, {reference->animationID, ANM2_LAYER, id}, t); - if (!frame.isVisible) continue; - if (frame.size.x <= 0 || frame.size.y <= 0) continue; - - mat4 rootModel(1.0f); - if (isRootTransform) - { - anm2_frame_from_time(self, &root, {reference->animationID, ANM2_ROOT}, t); - rootModel = quad_model_parent_get(root.position, root.pivot, PERCENT_TO_UNIT(root.scale), root.rotation); - } - - mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 fullModel = rootModel * model; - - vec2 corners[4] = { {0,0}, {1,0}, {1,1}, {0,1} }; - - for (auto& corner : corners) - { - vec4 world = fullModel * 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; - } - } +static void _anm2_frame_serialize(Anm2Frame* frame, Anm2Type type, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, + std::string* string = nullptr) { + XMLDocument localDocument; + XMLDocument* useDocument = document ? document : &localDocument; + + XMLElement* element = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); + + if (type == ANM2_TRIGGER) { + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_EVENT_ID], frame->eventID); // EventID + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_AT_FRAME], frame->atFrame); // AtFrame + } else { + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_POSITION], frame->position.x); // XPosition + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_POSITION], frame->position.y); // YPosition + + if (type == ANM2_LAYER) { + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_PIVOT], frame->pivot.x); // XPivot + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_PIVOT], frame->pivot.y); // YPivot + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_CROP], frame->crop.x); // XCrop + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_CROP], frame->crop.y); // YCrop + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_WIDTH], frame->size.x); // Width + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_HEIGHT], frame->size.y); // Height } - if (!any) return vec4(-1.0f); - return {minX, minY, maxX - minX, maxY - minY}; + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_X_SCALE], frame->scale.x); // XScale + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame->scale.y); // YScale + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame->delay); /* Delay */ + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame->isVisible); // Visible + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], FLOAT_TO_UINT8(frame->tintRGBA.r)); // RedTint + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], FLOAT_TO_UINT8(frame->tintRGBA.g)); // GreenTint + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], FLOAT_TO_UINT8(frame->tintRGBA.b)); // BlueTint + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], FLOAT_TO_UINT8(frame->tintRGBA.a)); // AlphaTint + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], FLOAT_TO_UINT8(frame->offsetRGB.r)); // RedOffset + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], FLOAT_TO_UINT8(frame->offsetRGB.g)); // GreenOffset + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], FLOAT_TO_UINT8(frame->offsetRGB.b)); // BlueOffset + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame->rotation); // Rotation + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame->isInterpolated); // Interpolated + } + + if (addElement) + addElement->InsertEndChild(element); + + if (string && !document) { + useDocument->InsertEndChild(element); + XMLPrinter printer; + useDocument->Print(&printer); + *string = std::string(printer.CStr()); + } } -void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string) -{ - _anm2_animation_serialize(animation, nullptr, nullptr, string); +static void _anm2_animation_serialize(Anm2Animation* animation, XMLDocument* document = nullptr, XMLElement* addElement = nullptr, + std::string* string = nullptr) { + XMLDocument localDocument; + XMLDocument* useDocument = document ? document : &localDocument; + + // Animation + XMLElement* element = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], animation->name.c_str()); // Name + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_FRAME_NUM], animation->frameNum); // FrameNum + element->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_LOOP], animation->isLoop); // Loop + + // RootAnimation + XMLElement* rootElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION]); + + for (auto& frame : animation->rootAnimation.frames) + _anm2_frame_serialize(&frame, ANM2_ROOT, useDocument, rootElement); + + element->InsertEndChild(rootElement); + + // LayerAnimations + XMLElement* layersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS]); + + for (auto& id : animation->layerOrder) { + // LayerAnimation + Anm2Item& layerAnimation = animation->layerAnimations[id]; + XMLElement* layerElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION]); + layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_LAYER_ID], id); // LayerId + layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], layerAnimation.isVisible); // Visible + + for (auto& frame : layerAnimation.frames) + _anm2_frame_serialize(&frame, ANM2_LAYER, useDocument, layerElement); + + layersElement->InsertEndChild(layerElement); + } + + element->InsertEndChild(layersElement); + + // Nulls + XMLElement* nullsElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATIONS]); + + for (auto& [id, null] : animation->nullAnimations) { + // NullAnimation + XMLElement* nullElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION]); + nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NULL_ID], id); // NullId + nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], null.isVisible); // Visible + + for (auto& frame : null.frames) + _anm2_frame_serialize(&frame, ANM2_NULL, useDocument, nullElement); + + nullsElement->InsertEndChild(nullElement); + } + + element->InsertEndChild(nullsElement); + + // Triggers + XMLElement* triggersElement = useDocument->NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]); + + for (auto& frame : animation->triggers.frames) + _anm2_frame_serialize(&frame, ANM2_TRIGGER, useDocument, triggersElement); + + element->InsertEndChild(triggersElement); + + if (addElement) + addElement->InsertEndChild(element); + + if (string && !document) { + useDocument->InsertEndChild(element); + XMLPrinter printer; + useDocument->Print(&printer); + *string = std::string(printer.CStr()); + } } -void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string) -{ - _anm2_frame_serialize(frame, type, nullptr, nullptr, string); +bool anm2_serialize(Anm2* self, const std::string& path) { + XMLDocument document; + + if (!self || path.empty()) + return false; + + if (self->version == 0) + _anm2_created_on_set(self); + + self->path = path; + self->version++; + + // AnimatedActor + XMLElement* animatedActorElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATED_ACTOR]); + document.InsertFirstChild(animatedActorElement); + + // Info + XMLElement* infoElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_INFO]); + infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_CREATED_BY], self->createdBy.c_str()); // CreatedBy + infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_CREATED_ON], self->createdOn.c_str()); // CreatedOn + infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VERSION], self->version); // Version + infoElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_FPS], self->fps); // FPS + animatedActorElement->InsertEndChild(infoElement); + + // Content + XMLElement* contentElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_CONTENT]); + + // Spritesheets + XMLElement* spritesheetsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEETS]); + + for (auto& [id, spritesheet] : self->spritesheets) { + // Spritesheet + XMLElement* spritesheetElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET]); + spritesheetElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_PATH], spritesheet.path.c_str()); // Path + spritesheetElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID + spritesheetsElement->InsertEndChild(spritesheetElement); + } + + contentElement->InsertEndChild(spritesheetsElement); + + // Layers + XMLElement* layersElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYERS]); + + for (auto& [id, layer] : self->layers) { + // Layer + XMLElement* layerElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER]); + layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], layer.name.c_str()); // Path + layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID + layerElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_SPRITESHEET_ID], layer.spritesheetID); // SpritesheetId + layersElement->InsertEndChild(layerElement); + } + + contentElement->InsertEndChild(layersElement); + + // Nulls + XMLElement* nullsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULLS]); + + for (auto& [id, null] : self->nulls) { + // Null + XMLElement* nullElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL]); + nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], null.name.c_str()); // Name + nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID + if (null.isShowRect) + nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_SHOW_RECT], null.isShowRect); // ShowRect + nullsElement->InsertEndChild(nullElement); + } + + contentElement->InsertEndChild(nullsElement); + + // Events + XMLElement* eventsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENTS]); + + for (auto& [id, event] : self->events) { + // Event + XMLElement* eventElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT]); + eventElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], event.name.c_str()); // Name + eventElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID + eventsElement->InsertEndChild(eventElement); + } + + contentElement->InsertEndChild(eventsElement); + + animatedActorElement->InsertEndChild(contentElement); + + // Animations + XMLElement* animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]); + if (self->defaultAnimationID != ID_NONE) + animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], + self->animations[self->defaultAnimationID].name.c_str()); // DefaultAnimation + + for (auto& [id, animation] : self->animations) + _anm2_animation_serialize(&animation, &document, animationsElement); + + animatedActorElement->InsertEndChild(animationsElement); + + XMLError error = document.SaveFile(path.c_str()); + + if (error != XML_SUCCESS) { + log_error(std::format(ANM2_WRITE_ERROR, path)); + return false; + } + + log_info(std::format(ANM2_WRITE_INFO, path)); + + return true; } -bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml) -{ - XMLDocument document; - - auto animation_deserialize_error = [&]() - { - log_error(std::format(ANM2_ANIMATION_PARSE_ERROR, xml, document.ErrorStr())); - return false; - }; - - if (document.Parse(xml.c_str()) != XML_SUCCESS) return animation_deserialize_error(); - - const XMLElement* element = document.RootElement(); - if (!element) return animation_deserialize_error(); - if (std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) - return animation_deserialize_error(); - - _anm2_animation_deserialize(animation, element); - return true; +static void _anm2_frame_deserialize(Anm2Frame* frame, const XMLElement* element) { + for (const XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_X_POSITION: + frame->position.x = std::atof(attribute->Value()); + break; // XPosition + case ANM2_ATTRIBUTE_Y_POSITION: + frame->position.y = std::atof(attribute->Value()); + break; // YPosition + case ANM2_ATTRIBUTE_X_PIVOT: + frame->pivot.x = std::atof(attribute->Value()); + break; // XPivot + case ANM2_ATTRIBUTE_Y_PIVOT: + frame->pivot.y = std::atof(attribute->Value()); + break; // YPivot + case ANM2_ATTRIBUTE_X_CROP: + frame->crop.x = std::atof(attribute->Value()); + break; // XCrop + case ANM2_ATTRIBUTE_Y_CROP: + frame->crop.y = std::atof(attribute->Value()); + break; // YCrop + case ANM2_ATTRIBUTE_WIDTH: + frame->size.x = std::atof(attribute->Value()); + break; // Width + case ANM2_ATTRIBUTE_HEIGHT: + frame->size.y = std::atof(attribute->Value()); + break; // Height + case ANM2_ATTRIBUTE_X_SCALE: + frame->scale.x = std::atof(attribute->Value()); + break; // XScale + case ANM2_ATTRIBUTE_Y_SCALE: + frame->scale.y = std::atof(attribute->Value()); + break; // YScale + case ANM2_ATTRIBUTE_RED_TINT: + frame->tintRGBA.r = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // RedTint + case ANM2_ATTRIBUTE_GREEN_TINT: + frame->tintRGBA.g = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // GreenTint + case ANM2_ATTRIBUTE_BLUE_TINT: + frame->tintRGBA.b = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // BlueTint + case ANM2_ATTRIBUTE_ALPHA_TINT: + frame->tintRGBA.a = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // AlphaTint + case ANM2_ATTRIBUTE_RED_OFFSET: + frame->offsetRGB.r = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // RedOffset + case ANM2_ATTRIBUTE_GREEN_OFFSET: + frame->offsetRGB.g = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // GreenOffset + case ANM2_ATTRIBUTE_BLUE_OFFSET: + frame->offsetRGB.b = UINT8_TO_FLOAT(std::atoi(attribute->Value())); + break; // BlueOffset + case ANM2_ATTRIBUTE_ROTATION: + frame->rotation = std::atof(attribute->Value()); + break; // Rotation + case ANM2_ATTRIBUTE_VISIBLE: + frame->isVisible = string_to_bool(attribute->Value()); + break; // Visible + case ANM2_ATTRIBUTE_INTERPOLATED: + frame->isInterpolated = string_to_bool(attribute->Value()); + break; // Interpolated + case ANM2_ATTRIBUTE_AT_FRAME: + frame->atFrame = std::atoi(attribute->Value()); + break; // AtFrame + case ANM2_ATTRIBUTE_DELAY: + frame->delay = std::atoi(attribute->Value()); + break; // Delay + case ANM2_ATTRIBUTE_EVENT_ID: + frame->eventID = std::atoi(attribute->Value()); + break; // EventID + default: + break; + } + } } -bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml) -{ - XMLDocument document; - - auto frame_deserialize_error = [&]() - { - log_error(std::format(ANM2_FRAME_PARSE_ERROR, xml, document.ErrorStr())); - return false; - }; +static void _anm2_animation_deserialize(Anm2Animation* animation, const XMLElement* element) { + auto frames_deserialize = [&](const XMLElement* itemElement, Anm2Item* item) { + // Frame + for (const XMLElement* frame = itemElement->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]); frame; + frame = frame->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME])) + _anm2_frame_deserialize(&item->frames.emplace_back(Anm2Frame()), frame); + }; - if (document.Parse(xml.c_str()) != XML_SUCCESS) return frame_deserialize_error(); + int id{}; - const XMLElement* element = document.RootElement(); - if (!element) return frame_deserialize_error(); + for (const XMLAttribute* attribute = element->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_NAME: + animation->name = std::string(attribute->Value()); + break; // Name + case ANM2_ATTRIBUTE_FRAME_NUM: + animation->frameNum = std::atoi(attribute->Value()); + break; // FrameNum + case ANM2_ATTRIBUTE_LOOP: + animation->isLoop = string_to_bool(attribute->Value()); + break; // Loop + default: + break; + } + } - if - ( - std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) && - std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]) - ) - return frame_deserialize_error(); + // RootAnimation + if (const XMLElement* rootAnimation = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION])) + frames_deserialize(rootAnimation, &animation->rootAnimation); - _anm2_frame_deserialize(frame, element); - - return true; + // LayerAnimations + if (const XMLElement* layerAnimations = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS])) { + // LayerAnimation + for (const XMLElement* layerAnimation = layerAnimations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION]); layerAnimation; + layerAnimation = layerAnimation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATION])) { + Anm2Item layerAnimationItem; + + for (const XMLAttribute* attribute = layerAnimation->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_LAYER_ID: + id = std::atoi(attribute->Value()); + break; // LayerID + case ANM2_ATTRIBUTE_VISIBLE: + layerAnimationItem.isVisible = string_to_bool(attribute->Value()); + break; // Visible + default: + break; + } + } + + frames_deserialize(layerAnimation, &layerAnimationItem); + animation->layerAnimations[id] = layerAnimationItem; + animation->layerOrder.push_back(id); + } + } + + // NullAnimations + if (const XMLElement* nullAnimations = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATIONS])) { + // NullAnimation + for (const XMLElement* nullAnimation = nullAnimations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION]); nullAnimation; + nullAnimation = nullAnimation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATION])) { + Anm2Item nullAnimationItem; + + for (const XMLAttribute* attribute = nullAnimation->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_NULL_ID: + id = std::atoi(attribute->Value()); + break; + case ANM2_ATTRIBUTE_VISIBLE: + nullAnimationItem.isVisible = string_to_bool(attribute->Value()); + break; + default: + break; + } + } + + frames_deserialize(nullAnimation, &nullAnimationItem); + animation->nullAnimations[id] = nullAnimationItem; + } + } + + // Triggers + if (const XMLElement* triggers = element->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS])) { + // Trigger + for (const XMLElement* trigger = triggers->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER]); trigger; + trigger = trigger->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER])) + _anm2_frame_deserialize(&animation->triggers.frames.emplace_back(Anm2Frame()), trigger); + } +} + +bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures) { + if (!self) + return false; + + if (path.empty()) { + log_error(ANM2_EMPTY_ERROR); + return false; + } + + XMLDocument document; + if (document.LoadFile(path.c_str()) != XML_SUCCESS) { + log_error(std::format(ANM2_PARSE_ERROR, path, document.ErrorStr())); + return false; + } + + anm2_new(self); + self->path = path; + std::string defaultAnimation{}; + int id{}; + + // Save old working directory and then use anm2's path as directory + // (used for loading textures from anm2 correctly which are relative) + std::filesystem::path workingPath = std::filesystem::current_path(); + working_directory_from_file_set(path); + + const XMLElement* root = document.RootElement(); + + // Info + if (const XMLElement* info = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_INFO])) { + for (const XMLAttribute* attribute = info->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_CREATED_BY: + self->createdBy = std::string(attribute->Value()); + break; // CreatedBy + case ANM2_ATTRIBUTE_CREATED_ON: + self->createdOn = std::string(attribute->Value()); + break; // CreatedOn + case ANM2_ATTRIBUTE_VERSION: + self->version = std::atoi(attribute->Value()); + break; // Version + case ANM2_ATTRIBUTE_FPS: + self->fps = std::atoi(attribute->Value()); + break; // FPS + default: + break; + } + } + } + + // Content + if (const XMLElement* content = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_CONTENT])) { + // Spritesheets + if (const XMLElement* spritesheets = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEETS])) { + for (const XMLElement* spritesheet = spritesheets->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET]); spritesheet; + spritesheet = spritesheet->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEET])) { + Anm2Spritesheet addSpritesheet; + + for (const XMLAttribute* attribute = spritesheet->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_PATH: + // Spritesheet paths from Isaac Rebirth are made with the assumption that + // the paths are case-insensitive (as the game was developed on Windows) + // However when using the resource dumper, the spritesheet paths are all lowercase (on Linux anyways) + // If the check doesn't work, set the spritesheet path to lowercase + // If the check doesn't work, replace backslashes with slashes + // At the minimum this should make all textures be able to be loaded on Linux + // If it doesn't work beyond that then that's on the user :^) + addSpritesheet.path = attribute->Value(); + if (!path_exists(addSpritesheet.path)) + addSpritesheet.path = string_to_lowercase(addSpritesheet.path); + if (!path_exists(addSpritesheet.path)) + addSpritesheet.path = string_backslash_replace(addSpritesheet.path); + if (isTextures) + texture_from_path_init(&addSpritesheet.texture, addSpritesheet.path); + break; + case ANM2_ATTRIBUTE_ID: + id = std::atoi(attribute->Value()); + break; + default: + break; + } + } + + self->spritesheets[id] = addSpritesheet; + } + } + + // Layers + if (const XMLElement* layers = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYERS])) { + for (const XMLElement* layer = layers->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER]); layer; + layer = layer->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER])) { + Anm2Layer addLayer; + + for (const XMLAttribute* attribute = layer->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_NAME: + addLayer.name = std::string(attribute->Value()); + break; // Name + case ANM2_ATTRIBUTE_ID: + id = std::atoi(attribute->Value()); + break; // ID + case ANM2_ATTRIBUTE_SPRITESHEET_ID: + addLayer.spritesheetID = std::atoi(attribute->Value()); + break; // ID + default: + break; + } + } + + self->layers[id] = addLayer; + } + } + + // Nulls + if (const XMLElement* nulls = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULLS])) { + for (const XMLElement* null = nulls->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL]); null; + null = null->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL])) { + Anm2Null addNull; + + for (const XMLAttribute* attribute = null->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_NAME: + addNull.name = std::string(attribute->Value()); + break; // Name + case ANM2_ATTRIBUTE_ID: + id = std::atoi(attribute->Value()); + break; // IDs + default: + break; + } + } + + self->nulls[id] = addNull; + } + } + + // Events + if (const XMLElement* events = content->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENTS])) { + // Event + for (const XMLElement* event = events->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT]); event; + event = event->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENT])) { + Anm2Event addEvent; + + for (const XMLAttribute* attribute = event->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_NAME: + addEvent.name = std::string(attribute->Value()); + break; // Name + case ANM2_ATTRIBUTE_ID: + id = std::atoi(attribute->Value()); + break; // ID + default: + break; + } + } + + self->events[id] = addEvent; + } + } + } + + // Animations + if (const XMLElement* animations = root->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS])) { + for (const XMLAttribute* attribute = animations->FirstAttribute(); attribute; attribute = attribute->Next()) { + switch (ANM2_ATTRIBUTE_STRING_TO_ENUM(attribute->Name())) { + case ANM2_ATTRIBUTE_DEFAULT_ANIMATION: + defaultAnimation = std::string(attribute->Value()); + break; // DefaultAnimation + default: + break; + } + } + + // Animation + for (const XMLElement* animation = animations->FirstChildElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION]); animation; + animation = animation->NextSiblingElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) + _anm2_animation_deserialize(&self->animations[map_next_id_get(self->animations)], animation); + } + + for (auto& [id, animation] : self->animations) + if (animation.name == defaultAnimation) + self->defaultAnimationID = id; + + if (isTextures) + anm2_spritesheet_texture_pixels_download(self); + + std::filesystem::current_path(workingPath); + + log_info(std::format(ANM2_READ_INFO, path)); + + return true; +} + +void anm2_animation_layer_animation_add(Anm2Animation* animation, int id) { + animation->layerAnimations[id] = Anm2Item{}; + animation->layerOrder.push_back(id); +} + +void anm2_animation_layer_animation_remove(Anm2Animation* animation, int id) { + animation->layerAnimations.erase(id); + vector_value_erase(animation->layerOrder, id); +} + +void anm2_animation_null_animation_add(Anm2Animation* animation, int id) { animation->nullAnimations[id] = Anm2Item{}; } + +void anm2_animation_null_animation_remove(Anm2Animation* animation, int id) { animation->nullAnimations.erase(id); } + +int anm2_layer_add(Anm2* self) { + int id = map_next_id_get(self->layers); + self->layers[id] = Anm2Layer{}; + return id; +} + +void anm2_layer_remove(Anm2* self, int id) { + self->layers.erase(id); + + for (auto& [_, animation] : self->animations) + anm2_animation_layer_animation_remove(&animation, id); +} + +int anm2_null_add(Anm2* self) { + int id = map_next_id_get(self->nulls); + self->nulls[id] = Anm2Null{}; + return id; +} + +void anm2_null_remove(Anm2* self, int id) { + if (!self->nulls.contains(id)) + return; + + self->nulls.erase(id); + + for (auto& [_, animation] : self->animations) + anm2_animation_null_animation_remove(&animation, id); +} + +int anm2_animation_add(Anm2* self, Anm2Animation* animation, int id) { + int addID = map_next_id_get(self->animations); + + Anm2Animation localAnimation; + Anm2Animation* addAnimation = animation ? animation : &localAnimation; + + if (!animation) + addAnimation->rootAnimation.frames.push_back(Anm2Frame{}); + + if (id != ID_NONE) { + map_insert_shift(self->animations, id, *addAnimation); + return id + 1; + } else + self->animations[addID] = *addAnimation; + + return addID; +} + +void anm2_animation_remove(Anm2* self, int id) { self->animations.erase(id); } + +void anm2_new(Anm2* self) { + *self = Anm2{}; + _anm2_created_on_set(self); +} + +Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference reference) { return map_find(self->animations, reference.animationID); } + +Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference) { + if (reference.itemType == ANM2_NONE) + return nullptr; + + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + + if (!animation) + return nullptr; + + switch (reference.itemType) { + case ANM2_ROOT: + return &animation->rootAnimation; + case ANM2_LAYER: + return map_find(animation->layerAnimations, reference.itemID); + case ANM2_NULL: + return map_find(animation->nullAnimations, reference.itemID); + case ANM2_TRIGGER: + return &animation->triggers; + default: + return nullptr; + } +} + +int anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, float time) { + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + if (!animation) + return INDEX_NONE; + if (time < 0 || time > animation->frameNum) + return INDEX_NONE; + + Anm2Item* item = anm2_item_from_reference(self, reference); + + if (!item) + return INDEX_NONE; + + if (reference.itemType == ANM2_TRIGGER) + for (auto [i, frame] : std::views::enumerate(item->frames)) + if (frame.atFrame == (int)time) + return i; + + int delayCurrent = 0; + int delayNext = 0; + + for (auto [i, frame] : std::views::enumerate(item->frames)) { + delayNext += frame.delay; + + if (time >= delayCurrent && time < delayNext) + return i; + + delayCurrent += frame.delay; + } + + return INDEX_NONE; +} + +float anm2_time_from_reference(Anm2* self, Anm2Reference reference) { + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + if (!animation) + return INDEX_NONE; + + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) + return INDEX_NONE; + + float time = 0.0f; + + if (reference.frameIndex <= 0) + return 0.0f; + + if (reference.frameIndex >= (int)item->frames.size()) { + for (auto& frame : item->frames) + time += (float)frame.delay; + return time; + } + + if (reference.itemType == ANM2_TRIGGER) + return (float)item->frames[reference.frameIndex].atFrame; + + for (int i = 0; i < reference.frameIndex; i++) + time += (float)item->frames[i].delay; + + return time; +} + +Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference reference) { + Anm2Item* item = anm2_item_from_reference(self, reference); + + if (!item) + return nullptr; + + if (reference.frameIndex <= INDEX_NONE || reference.frameIndex >= (int)item->frames.size()) + return nullptr; + + return &item->frames[reference.frameIndex]; +} + +void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, float time) { + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + if (!animation) + return; + + time = std::clamp(ROUND_NEAREST_MULTIPLE(time, 1.0f), 0.0f, animation->frameNum - 1.0f); + + Anm2Item* item = anm2_item_from_reference(self, reference); + + if (!item) + return; + + Anm2Frame* frameNext = nullptr; + int delayCurrent = 0; + int delayNext = 0; + + for (auto [i, iFrame] : std::views::enumerate(item->frames)) { + if (reference.itemType == ANM2_TRIGGER) { + if ((int)time == iFrame.atFrame) { + *frame = iFrame; + break; + } + } else { + *frame = iFrame; + + delayNext += frame->delay; + + if (time >= delayCurrent && time < delayNext) { + if (i + 1 < (int)item->frames.size()) + frameNext = &item->frames[i + 1]; + else + frameNext = nullptr; + break; + } + + delayCurrent += frame->delay; + } + } + + if (reference.itemType == ANM2_TRIGGER) + return; + + if (frame->isInterpolated && frameNext && frame->delay > 1) { + float 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->offsetRGB = glm::mix(frame->offsetRGB, frameNext->offsetRGB, interpolation); + frame->tintRGBA = glm::mix(frame->tintRGBA, frameNext->tintRGBA, interpolation); + } +} + +int anm2_animation_length_get(Anm2Animation* self) { + int length = 0; + + auto accumulate_max_delay = [&](const std::vector& frames) { + int delaySum = 0; + for (const auto& frame : frames) { + delaySum += frame.delay; + length = std::max(length, delaySum); + } + }; + + accumulate_max_delay(self->rootAnimation.frames); + + for (const auto& [_, item] : self->layerAnimations) + accumulate_max_delay(item.frames); + + for (const auto& [_, item] : self->nullAnimations) + accumulate_max_delay(item.frames); + + for (const auto& frame : self->triggers.frames) + length = std::max(length, frame.atFrame + 1); + + return length; +} + +void anm2_animation_length_set(Anm2Animation* self) { self->frameNum = anm2_animation_length_get(self); } + +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference reference) { + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!animation || !item) + return nullptr; + + Anm2Frame frameAdd = frame ? *frame : Anm2Frame{}; + + if (reference.itemType == ANM2_TRIGGER) { + int atFrame = frame ? frame->atFrame : 0; + + for (auto& frameCheck : item->frames) { + if (frameCheck.atFrame == atFrame) { + atFrame++; + break; + } + } + + frameAdd.atFrame = atFrame; + + return &item->frames.emplace_back(frameAdd); + } else { + int index = reference.frameIndex; + + if (index < 0 || index >= (int)item->frames.size()) { + item->frames.push_back(frameAdd); + return &item->frames.back(); + } else { + item->frames.insert(item->frames.begin() + index, frameAdd); + return &item->frames[(size_t)(index)]; + } + } +} + +void anm2_frame_remove(Anm2* self, Anm2Reference reference) { + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) + return; + item->frames.erase(item->frames.begin() + reference.frameIndex); +} + +void anm2_item_frame_set(Anm2* self, Anm2Reference reference, const Anm2FrameChange& change, Anm2ChangeType changeType, int start, int count) { + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) + return; + + int size = (int)item->frames.size(); + if (size == 0 || start >= size) + return; + if (start < 0 || count <= 0) + return; + + int end = std::min(start + count, size); + + for (int i = start; i < end; i++) { + Anm2Frame& dest = item->frames[i]; + +#define X(name, ctype, ...) \ + if (change.name) { \ + if constexpr (std::is_same_v) { \ + dest.name = *change.name; \ + } else { \ + switch (changeType) { \ + case ANM2_CHANGE_SET: \ + dest.name = *change.name; \ + break; \ + case ANM2_CHANGE_ADD: \ + dest.name += *change.name; \ + break; \ + case ANM2_CHANGE_SUBTRACT: \ + dest.name -= *change.name; \ + break; \ + } \ + } \ + } + ANM2_FRAME_MEMBERS +#undef X + } +} + +void anm2_animation_merge(Anm2* self, int animationID, const std::vector& mergeIDs, Anm2MergeType type) { + Anm2Animation newAnimation = self->animations[animationID]; + + auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) { + switch (type) { + case ANM2_MERGE_APPEND: + destinationItem.frames.insert(destinationItem.frames.end(), sourceItem.frames.begin(), sourceItem.frames.end()); + break; + case ANM2_MERGE_PREPEND: + destinationItem.frames.insert(destinationItem.frames.begin(), sourceItem.frames.begin(), sourceItem.frames.end()); + break; + case ANM2_MERGE_REPLACE: + if (destinationItem.frames.size() < sourceItem.frames.size()) + destinationItem.frames.resize(sourceItem.frames.size()); + for (int i = 0; i < (int)sourceItem.frames.size(); i++) + destinationItem.frames[i] = sourceItem.frames[i]; + break; + case ANM2_MERGE_IGNORE: + break; + } + }; + + for (auto mergeID : mergeIDs) { + if (animationID == mergeID) + continue; + + const Anm2Animation& mergeAnimation = self->animations[mergeID]; + + merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); + + for (const auto& [id, layerAnimation] : mergeAnimation.layerAnimations) + merge_item(newAnimation.layerAnimations[id], layerAnimation); + + for (const auto& [id, nullAnimation] : mergeAnimation.nullAnimations) + merge_item(newAnimation.nullAnimations[id], nullAnimation); + + merge_item(newAnimation.triggers, mergeAnimation.triggers); + } + + self->animations[animationID] = newAnimation; + + anm2_animation_length_set(&self->animations[animationID]); +} + +void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation) { + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) + return; + + Anm2Frame* frame = anm2_frame_from_reference(self, reference); + if (!frame) + return; + + Anm2Reference referenceNext = {reference.animationID, reference.itemType, reference.itemID, reference.frameIndex + 1}; + Anm2Frame* frameNext = anm2_frame_from_reference(self, referenceNext); + if (!frameNext) + frameNext = frame; + + const Anm2Frame baseFrame = *frame; + const Anm2Frame baseFrameNext = *frameNext; + + int delay = 0; + int insertIndex = reference.frameIndex; + + while (delay < baseFrame.delay) { + float interpolation = (float)delay / baseFrame.delay; + + Anm2Frame baked = *frame; + baked.delay = std::min(interval, baseFrame.delay - delay); + baked.isInterpolated = (insertIndex == 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.offsetRGB = glm::mix(baseFrame.offsetRGB, baseFrameNext.offsetRGB, interpolation); + baked.tintRGBA = glm::mix(baseFrame.tintRGBA, baseFrameNext.tintRGBA, interpolation); + + if (isRoundScale) + baked.scale = vec2((int)baked.scale.x, (int)baked.scale.y); + if (isRoundRotation) + baked.rotation = (int)baked.rotation; + + if (insertIndex == reference.frameIndex) + item->frames[insertIndex] = baked; + else + item->frames.insert(item->frames.begin() + insertIndex, baked); + insertIndex++; + + delay += baked.delay; + } +} + +void anm2_scale(Anm2* self, float scale) { + auto frame_scale = [&](Anm2Frame& frame) { + frame.position = vec2((int)(frame.position.x * scale), (int)(frame.position.y * scale)); + frame.size = vec2((int)(frame.size.x * scale), (int)(frame.size.y * scale)); + frame.crop = vec2((int)(frame.crop.x * scale), (int)(frame.crop.y * scale)); + frame.pivot = vec2((int)(frame.pivot.x * scale), (int)(frame.pivot.y * scale)); + }; + + for (auto& [_, animation] : self->animations) { + for (auto& frame : animation.rootAnimation.frames) + frame_scale(frame); + + for (auto& [_, layerAnimation] : animation.layerAnimations) + for (auto& frame : layerAnimation.frames) + frame_scale(frame); + + for (auto& [_, nullAnimation] : animation.nullAnimations) + for (auto& frame : nullAnimation.frames) + frame_scale(frame); + } +} + +void anm2_generate_from_grid(Anm2* self, Anm2Reference reference, vec2 startPosition, vec2 size, vec2 pivot, int columns, int count, int delay) { + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) + return; + + Anm2Reference frameReference = reference; + + for (int i = 0; i < count; i++) { + const int row = i / columns; + const int column = i % columns; + + Anm2Frame frame{}; + + frame.delay = delay; + frame.pivot = pivot; + frame.size = size; + frame.crop = startPosition + vec2(size.x * column, size.y * row); + + anm2_frame_add(self, &frame, frameReference); + frameReference.frameIndex++; + } +} + +void anm2_free(Anm2* self) { + for (auto& [id, spritesheet] : self->spritesheets) + texture_free(&spritesheet.texture); +} + +void anm2_spritesheet_texture_pixels_upload(Anm2* self) { + for (auto& [_, spritesheet] : self->spritesheets) { + Texture& texture = spritesheet.texture; + + if (texture.id != GL_ID_NONE && !texture.isInvalid) { + assert(!spritesheet.pixels.empty()); + glBindTexture(GL_TEXTURE_2D, texture.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture.size.x, texture.size.y, GL_RGBA, GL_UNSIGNED_BYTE, spritesheet.pixels.data()); + } + } +} + +void anm2_spritesheet_texture_pixels_download(Anm2* self) { + for (auto& [_, spritesheet] : self->spritesheets) { + Texture& texture = spritesheet.texture; + + if (texture.id != GL_ID_NONE && !texture.isInvalid) { + size_t bufferSize = (size_t)texture.size.x * (size_t)texture.size.y * TEXTURE_CHANNELS; + spritesheet.pixels.resize(bufferSize); + glBindTexture(GL_TEXTURE_2D, texture.id); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, spritesheet.pixels.data()); + } + } +} + +vec4 anm2_animation_rect_get(Anm2* self, Anm2Reference reference, bool isRootTransform) { + float minX = std::numeric_limits::infinity(); + float minY = std::numeric_limits::infinity(); + float maxX = -std::numeric_limits::infinity(); + float maxY = -std::numeric_limits::infinity(); + + bool any = false; + + Anm2Frame frame; + Anm2Frame root; + + Anm2Animation* animation = anm2_animation_from_reference(self, reference); + if (!animation) + return vec4(-1.0f); + + for (float t = 0.0f; t <= animation->frameNum; t += 1.0f) { + for (const auto& [id, _] : animation->layerAnimations) { + anm2_frame_from_time(self, &frame, {reference.animationID, ANM2_LAYER, id}, t); + if (!frame.isVisible) + continue; + if (frame.size.x <= 0 || frame.size.y <= 0) + continue; + + mat4 rootModel(1.0f); + if (isRootTransform) { + anm2_frame_from_time(self, &root, {reference.animationID, ANM2_ROOT}, t); + rootModel = quad_model_parent_get(root.position, root.pivot, PERCENT_TO_UNIT(root.scale), root.rotation); + } + + mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 fullModel = rootModel * model; + + vec2 corners[4] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + + for (auto& corner : corners) { + vec4 world = fullModel * 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}; +} + +void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string) { _anm2_animation_serialize(animation, nullptr, nullptr, string); } + +void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string) { _anm2_frame_serialize(frame, type, nullptr, nullptr, string); } + +bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml) { + XMLDocument document; + + auto animation_deserialize_error = [&]() { + log_error(std::format(ANM2_ANIMATION_PARSE_ERROR, xml, document.ErrorStr())); + return false; + }; + + if (document.Parse(xml.c_str()) != XML_SUCCESS) + return animation_deserialize_error(); + + const XMLElement* element = document.RootElement(); + if (!element) + return animation_deserialize_error(); + if (std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATION])) + return animation_deserialize_error(); + + _anm2_animation_deserialize(animation, element); + return true; +} + +bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml) { + XMLDocument document; + + auto frame_deserialize_error = [&]() { + log_error(std::format(ANM2_FRAME_PARSE_ERROR, xml, document.ErrorStr())); + return false; + }; + + if (document.Parse(xml.c_str()) != XML_SUCCESS) + return frame_deserialize_error(); + + const XMLElement* element = document.RootElement(); + if (!element) + return frame_deserialize_error(); + + if (std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_FRAME]) && + std::string(element->Name()) != std::string(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGER])) + return frame_deserialize_error(); + + _anm2_frame_deserialize(frame, element); + + return true; } /* void anm2_merge(Anm2* self, const std::string& path, Anm2MergeType type) { - Anm2 anm2; + Anm2 anm2; - if (anm2_deserialize(&anm2, path, false)) - { - std::unordered_map spritesheetMap; - for (auto& [id, spritesheet] : anm2.spritesheets) - { - bool isExists = false; - - for (auto& [selfID, selfSpritesheet] : self->spritesheets) - { - if (spritesheet.path == selfSpritesheet.path) isExists = true; - spritesheetMap[id] = selfID; - } + if (anm2_deserialize(&anm2, path, false)) + { + std::unordered_map spritesheetMap; + for (auto& [id, spritesheet] : anm2.spritesheets) + { + bool isExists = false; - if (isExists) continue; + for (auto& [selfID, selfSpritesheet] : self->spritesheets) + { + if (spritesheet.path == selfSpritesheet.path) isExists = true; + spritesheetMap[id] = selfID; + } - s32 nextID = map_next_id_get(self->spritesheets); - self->spritesheet[nextID] = spritesheet; - spritesheetMap[id] = nextID; - } + if (isExists) continue; - std::unordered_map layerMap; - for (auto& [id, layer] : anm2.layers) - { - bool isExists = false; + int nextID = map_next_id_get(self->spritesheets); + self->spritesheet[nextID] = spritesheet; + spritesheetMap[id] = nextID; + } - layer.spritesheetID = spritesheetMap[layer.spritesheetID]; + std::unordered_map layerMap; + for (auto& [id, layer] : anm2.layers) + { + bool isExists = false; - for (auto& [selfID, selfLayer] : self->layers) - { - if (layer.name == selfLayer.name) isExists = true; - layerMap[id] = selfID; - } + layer.spritesheetID = spritesheetMap[layer.spritesheetID]; - if (isExists) continue; + for (auto& [selfID, selfLayer] : self->layers) + { + if (layer.name == selfLayer.name) isExists = true; + layerMap[id] = selfID; + } - s32 nextID = map_next_id_get(self->layers); - self->layer[nextID] = layer; - layerMap[id] = nextID; - } + if (isExists) continue; - std::unordered_map nullMap; - for (auto& [id, null] : anm2.nulls) - { - bool isExists = false; - - for (auto& [selfID, selfNull] : self->nulls) - { - if (null.name == selfNull.name) isExists = true; - nullMap[id] = selfID; - } + int nextID = map_next_id_get(self->layers); + self->layer[nextID] = layer; + layerMap[id] = nextID; + } - if (isExists) continue; + std::unordered_map nullMap; + for (auto& [id, null] : anm2.nulls) + { + bool isExists = false; - s32 nextID = map_next_id_get(self->nulls); - self->null[nextID] = null; - nullMap[id] = nextID; - } + for (auto& [selfID, selfNull] : self->nulls) + { + if (null.name == selfNull.name) isExists = true; + nullMap[id] = selfID; + } - std::unordered_map eventMap; - for (auto& [id, event] : anm2.events) - { - bool isExists = false; - - for (auto& [selfID, selfEvent] : self->events) - { - if (event.name == selfEvent.name) isExists = true; - eventMap[id] = selfID; - } + if (isExists) continue; - if (isExists) continue; + int nextID = map_next_id_get(self->nulls); + self->null[nextID] = null; + nullMap[id] = nextID; + } - s32 nextID = map_next_id_get(self->events); - self->event[nextID] = event; - eventMap[id] = nextID; - } + std::unordered_map eventMap; + for (auto& [id, event] : anm2.events) + { + bool isExists = false; - for (auto& [id, animation] : anm2.animations) - { - bool isExists = false; - - for (auto& [selfID, selfAnimation] : self->animations) - { - if (event.name == selfAnimation.name) isExists = true; - eventMap[id] = selfID; - } + for (auto& [selfID, selfEvent] : self->events) + { + if (event.name == selfEvent.name) isExists = true; + eventMap[id] = selfID; + } - if (isExists) continue; + if (isExists) continue; - for (auto& frame : animation.rootAnimation.frames) - { + int nextID = map_next_id_get(self->events); + self->event[nextID] = event; + eventMap[id] = nextID; + } - - } + for (auto& [id, animation] : anm2.animations) + { + bool isExists = false; - for (auto& [layerID, layerAnimation] : animation.layerAnimations) - { - s32 newLayerID = layerMap[layerID]; + for (auto& [selfID, selfAnimation] : self->animations) + { + if (event.name == selfAnimation.name) isExists = true; + eventMap[id] = selfID; + } + + if (isExists) continue; + + for (auto& frame : animation.rootAnimation.frames) + { - } - } - } + } + + for (auto& [layerID, layerAnimation] : animation.layerAnimations) + { + int newLayerID = layerMap[layerID]; + + + } + } + } } */ \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 02c8614..b0affcb 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -2,8 +2,8 @@ #include "resources.h" -#define ANM2_SCALE_CONVERT(x) ((f32)x / 100.0f) -#define ANM2_TINT_CONVERT(x) ((f32)x / 255.0f) +#define ANM2_SCALE_CONVERT(x) ((float)x / 100.0f) +#define ANM2_TINT_CONVERT(x) ((float)x / 255.0f) #define ANM2_FPS_MIN 0 #define ANM2_FPS_DEFAULT 30 @@ -26,283 +26,240 @@ #define ANM2_EXTENSION "anm2" #define ANM2_SPRITESHEET_EXTENSION "png" -#define ANM2_ELEMENT_LIST \ - X(ANIMATED_ACTOR, "AnimatedActor") \ - X(INFO, "Info") \ - X(CONTENT, "Content") \ - X(SPRITESHEETS, "Spritesheets") \ - X(SPRITESHEET, "Spritesheet") \ - X(LAYERS, "Layers") \ - X(LAYER, "Layer") \ - X(NULLS, "Nulls") \ - X(NULL, "Null") \ - X(EVENTS, "Events") \ - X(EVENT, "Event") \ - X(ANIMATIONS, "Animations") \ - X(ANIMATION, "Animation") \ - X(ROOT_ANIMATION, "RootAnimation") \ - X(FRAME, "Frame") \ - X(LAYER_ANIMATIONS, "LayerAnimations") \ - X(LAYER_ANIMATION, "LayerAnimation") \ - X(NULL_ANIMATIONS, "NullAnimations") \ - X(NULL_ANIMATION, "NullAnimation") \ - X(TRIGGERS, "Triggers") \ - X(TRIGGER, "Trigger") +#define ANM2_ELEMENT_LIST \ + X(ANIMATED_ACTOR, "AnimatedActor") \ + X(INFO, "Info") \ + X(CONTENT, "Content") \ + X(SPRITESHEETS, "Spritesheets") \ + X(SPRITESHEET, "Spritesheet") \ + X(LAYERS, "Layers") \ + X(LAYER, "Layer") \ + X(NULLS, "Nulls") \ + X(NULL, "Null") \ + X(EVENTS, "Events") \ + X(EVENT, "Event") \ + X(ANIMATIONS, "Animations") \ + X(ANIMATION, "Animation") \ + X(ROOT_ANIMATION, "RootAnimation") \ + X(FRAME, "Frame") \ + X(LAYER_ANIMATIONS, "LayerAnimations") \ + X(LAYER_ANIMATION, "LayerAnimation") \ + X(NULL_ANIMATIONS, "NullAnimations") \ + X(NULL_ANIMATION, "NullAnimation") \ + X(TRIGGERS, "Triggers") \ + X(TRIGGER, "Trigger") -typedef enum -{ - #define X(name, str) ANM2_ELEMENT_##name, - ANM2_ELEMENT_LIST - #undef X - ANM2_ELEMENT_COUNT +typedef enum { +#define X(name, str) ANM2_ELEMENT_##name, + ANM2_ELEMENT_LIST +#undef X + ANM2_ELEMENT_COUNT } Anm2Element; -const inline char* ANM2_ELEMENT_STRINGS[] = -{ - #define X(name, str) str, +const inline char* ANM2_ELEMENT_STRINGS[] = { +#define X(name, str) str, ANM2_ELEMENT_LIST - #undef X +#undef X }; DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ELEMENT_STRING_TO_ENUM, Anm2Element, ANM2_ELEMENT_STRINGS, ANM2_ELEMENT_COUNT) -#define ANM2_ATTRIBUTE_LIST \ - X(CREATED_BY, "CreatedBy") \ - X(CREATED_ON, "CreatedOn") \ - X(VERSION, "Version") \ - X(FPS, "Fps") \ - X(ID, "Id") \ - X(PATH, "Path") \ - X(NAME, "Name") \ - X(SPRITESHEET_ID, "SpritesheetId") \ - X(SHOW_RECT, "ShowRect") \ - X(DEFAULT_ANIMATION, "DefaultAnimation") \ - X(FRAME_NUM, "FrameNum") \ - X(LOOP, "Loop") \ - X(X_POSITION, "XPosition") \ - X(Y_POSITION, "YPosition") \ - X(X_PIVOT, "XPivot") \ - X(Y_PIVOT, "YPivot") \ - X(X_CROP, "XCrop") \ - X(Y_CROP, "YCrop") \ - X(WIDTH, "Width") \ - X(HEIGHT, "Height") \ - X(X_SCALE, "XScale") \ - X(Y_SCALE, "YScale") \ - X(DELAY, "Delay") \ - X(VISIBLE, "Visible") \ - X(RED_TINT, "RedTint") \ - X(GREEN_TINT, "GreenTint") \ - X(BLUE_TINT, "BlueTint") \ - X(ALPHA_TINT, "AlphaTint") \ - X(RED_OFFSET, "RedOffset") \ - X(GREEN_OFFSET, "GreenOffset") \ - X(BLUE_OFFSET, "BlueOffset") \ - X(ROTATION, "Rotation") \ - X(INTERPOLATED, "Interpolated") \ - X(LAYER_ID, "LayerId") \ - X(NULL_ID, "NullId") \ - X(EVENT_ID, "EventId") \ - X(AT_FRAME, "AtFrame") +#define ANM2_ATTRIBUTE_LIST \ + X(CREATED_BY, "CreatedBy") \ + X(CREATED_ON, "CreatedOn") \ + X(VERSION, "Version") \ + X(FPS, "Fps") \ + X(ID, "Id") \ + X(PATH, "Path") \ + X(NAME, "Name") \ + X(SPRITESHEET_ID, "SpritesheetId") \ + X(SHOW_RECT, "ShowRect") \ + X(DEFAULT_ANIMATION, "DefaultAnimation") \ + X(FRAME_NUM, "FrameNum") \ + X(LOOP, "Loop") \ + X(X_POSITION, "XPosition") \ + X(Y_POSITION, "YPosition") \ + X(X_PIVOT, "XPivot") \ + X(Y_PIVOT, "YPivot") \ + X(X_CROP, "XCrop") \ + X(Y_CROP, "YCrop") \ + X(WIDTH, "Width") \ + X(HEIGHT, "Height") \ + X(X_SCALE, "XScale") \ + X(Y_SCALE, "YScale") \ + X(DELAY, "Delay") \ + X(VISIBLE, "Visible") \ + X(RED_TINT, "RedTint") \ + X(GREEN_TINT, "GreenTint") \ + X(BLUE_TINT, "BlueTint") \ + X(ALPHA_TINT, "AlphaTint") \ + X(RED_OFFSET, "RedOffset") \ + X(GREEN_OFFSET, "GreenOffset") \ + X(BLUE_OFFSET, "BlueOffset") \ + X(ROTATION, "Rotation") \ + X(INTERPOLATED, "Interpolated") \ + X(LAYER_ID, "LayerId") \ + X(NULL_ID, "NullId") \ + X(EVENT_ID, "EventId") \ + X(AT_FRAME, "AtFrame") -typedef enum -{ - #define X(name, str) ANM2_ATTRIBUTE_##name, - ANM2_ATTRIBUTE_LIST - #undef X - ANM2_ATTRIBUTE_COUNT +typedef enum { +#define X(name, str) ANM2_ATTRIBUTE_##name, + ANM2_ATTRIBUTE_LIST +#undef X + ANM2_ATTRIBUTE_COUNT } Anm2Attribute; -static const char* ANM2_ATTRIBUTE_STRINGS[] = -{ - #define X(name, str) str, +static const char* ANM2_ATTRIBUTE_STRINGS[] = { +#define X(name, str) str, ANM2_ATTRIBUTE_LIST - #undef X +#undef X }; DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ATTRIBUTE_STRING_TO_ENUM, Anm2Attribute, ANM2_ATTRIBUTE_STRINGS, ANM2_ATTRIBUTE_COUNT) -enum Anm2Type -{ - ANM2_NONE, - ANM2_ROOT, - ANM2_LAYER, - ANM2_NULL, - ANM2_TRIGGERS, - ANM2_COUNT +enum Anm2Type { ANM2_NONE, ANM2_ROOT, ANM2_LAYER, ANM2_NULL, ANM2_TRIGGER, ANM2_COUNT }; + +struct Anm2Spritesheet { + std::string path{}; + Texture texture; + std::vector pixels; + + auto operator<=>(const Anm2Spritesheet&) const = default; }; -struct Anm2Spritesheet -{ - std::string path{}; - Texture texture; - std::vector pixels; +struct Anm2Layer { + std::string name = "New Layer"; + int spritesheetID{}; - auto operator<=>(const Anm2Spritesheet&) const = default; + auto operator<=>(const Anm2Layer&) const = default; }; -struct Anm2Layer -{ - std::string name = "New Layer"; - s32 spritesheetID{}; +struct Anm2Null { + std::string name = "New Null"; + bool isShowRect = false; - auto operator<=>(const Anm2Layer&) const = default; + auto operator<=>(const Anm2Null&) const = default; }; -struct Anm2Null -{ - std::string name = "New Null"; - bool isShowRect = false; +struct Anm2Event { + std::string name = "New Event"; - auto operator<=>(const Anm2Null&) const = default; + auto operator<=>(const Anm2Event&) const = default; }; -struct Anm2Event -{ - std::string name = "New Event"; +#define ANM2_FRAME_MEMBERS \ + X(isVisible, bool, true) \ + X(isInterpolated, bool, false) \ + X(rotation, float, 0.0f) \ + X(delay, int, ANM2_FRAME_DELAY_MIN) \ + X(atFrame, int, INDEX_NONE) \ + X(eventID, int, ID_NONE) \ + X(crop, vec2, {}) \ + X(pivot, vec2, {}) \ + X(position, vec2, {}) \ + X(size, vec2, {}) \ + X(scale, vec2, {100, 100}) \ + X(offsetRGB, vec3, COLOR_OFFSET_NONE) \ + X(tintRGBA, vec4, COLOR_OPAQUE) - auto operator<=>(const Anm2Event&) const = default; +struct Anm2Frame { +#define X(name, type, ...) type name = __VA_ARGS__; + ANM2_FRAME_MEMBERS +#undef X + auto operator<=>(const Anm2Frame&) const = default; }; -struct Anm2Frame -{ - bool isVisible = true; - bool isInterpolated = false; - f32 rotation{}; - s32 delay = ANM2_FRAME_DELAY_MIN; - s32 atFrame = INDEX_NONE; - s32 eventID = ID_NONE; - vec2 crop{}; - vec2 pivot{}; - vec2 position{}; - vec2 size{}; - vec2 scale = {100, 100}; - vec3 offsetRGB = COLOR_OFFSET_NONE; - vec4 tintRGBA = COLOR_OPAQUE; - - auto operator<=>(const Anm2Frame&) const = default; +struct Anm2FrameChange { +#define X(name, type, ...) std::optional name{}; + ANM2_FRAME_MEMBERS +#undef X }; -struct Anm2Item -{ - bool isVisible = true; - std::vector frames; +struct Anm2Item { + bool isVisible = true; + std::vector frames; - auto operator<=>(const Anm2Item&) const = default; + auto operator<=>(const Anm2Item&) const = default; }; -struct Anm2Animation -{ - s32 frameNum = ANM2_FRAME_NUM_MIN; - std::string name = "New Animation"; - bool isLoop = true; - Anm2Item rootAnimation; - std::unordered_map layerAnimations; - std::vector layerOrder; - std::map nullAnimations; - Anm2Item triggers; - - auto operator<=>(const Anm2Animation&) const = default; +struct Anm2Animation { + int frameNum = ANM2_FRAME_NUM_MIN; + std::string name = "New Animation"; + bool isLoop = true; + Anm2Item rootAnimation; + std::unordered_map layerAnimations; + std::vector layerOrder; + std::map nullAnimations; + Anm2Item triggers; + + auto operator<=>(const Anm2Animation&) const = default; }; -struct Anm2 -{ - std::string path{}; - std::string createdBy = "robot"; - std::string createdOn{}; - std::map spritesheets; - std::map layers; - std::map nulls; - std::map events; - std::map animations; - s32 defaultAnimationID = ID_NONE; - s32 fps = ANM2_FPS_DEFAULT; - s32 version{}; +struct Anm2 { + std::string path{}; + std::string createdBy = "robot"; + std::string createdOn{}; + std::map spritesheets; + std::map layers; + std::map nulls; + std::map events; + std::map animations; + int defaultAnimationID = ID_NONE; + int fps = ANM2_FPS_DEFAULT; + int version{}; - auto operator<=>(const Anm2&) const = default; + auto operator<=>(const Anm2&) const = default; }; -struct Anm2Reference -{ - s32 animationID = ID_NONE; - Anm2Type itemType = ANM2_NONE; - s32 itemID = ID_NONE; - s32 frameIndex = INDEX_NONE; - f32 time = VALUE_NONE; - auto operator<=>(const Anm2Reference&) const = default; +struct Anm2Reference { + int animationID = ID_NONE; + Anm2Type itemType = ANM2_NONE; + int itemID = ID_NONE; + int frameIndex = INDEX_NONE; + float time = VALUE_NONE; + auto operator<=>(const Anm2Reference&) const = default; }; -struct Anm2FrameChange -{ - std::optional isVisible; - std::optional isInterpolated; - std::optional rotation; - std::optional delay; - std::optional crop; - std::optional pivot; - std::optional position; - std::optional size; - std::optional scale; - std::optional offsetRGB; - std::optional tintRGBA; -}; +enum Anm2MergeType { ANM2_MERGE_APPEND, ANM2_MERGE_REPLACE, ANM2_MERGE_PREPEND, ANM2_MERGE_IGNORE }; -enum Anm2MergeType -{ - ANM2_MERGE_APPEND, - ANM2_MERGE_REPLACE, - ANM2_MERGE_PREPEND, - ANM2_MERGE_IGNORE -}; +enum Anm2ChangeType { ANM2_CHANGE_ADD, ANM2_CHANGE_SUBTRACT, ANM2_CHANGE_SET }; -enum Anm2ChangeType -{ - ANM2_CHANGE_ADD, - ANM2_CHANGE_SUBTRACT, - ANM2_CHANGE_SET -}; +enum OnionskinDrawOrder { ONIONSKIN_BELOW, ONIONSKIN_ABOVE }; -enum OnionskinDrawOrder -{ - ONIONSKIN_BELOW, - ONIONSKIN_ABOVE -}; - -Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference); -Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference); -Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference); -Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference); +Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference reference); +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference reference); +Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference reference); +Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference reference); bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml); bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true); bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml); bool anm2_serialize(Anm2* self, const std::string& path); -s32 anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, s32 id = ID_NONE); -s32 anm2_animation_length_get(Anm2Animation* self); -s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); -s32 anm2_layer_add(Anm2* self); -s32 anm2_null_add(Anm2* self); -vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference* reference, bool isRootTransform); -void anm2_animation_layer_animation_add(Anm2Animation* animation, s32 id); -void anm2_animation_layer_animation_remove(Anm2Animation* animation, s32 id); +int anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, int id = ID_NONE); +int anm2_animation_length_get(Anm2Animation* self); +int anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, float time); +int anm2_layer_add(Anm2* self); +int anm2_null_add(Anm2* self); +vec4 anm2_animation_rect_get(Anm2* anm2, Anm2Reference reference, bool isRootTransform); +void anm2_animation_layer_animation_add(Anm2Animation* animation, int id); +void anm2_animation_layer_animation_remove(Anm2Animation* animation, int id); void anm2_animation_length_set(Anm2Animation* self); -void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type); -void anm2_animation_null_animation_add(Anm2Animation* animation, s32 id); -void anm2_animation_null_animation_remove(Anm2Animation* animation, s32 id); -void anm2_animation_remove(Anm2* self, s32 id); +void anm2_animation_merge(Anm2* self, int animationID, const std::vector& mergeIDs, Anm2MergeType type); +void anm2_animation_null_animation_add(Anm2Animation* animation, int id); +void anm2_animation_null_animation_remove(Anm2Animation* animation, int id); +void anm2_animation_remove(Anm2* self, int id); void anm2_animation_serialize_to_string(Anm2Animation* animation, std::string* string); -void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation); -void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); -void anm2_frame_remove(Anm2* self, Anm2Reference* reference); +void anm2_frame_bake(Anm2* self, Anm2Reference reference, int interval, bool isRoundScale, bool isRoundRotation); +void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, float time); +void anm2_frame_remove(Anm2* self, Anm2Reference reference); void anm2_frame_serialize_to_string(Anm2Frame* frame, Anm2Type type, std::string* string); void anm2_free(Anm2* self); -void anm2_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay); -void anm2_item_frame_set(Anm2* self, Anm2Reference* reference, const Anm2FrameChange& change, Anm2ChangeType type, s32 start, s32 count); -void anm2_layer_remove(Anm2* self, s32 id); +void anm2_generate_from_grid(Anm2* self, Anm2Reference reference, vec2 startPosition, vec2 size, vec2 pivot, int columns, int count, int delay); +void anm2_item_frame_set(Anm2* self, Anm2Reference reference, const Anm2FrameChange& change, Anm2ChangeType changeType, int start, int count); +void anm2_layer_remove(Anm2* self, int id); void anm2_new(Anm2* self); -void anm2_null_remove(Anm2* self, s32 id); -void anm2_reference_clear(Anm2Reference* self); -void anm2_reference_frame_clear(Anm2Reference* self); -void anm2_reference_item_clear(Anm2Reference* self); -void anm2_scale(Anm2* self, f32 scale); +void anm2_null_remove(Anm2* self, int id); +void anm2_scale(Anm2* self, float scale); void anm2_spritesheet_texture_pixels_download(Anm2* self); -void anm2_spritesheet_texture_pixels_upload(Anm2* self); \ No newline at end of file +void anm2_spritesheet_texture_pixels_upload(Anm2* self); +float anm2_time_from_reference(Anm2* self, Anm2Reference reference); \ No newline at end of file diff --git a/src/canvas.cpp b/src/canvas.cpp index 806ec9a..5e55649 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1,244 +1,224 @@ #include "canvas.h" -static void _canvas_framebuffer_set(Canvas* self, const ivec2& size) -{ - self->size = size; - self->previousSize = size; - - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); +static void _canvas_framebuffer_set(Canvas* self, const ivec2& size) { + self->size = size; + self->previousSize = size; - glBindTexture(GL_TEXTURE_2D, self->framebuffer); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->framebuffer, 0); + glBindTexture(GL_TEXTURE_2D, self->framebuffer); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBindRenderbuffer(GL_RENDERBUFFER, self->rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, self->size.x, self->size.y); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->framebuffer, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindRenderbuffer(GL_RENDERBUFFER, self->rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, self->size.x, self->size.y); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); } -void canvas_init(Canvas* self, const ivec2& size) -{ - // Axis - glGenVertexArrays(1, &self->axisVAO); - glGenBuffers(1, &self->axisVBO); +void canvas_init(Canvas* self, const ivec2& size) { + // Axis + glGenVertexArrays(1, &self->axisVAO); + glGenBuffers(1, &self->axisVBO); - glBindVertexArray(self->axisVAO); + glBindVertexArray(self->axisVAO); - glBindBuffer(GL_ARRAY_BUFFER, self->axisVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, sizeof(f32), (void*)0); - - // Grid - glGenVertexArrays(1, &self->gridVAO); - glGenBuffers(1, &self->gridVBO); - - glBindVertexArray(self->gridVAO); - glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); + glBindBuffer(GL_ARRAY_BUFFER, self->axisVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, sizeof(float), (void*)0); - // Rect - glGenVertexArrays(1, &self->rectVAO); - glGenBuffers(1, &self->rectVBO); + // Grid + glGenVertexArrays(1, &self->gridVAO); + glGenBuffers(1, &self->gridVBO); - glBindVertexArray(self->rectVAO); + glBindVertexArray(self->gridVAO); + glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); - glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_RECT_VERTICES), CANVAS_RECT_VERTICES, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + // Rect + glGenVertexArrays(1, &self->rectVAO); + glGenBuffers(1, &self->rectVBO); - // Grid - glGenVertexArrays(1, &self->gridVAO); - glBindVertexArray(self->gridVAO); + glBindVertexArray(self->rectVAO); - glGenBuffers(1, &self->gridVBO); - glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_GRID_VERTICES), CANVAS_GRID_VERTICES, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_RECT_VERTICES), CANVAS_RECT_VERTICES, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); - glBindVertexArray(0); + // Grid + glGenVertexArrays(1, &self->gridVAO); + glBindVertexArray(self->gridVAO); - // Texture - glGenVertexArrays(1, &self->textureVAO); - glGenBuffers(1, &self->textureVBO); - glGenBuffers(1, &self->textureEBO); + glGenBuffers(1, &self->gridVBO); + glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_GRID_VERTICES), CANVAS_GRID_VERTICES, GL_STATIC_DRAW); - glBindVertexArray(self->textureVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, (void*)0); - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, nullptr, GL_DYNAMIC_DRAW); + glBindVertexArray(0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_DYNAMIC_DRAW); + // Texture + glGenVertexArrays(1, &self->textureVAO); + glGenBuffers(1, &self->textureVBO); + glGenBuffers(1, &self->textureEBO); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)0); + glBindVertexArray(self->textureVAO); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); - - glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 4 * 4, nullptr, GL_DYNAMIC_DRAW); - // Framebuffer - glGenTextures(1, &self->framebuffer); - glGenFramebuffers(1, &self->fbo); - glGenRenderbuffers(1, &self->rbo); - _canvas_framebuffer_set(self, size); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_DYNAMIC_DRAW); - self->isInit = true; + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); + + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float))); + + glBindVertexArray(0); + + // Framebuffer + glGenTextures(1, &self->framebuffer); + glGenFramebuffers(1, &self->fbo); + glGenRenderbuffers(1, &self->rbo); + _canvas_framebuffer_set(self, size); + + self->isInit = true; } -mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin) -{ - f32 zoomFactor = PERCENT_TO_UNIT(zoom); - mat4 projection = glm::ortho(0.0f, (f32)self->size.x, 0.0f, (f32)self->size.y, -1.0f, 1.0f); - mat4 view = mat4{1.0f}; - vec2 size = vec2(self->size.x, self->size.y); - - switch (origin) - { - case ORIGIN_TOP_LEFT: - view = glm::translate(view, vec3(pan, 0.0f)); - break; - default: - view = glm::translate(view, vec3((size * 0.5f) + pan, 0.0f)); - break; - } +mat4 canvas_transform_get(Canvas* self, vec2 pan, float zoom, OriginType origin) { + float zoomFactor = PERCENT_TO_UNIT(zoom); + mat4 projection = glm::ortho(0.0f, (float)self->size.x, 0.0f, (float)self->size.y, -1.0f, 1.0f); + mat4 view = mat4{1.0f}; + vec2 size = vec2(self->size.x, self->size.y); - view = glm::scale(view, vec3(zoomFactor, zoomFactor, 1.0f)); - - return projection * view; + switch (origin) { + case ORIGIN_TOP_LEFT: + view = glm::translate(view, vec3(pan, 0.0f)); + break; + default: + view = glm::translate(view, vec3((size * 0.5f) + pan, 0.0f)); + break; + } + + view = glm::scale(view, vec3(zoomFactor, zoomFactor, 1.0f)); + + return projection * view; } -void canvas_clear(vec4& color) -{ - glClearColor(color.r, color.g, color.b, color.a); - glClear(GL_COLOR_BUFFER_BIT); +void canvas_clear(vec4& color) { + glClearColor(color.r, color.g, color.b, color.a); + glClear(GL_COLOR_BUFFER_BIT); } -void canvas_viewport_set(Canvas* self) -{ - glViewport(0, 0, (s32)self->size.x, (s32)self->size.y); +void canvas_viewport_set(Canvas* self) { glViewport(0, 0, (int)self->size.x, (int)self->size.y); } + +void canvas_framebuffer_resize_check(Canvas* self) { + if (self->previousSize != self->size) + _canvas_framebuffer_set(self, self->size); } -void canvas_framebuffer_resize_check(Canvas* self) -{ - if (self->previousSize != self->size) - _canvas_framebuffer_set(self, self->size); +void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color) { + mat4 inverseTransform = glm::inverse(transform); + + glUseProgram(shader); + + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_MODEL), 1, GL_FALSE, glm::value_ptr(inverseTransform)); + glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_SIZE), size.x, size.y); + glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_OFFSET), offset.x, offset.y); + glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a); + + glBindVertexArray(self->gridVAO); + glDrawArrays(GL_TRIANGLES, 0, 3); + glBindVertexArray(0); + + glUseProgram(0); } -void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color) -{ - mat4 inverseTransform = glm::inverse(transform); +void canvas_texture_draw(Canvas* self, GLuint& shader, GLuint& texture, mat4& transform, const float* vertices, vec4 tint, vec3 colorOffset) { + glUseProgram(shader); - glUseProgram(shader); + glBindVertexArray(self->textureVAO); - glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_MODEL), 1, GL_FALSE, glm::value_ptr(inverseTransform)); - glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_SIZE), size.x, size.y); - glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_OFFSET), offset.x, offset.y); - glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a); + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_TEXTURE_VERTICES), vertices, GL_DYNAMIC_DRAW); - glBindVertexArray(self->gridVAO); - glDrawArrays(GL_TRIANGLES, 0, 3); - glBindVertexArray(0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); - glUseProgram(0); + glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_TEXTURE), 0); + glUniform3fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(colorOffset)); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TINT), 1, value_ptr(tint)); + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); } -void canvas_texture_draw(Canvas* self, GLuint& shader, GLuint& texture, mat4& transform, const f32* vertices, vec4 tint, vec3 colorOffset) -{ - glUseProgram(shader); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_TEXTURE_VERTICES), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture); +void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color) { + glUseProgram(shader); - glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_TEXTURE), 0); - glUniform3fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(colorOffset)); - glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TINT), 1, value_ptr(tint)); - glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + glBindVertexArray(self->rectVAO); + + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); + + glDrawArrays(GL_LINE_LOOP, 0, 4); + + glBindVertexArray(0); + glUseProgram(0); } -void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color) -{ - glUseProgram(shader); +void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color) { + vec4 originNDC = transform * vec4(0.0f, 0.0f, 0.0f, 1.0f); + originNDC /= originNDC.w; - glBindVertexArray(self->rectVAO); - - glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); - glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); - - glDrawArrays(GL_LINE_LOOP, 0, 4); - - glBindVertexArray(0); - glUseProgram(0); + glUseProgram(shader); + glBindVertexArray(self->axisVAO); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); + glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_ORIGIN_NDC), originNDC.x, originNDC.y); + glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 0); + glDrawArrays(GL_LINES, 0, 2); + glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 1); + glDrawArrays(GL_LINES, 0, 2); + glBindVertexArray(0); + glUseProgram(0); } +void canvas_bind(Canvas* self) { glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); } -void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color) -{ - vec4 originNDC = transform * vec4(0.0f, 0.0f, 0.0f, 1.0f); - originNDC /= originNDC.w; +void canvas_unbind(void) { glBindFramebuffer(GL_FRAMEBUFFER, 0); } - glUseProgram(shader); - glBindVertexArray(self->axisVAO); - glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); - glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_ORIGIN_NDC), originNDC.x, originNDC.y); - glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 0); - glDrawArrays(GL_LINES, 0, 2); - glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 1); - glDrawArrays(GL_LINES, 0, 2); - glBindVertexArray(0); - glUseProgram(0); -} +void canvas_free(Canvas* self) { + if (!self->isInit) + return; -void canvas_bind(Canvas* self) -{ - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); -} - -void canvas_unbind(void) -{ - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void canvas_free(Canvas* self) -{ - if (!self->isInit) return; - - glDeleteFramebuffers(1, &self->fbo); - glDeleteRenderbuffers(1, &self->rbo); - glDeleteTextures(1, &self->framebuffer); - glDeleteVertexArrays(1, &self->axisVAO); - glDeleteVertexArrays(1, &self->rectVAO); - glDeleteVertexArrays(1, &self->gridVAO); - glDeleteVertexArrays(1, &self->textureVAO); - glDeleteBuffers(1, &self->axisVBO); - glDeleteBuffers(1, &self->rectVBO); - glDeleteBuffers(1, &self->gridVBO); - glDeleteBuffers(1, &self->textureVBO); - glDeleteBuffers(1, &self->textureEBO); + glDeleteFramebuffers(1, &self->fbo); + glDeleteRenderbuffers(1, &self->rbo); + glDeleteTextures(1, &self->framebuffer); + glDeleteVertexArrays(1, &self->axisVAO); + glDeleteVertexArrays(1, &self->rectVAO); + glDeleteVertexArrays(1, &self->gridVAO); + glDeleteVertexArrays(1, &self->textureVAO); + glDeleteBuffers(1, &self->axisVBO); + glDeleteBuffers(1, &self->rectVBO); + glDeleteBuffers(1, &self->gridVBO); + glDeleteBuffers(1, &self->textureVBO); + glDeleteBuffers(1, &self->textureEBO); } \ No newline at end of file diff --git a/src/canvas.h b/src/canvas.h index 06a9d2f..d7f58e2 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -13,64 +13,40 @@ const inline vec2 CANVAS_PIVOT_SIZE = {4, 4}; const inline vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f}; -const inline f32 CANVAS_AXIS_VERTICES[] = {-1.0f, 1.0f}; +const inline float CANVAS_AXIS_VERTICES[] = {-1.0f, 1.0f}; -const inline f32 CANVAS_GRID_VERTICES[] = -{ - -1.0f, -1.0f, - 3.0f, -1.0f, - -1.0f, 3.0f +const inline float CANVAS_GRID_VERTICES[] = {-1.0f, -1.0f, 3.0f, -1.0f, -1.0f, 3.0f}; + +const inline float CANVAS_RECT_VERTICES[] = {0, 0, 1, 0, 1, 1, 0, 1}; + +const inline float CANVAS_TEXTURE_VERTICES[] = {0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f, 1, 1, 1.0f, 1.0f, 0, 1, 0.0f, 1.0f}; + +struct Canvas { + GLuint fbo{}; + GLuint rbo{}; + GLuint axisVAO{}; + GLuint axisVBO{}; + GLuint rectVAO{}; + GLuint rectVBO{}; + GLuint gridVAO{}; + GLuint gridVBO{}; + GLuint framebuffer{}; + GLuint textureVAO{}; + GLuint textureVBO{}; + GLuint textureEBO{}; + ivec2 size{}; + ivec2 previousSize{}; + bool isInit = false; }; -const inline f32 CANVAS_RECT_VERTICES[] = -{ - 0, 0, - 1, 0, - 1, 1, - 0, 1 -}; - -const inline f32 CANVAS_TEXTURE_VERTICES[] = -{ - 0, 0, 0.0f, 0.0f, - 1, 0, 1.0f, 0.0f, - 1, 1, 1.0f, 1.0f, - 0, 1, 0.0f, 1.0f -}; - -struct Canvas -{ - GLuint fbo{}; - GLuint rbo{}; - GLuint axisVAO{}; - GLuint axisVBO{}; - GLuint rectVAO{}; - GLuint rectVBO{}; - GLuint gridVAO{}; - GLuint gridVBO{}; - GLuint framebuffer{}; - GLuint textureVAO{}; - GLuint textureVBO{}; - GLuint textureEBO{}; - ivec2 size{}; - ivec2 previousSize{}; - bool isInit = false; -}; - -#define UV_VERTICES(uvMin, uvMax) \ -{ \ - 0, 0, uvMin.x, uvMin.y, \ - 1, 0, uvMax.x, uvMin.y, \ - 1, 1, uvMax.x, uvMax.y, \ - 0, 1, uvMin.x, uvMax.y \ -} +#define UV_VERTICES(uvMin, uvMax) {0, 0, uvMin.x, uvMin.y, 1, 0, uvMax.x, uvMin.y, 1, 1, uvMax.x, uvMax.y, 0, 1, uvMin.x, uvMax.y} #define ATLAS_UV_MIN(type) (ATLAS_POSITION(type) / TEXTURE_ATLAS_SIZE) #define ATLAS_UV_MAX(type) ((ATLAS_POSITION(type) + ATLAS_SIZE(type)) / TEXTURE_ATLAS_SIZE) #define ATLAS_UV_ARGS(type) ATLAS_UV_MIN(type), ATLAS_UV_MAX(type) #define ATLAS_UV_VERTICES(type) UV_VERTICES(ATLAS_UV_MIN(type), ATLAS_UV_MAX(type)) -mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin); +mat4 canvas_transform_get(Canvas* self, vec2 pan, float zoom, OriginType origin); void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color); void canvas_bind(Canvas* self); void canvas_clear(vec4& color); @@ -83,13 +59,5 @@ void canvas_framebuffer_resize_check(Canvas* self); void canvas_unbind(void); void canvas_viewport_set(Canvas* self); -void canvas_texture_draw -( - Canvas* self, - GLuint& shader, - GLuint& texture, - mat4& transform, - const f32* vertices = CANVAS_TEXTURE_VERTICES, - vec4 tint = COLOR_OPAQUE, - vec3 colorOffset = COLOR_OFFSET_NONE -); \ No newline at end of file +void canvas_texture_draw(Canvas* self, GLuint& shader, GLuint& texture, mat4& transform, const float* vertices = CANVAS_TEXTURE_VERTICES, + vec4 tint = COLOR_OPAQUE, vec3 colorOffset = COLOR_OFFSET_NONE); \ No newline at end of file diff --git a/src/clipboard.cpp b/src/clipboard.cpp index 874db01..3e5d09d 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -1,113 +1,102 @@ #include "clipboard.h" -void clipboard_copy(Clipboard* self) -{ - std::string clipboardText{}; - - auto clipboard_text_set = [&]() - { - if (!SDL_SetClipboardText(clipboardText.c_str())) - log_warning(std::format(CLIPBOARD_TEXT_SET_WARNING, SDL_GetError())); - }; - - switch (self->type) - { - case CLIPBOARD_FRAME: - { - Anm2Reference* reference = std::get_if(&self->location); - if (!reference) break; - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, reference); - if (!frame) break; - anm2_frame_serialize_to_string(frame, reference->itemType, &clipboardText); - clipboard_text_set(); - break; - } - case CLIPBOARD_ANIMATION: - { - s32* id = std::get_if(&self->location); - if (!id) break; - Anm2Animation* animation = map_find(self->anm2->animations, *id); - if (!animation) break; - anm2_animation_serialize_to_string(animation, &clipboardText); - clipboard_text_set(); - break; - } - break; - default: - break; - } +void clipboard_copy(Clipboard* self) { + std::string clipboardText{}; + + auto clipboard_text_set = [&]() { + if (!SDL_SetClipboardText(clipboardText.c_str())) + log_warning(std::format(CLIPBOARD_TEXT_SET_WARNING, SDL_GetError())); + }; + + switch (self->type) { + case CLIPBOARD_FRAME: { + Anm2Reference* reference = std::get_if(&self->location); + if (!reference) + break; + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *reference); + if (!frame) + break; + anm2_frame_serialize_to_string(frame, reference->itemType, &clipboardText); + clipboard_text_set(); + break; + } + case CLIPBOARD_ANIMATION: { + int* id = std::get_if(&self->location); + if (!id) + break; + Anm2Animation* animation = map_find(self->anm2->animations, *id); + if (!animation) + break; + anm2_animation_serialize_to_string(animation, &clipboardText); + clipboard_text_set(); + break; + } break; + default: + break; + } } -void clipboard_cut(Clipboard* self) -{ - clipboard_copy(self); +void clipboard_cut(Clipboard* self) { + clipboard_copy(self); - switch (self->type) - { - case CLIPBOARD_FRAME: - { - Anm2Reference* reference = std::get_if(&self->location); - if (!reference) break; - anm2_frame_remove(self->anm2, reference); - break; - } - case CLIPBOARD_ANIMATION: - { - s32* id = std::get_if(&self->location); - if (!id) break; - anm2_animation_remove(self->anm2, *id); - break; - } - default: - break; - } + switch (self->type) { + case CLIPBOARD_FRAME: { + Anm2Reference* reference = std::get_if(&self->location); + if (!reference) + break; + anm2_frame_remove(self->anm2, *reference); + break; + } + case CLIPBOARD_ANIMATION: { + int* id = std::get_if(&self->location); + if (!id) + break; + anm2_animation_remove(self->anm2, *id); + break; + } + default: + break; + } } -bool clipboard_paste(Clipboard* self) -{ - auto clipboard_string = [&]() - { - char* clipboardText = SDL_GetClipboardText(); - std::string clipboardString = std::string(clipboardText); - SDL_free(clipboardText); - return clipboardString; - }; +bool clipboard_paste(Clipboard* self) { + auto clipboard_string = [&]() { + char* clipboardText = SDL_GetClipboardText(); + std::string clipboardString = std::string(clipboardText); + SDL_free(clipboardText); + return clipboardString; + }; - switch (self->type) - { - case CLIPBOARD_FRAME: - { - Anm2Reference* reference = std::get_if(&self->location); - if (!reference) break; - Anm2Frame frame; - if (anm2_frame_deserialize_from_xml(&frame, clipboard_string())) - anm2_frame_add(self->anm2, &frame, reference); - else return false; - break; - } - case CLIPBOARD_ANIMATION: - { - s32* id = std::get_if(&self->location); - if (!id) break; - Anm2Animation animation; - if (anm2_animation_deserialize_from_xml(&animation, clipboard_string())) - anm2_animation_add(self->anm2, &animation, *id); - else return false; - break; - } - default: - break; - } + switch (self->type) { + case CLIPBOARD_FRAME: { + Anm2Reference* reference = std::get_if(&self->location); + if (!reference) + break; + Anm2Frame frame; + if (anm2_frame_deserialize_from_xml(&frame, clipboard_string())) + anm2_frame_add(self->anm2, &frame, *reference); + else + return false; + break; + } + case CLIPBOARD_ANIMATION: { + int* id = std::get_if(&self->location); + if (!id) + break; + Anm2Animation animation; + if (anm2_animation_deserialize_from_xml(&animation, clipboard_string())) + anm2_animation_add(self->anm2, &animation, *id); + else + return false; + break; + } + default: + break; + } - return true; + return true; } -void clipboard_init(Clipboard* self, Anm2* anm2) -{ - self->anm2 = anm2; -} +void clipboard_init(Clipboard* self, Anm2* anm2) { self->anm2 = anm2; } -bool clipboard_is_value(void) -{ - return SDL_HasClipboardText(); -} \ No newline at end of file +bool clipboard_is_value(void) { return SDL_HasClipboardText(); } \ No newline at end of file diff --git a/src/clipboard.h b/src/clipboard.h index d269ce7..98621e1 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -4,20 +4,14 @@ #define CLIPBOARD_TEXT_SET_WARNING "Unable to set clipboard text! ({})" -enum ClipboardType -{ - CLIPBOARD_NONE, - CLIPBOARD_FRAME, - CLIPBOARD_ANIMATION -}; +enum ClipboardType { CLIPBOARD_NONE, CLIPBOARD_FRAME, CLIPBOARD_ANIMATION }; -using ClipboardLocation = std::variant; +using ClipboardLocation = std::variant; -struct Clipboard -{ - Anm2* anm2 = nullptr; - ClipboardType type; - ClipboardLocation location; +struct Clipboard { + Anm2* anm2 = nullptr; + ClipboardType type; + ClipboardLocation location; }; bool clipboard_is_value(void); diff --git a/src/dialog.cpp b/src/dialog.cpp index f3a1449..1db4a23 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -1,91 +1,70 @@ #include "dialog.h" #ifdef _WIN32 - #include +#include #endif -static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter) -{ - Dialog* self; +static void _dialog_callback(void* userdata, const char* const* filelist, int filter) { + Dialog* self; - self = (Dialog*)userdata; + self = (Dialog*)userdata; - if (filelist && filelist[0] && strlen(filelist[0]) > 0) - { - self->path = filelist[0]; - self->isSelected = true; - self->selectedFilter = filter; - } - else - { - self->isSelected = false; - self->selectedFilter = INDEX_NONE; - } + if (filelist && filelist[0] && strlen(filelist[0]) > 0) { + self->path = filelist[0]; + self->isSelected = true; + self->selectedFilter = filter; + } else { + self->isSelected = false; + self->selectedFilter = INDEX_NONE; + } } -void dialog_init(Dialog* self, SDL_Window* window) -{ - self->window = window; +void dialog_init(Dialog* self, SDL_Window* window) { self->window = window; } + +void dialog_anm2_open(Dialog* self) { + SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr, false); + self->type = DIALOG_ANM2_OPEN; } -void dialog_anm2_open(Dialog* self) -{ - SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr, false); - self->type = DIALOG_ANM2_OPEN; +void dialog_anm2_save(Dialog* self) { + SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr); + self->type = DIALOG_ANM2_SAVE; } -void dialog_anm2_save(Dialog* self) -{ - SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr); - self->type = DIALOG_ANM2_SAVE; +void dialog_spritesheet_add(Dialog* self) { + SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false); + self->type = DIALOG_SPRITESHEET_ADD; } -void dialog_spritesheet_add(Dialog* self) -{ - SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false); - self->type = DIALOG_SPRITESHEET_ADD; +void dialog_spritesheet_replace(Dialog* self, int id) { + SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false); + self->replaceID = id; + self->type = DIALOG_SPRITESHEET_REPLACE; } -void dialog_spritesheet_replace(Dialog* self, s32 id) -{ - SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false); - self->replaceID = id; - self->type = DIALOG_SPRITESHEET_REPLACE; +void dialog_render_path_set(Dialog* self, RenderType type) { + SDL_DialogFileFilter filter = DIALOG_RENDER_FILE_FILTERS[type]; + + if (type == RENDER_PNG) + SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); + else + SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, &filter, 1, nullptr); + self->type = DIALOG_RENDER_PATH_SET; } -void dialog_render_path_set(Dialog* self, RenderType type) -{ - SDL_DialogFileFilter filter = DIALOG_RENDER_FILE_FILTERS[type]; - - if (type == RENDER_PNG) - SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); - else - SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, &filter, 1, nullptr); - self->type = DIALOG_RENDER_PATH_SET; +void dialog_ffmpeg_path_set(Dialog* self) { + SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, std::size(DIALOG_FILE_FILTER_FFMPEG), nullptr, false); + self->type = DIALOG_FFMPEG_PATH_SET; } -void dialog_ffmpeg_path_set(Dialog* self) -{ - SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, std::size(DIALOG_FILE_FILTER_FFMPEG), nullptr, false); - self->type = DIALOG_FFMPEG_PATH_SET; -} - -void dialog_explorer_open(const std::string& path) -{ +void dialog_explorer_open(const std::string& path) { #ifdef _WIN32 - ShellExecuteA(NULL, DIALOG_FILE_EXPLORER_COMMAND, path.c_str(), NULL, NULL, SW_SHOWNORMAL); -#else - char command[DIALOG_FILE_EXPLORER_COMMAND_SIZE]; - snprintf(command, sizeof(command), DIALOG_FILE_EXPLORER_COMMAND, path.c_str()); - system(command); + ShellExecuteA(NULL, DIALOG_FILE_EXPLORER_COMMAND, path.c_str(), NULL, NULL, SW_SHOWNORMAL); +#else + char command[DIALOG_FILE_EXPLORER_COMMAND_SIZE]; + snprintf(command, sizeof(command), DIALOG_FILE_EXPLORER_COMMAND, path.c_str()); + system(command); #endif } -void -dialog_reset(Dialog* self) -{ - self->replaceID = ID_NONE; - self->type = DIALOG_NONE; - self->path.clear(); - self->isSelected = false; -} \ No newline at end of file +void dialog_reset(Dialog* self) { *self = {self->window}; } \ No newline at end of file diff --git a/src/dialog.h b/src/dialog.h index f624e17..c965e0a 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -7,30 +7,17 @@ #ifdef _WIN32 #define DIALOG_FILE_EXPLORER_COMMAND "open" -#else +#else #define DIALOG_FILE_EXPLORER_COMMAND "xdg-open \"%s\" &" #endif -const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] = -{ - {"Anm2 file", "anm2;xml"} -}; +const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] = {{"Anm2 file", "anm2;xml"}}; -const SDL_DialogFileFilter DIALOG_FILE_FILTER_PNG[] = -{ - {"PNG image", "png"} -}; +const SDL_DialogFileFilter DIALOG_FILE_FILTER_PNG[] = {{"PNG image", "png"}}; -const SDL_DialogFileFilter DIALOG_RENDER_FILE_FILTERS[] = -{ - {"PNG image", "png"}, - {"GIF image", "gif"}, - {"WebM video", "webm"}, - {"MP4 video", "mp4"} -}; +const SDL_DialogFileFilter DIALOG_RENDER_FILE_FILTERS[] = {{"PNG image", "png"}, {"GIF image", "gif"}, {"WebM video", "webm"}, {"MP4 video", "mp4"}}; -const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] = -{ +const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] = { #ifdef _WIN32 {"Executable", "exe"} #else @@ -38,31 +25,29 @@ const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] = #endif }; -enum DialogType -{ - DIALOG_NONE, - DIALOG_ANM2_OPEN, - DIALOG_ANM2_SAVE, - DIALOG_SPRITESHEET_ADD, - DIALOG_SPRITESHEET_REPLACE, - DIALOG_RENDER_PATH_SET, - DIALOG_FFMPEG_PATH_SET +enum DialogType { + DIALOG_NONE, + DIALOG_ANM2_OPEN, + DIALOG_ANM2_SAVE, + DIALOG_SPRITESHEET_ADD, + DIALOG_SPRITESHEET_REPLACE, + DIALOG_RENDER_PATH_SET, + DIALOG_FFMPEG_PATH_SET }; -struct Dialog -{ - SDL_Window* window = nullptr; - s32 selectedFilter = ID_NONE; - std::string path{}; - s32 replaceID = ID_NONE; - DialogType type = DIALOG_NONE; - bool isSelected = false; +struct Dialog { + SDL_Window* window = nullptr; + std::string path{}; + int selectedFilter = ID_NONE; + int replaceID = ID_NONE; + DialogType type = DIALOG_NONE; + bool isSelected{}; }; void dialog_init(Dialog* self, SDL_Window* window); void dialog_anm2_open(Dialog* self); void dialog_spritesheet_add(Dialog* self); -void dialog_spritesheet_replace(Dialog* self, s32 id); +void dialog_spritesheet_replace(Dialog* self, int id); void dialog_anm2_save(Dialog* self); void dialog_render_path_set(Dialog* self, RenderType type); void dialog_render_directory_set(Dialog* self); diff --git a/src/editor.cpp b/src/editor.cpp index 02e2a13..7bd0d3a 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,61 +1,54 @@ #include "editor.h" -void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) -{ - self->anm2 = anm2; - self->reference = reference; - self->resources = resources; - self->settings = settings; +void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { + self->anm2 = anm2; + self->reference = reference; + self->resources = resources; + self->settings = settings; - canvas_init(&self->canvas, vec2()); + canvas_init(&self->canvas, vec2()); } -void editor_draw(Editor* self) -{ - ivec2& gridSize = self->settings->editorGridSize; - ivec2& gridOffset = self->settings->editorGridOffset; - vec4& gridColor = self->settings->editorGridColor; - GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; - GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; - mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT); - - canvas_framebuffer_resize_check(&self->canvas); - - canvas_bind(&self->canvas); - canvas_viewport_set(&self->canvas); - canvas_clear(self->settings->editorBackgroundColor); +void editor_draw(Editor* self) { + ivec2& gridSize = self->settings->editorGridSize; + ivec2& gridOffset = self->settings->editorGridOffset; + vec4& gridColor = self->settings->editorGridColor; + GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; + GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; + mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT); - if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->spritesheetID)) - { - Texture& texture = spritesheet->texture; - - mat4 spritesheetTransform = transform * quad_model_get(texture.size); - canvas_texture_draw(&self->canvas, shaderTexture, texture.id, spritesheetTransform); + canvas_framebuffer_resize_check(&self->canvas); - if (self->settings->editorIsBorder) - canvas_rect_draw(&self->canvas, shaderLine, spritesheetTransform, EDITOR_BORDER_COLOR); + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->editorBackgroundColor); - Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, self->reference); - - if (frame) - { - mat4 cropTransform = transform * quad_model_get(frame->size, frame->crop); - canvas_rect_draw(&self->canvas, shaderLine, cropTransform, EDITOR_FRAME_COLOR); + if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->spritesheetID)) { + Texture& texture = spritesheet->texture; - mat4 pivotTransform = transform * quad_model_get(CANVAS_PIVOT_SIZE, frame->crop + frame->pivot, CANVAS_PIVOT_SIZE * 0.5f); - f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, EDITOR_PIVOT_COLOR); - } + mat4 spritesheetTransform = transform * quad_model_get(texture.size); + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, spritesheetTransform); + + if (self->settings->editorIsBorder) + canvas_rect_draw(&self->canvas, shaderLine, spritesheetTransform, EDITOR_BORDER_COLOR); + + Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, *self->reference); + + if (frame) { + mat4 cropTransform = transform * quad_model_get(frame->size, frame->crop); + canvas_rect_draw(&self->canvas, shaderLine, cropTransform, EDITOR_FRAME_COLOR); + + mat4 pivotTransform = transform * quad_model_get(CANVAS_PIVOT_SIZE, frame->crop + frame->pivot, CANVAS_PIVOT_SIZE * 0.5f); + float vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, EDITOR_PIVOT_COLOR); } + } - if (self->settings->editorIsGrid) - canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor); + if (self->settings->editorIsGrid) + canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor); - canvas_unbind(); + canvas_unbind(); } -void editor_free(Editor* self) -{ - canvas_free(&self->canvas); -} \ No newline at end of file +void editor_free(Editor* self) { canvas_free(&self->canvas); } \ No newline at end of file diff --git a/src/editor.h b/src/editor.h index 8963e0b..c4a84e8 100644 --- a/src/editor.h +++ b/src/editor.h @@ -17,26 +17,25 @@ static const vec4 EDITOR_BORDER_COLOR = COLOR_OPAQUE; static const vec4 EDITOR_FRAME_COLOR = COLOR_RED; static const vec4 EDITOR_PIVOT_COLOR = COLOR_PINK; -struct Editor -{ - Anm2* anm2 = nullptr; - Anm2Reference* reference = nullptr; - Resources* resources = nullptr; - Settings* settings = nullptr; - Canvas canvas; - GLuint fbo; - GLuint rbo; - GLuint gridVAO; - GLuint gridVBO; - GLuint texture; - GLuint textureEBO; - GLuint textureVAO; - GLuint textureVBO; - GLuint borderVAO; - GLuint borderVBO; - s32 spritesheetID = ID_NONE; +struct Editor { + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; + Canvas canvas; + GLuint fbo; + GLuint rbo; + GLuint gridVAO; + GLuint gridVBO; + GLuint texture; + GLuint textureEBO; + GLuint textureVAO; + GLuint textureVBO; + GLuint borderVAO; + GLuint borderVBO; + int spritesheetID = ID_NONE; }; -void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); +void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); void editor_draw(Editor* self); void editor_free(Editor* self); \ No newline at end of file diff --git a/src/ffmpeg.cpp b/src/ffmpeg.cpp index 36edf68..b8ff2ee 100644 --- a/src/ffmpeg.cpp +++ b/src/ffmpeg.cpp @@ -1,69 +1,56 @@ #include "ffmpeg.h" -bool -ffmpeg_render -( - const std::string& ffmpegPath, - const std::string& outputPath, - const std::vector& frames, - ivec2 size, - s32 fps, - enum RenderType type -) -{ - if (frames.empty() || size.x <= 0 || size.y <= 0 || fps <= 0 || ffmpegPath.empty() || outputPath.empty()) return false; - - std::string command{}; +bool ffmpeg_render(const std::string& ffmpegPath, const std::string& outputPath, const std::vector& frames, ivec2 size, int fps, + enum RenderType type) { + if (frames.empty() || size.x <= 0 || size.y <= 0 || fps <= 0 || ffmpegPath.empty() || outputPath.empty()) + return false; - switch (type) - { - case RENDER_GIF: - command = std::format(FFMPEG_GIF_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); - break; - case RENDER_WEBM: - command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); - break; - case RENDER_MP4: - command = std::format(FFMPEG_MP4_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); - break; - default: - break; - } + std::string command{}; + + switch (type) { + case RENDER_GIF: + command = std::format(FFMPEG_GIF_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); + break; + case RENDER_WEBM: + command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); + break; + case RENDER_MP4: + command = std::format(FFMPEG_MP4_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); + break; + default: + break; + } #if _WIN32 - command = string_quote(command); + command = string_quote(command); #endif - log_command(command); + log_command(command); - FILE* fp = POPEN(command.c_str(), PWRITE_MODE); + FILE* fp = POPEN(command.c_str(), PWRITE_MODE); - if (!fp) - { - log_error(std::format(FFMPEG_POPEN_ERROR, strerror(errno))); - return false; - } - - size_t frameBytes = size.x * size.y * TEXTURE_CHANNELS; + if (!fp) { + log_error(std::format(FFMPEG_POPEN_ERROR, strerror(errno))); + return false; + } - for (const auto& frame : frames) - { - std::vector rgba = texture_download(&frame); + size_t frameBytes = size.x * size.y * TEXTURE_CHANNELS; - if (rgba.size() != frameBytes) - { - PCLOSE(fp); - return false; - } + for (const auto& frame : frames) { + std::vector rgba = texture_download(&frame); - if (fwrite(rgba.data(), 1, frameBytes, fp) != frameBytes) - { - PCLOSE(fp); - return false; - } + if (rgba.size() != frameBytes) { + PCLOSE(fp); + return false; } - const int code = PCLOSE(fp); + if (fwrite(rgba.data(), 1, frameBytes, fp) != frameBytes) { + PCLOSE(fp); + return false; + } + } - return (code == 0); + const int code = PCLOSE(fp); + + return (code == 0); } \ No newline at end of file diff --git a/src/ffmpeg.h b/src/ffmpeg.h index 2747a57..099978e 100644 --- a/src/ffmpeg.h +++ b/src/ffmpeg.h @@ -5,34 +5,22 @@ #define FFMPEG_POPEN_ERROR "popen() (for FFmpeg) failed!\n{}" -static constexpr const char* FFMPEG_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}\""; +static constexpr const char* FFMPEG_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}\""; -static constexpr const char* FFMPEG_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}\""; +static constexpr const char* FFMPEG_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}\""; -static constexpr const char* FFMPEG_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}\""; +static constexpr const char* FFMPEG_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 -ffmpeg_render -( - const std::string& ffmpegPath, - const std::string& outputPath, - const std::vector& frames, - ivec2 size, - s32 fps, - enum RenderType type -); \ No newline at end of file +bool ffmpeg_render(const std::string& ffmpegPath, const std::string& outputPath, const std::vector& frames, ivec2 size, int fps, enum RenderType type); \ No newline at end of file diff --git a/src/generate_preview.cpp b/src/generate_preview.cpp index 7faa101..0d2ffe7 100644 --- a/src/generate_preview.cpp +++ b/src/generate_preview.cpp @@ -1,57 +1,50 @@ #include "generate_preview.h" -void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) -{ - self->anm2 = anm2; - self->reference = reference; - self->resources = resources; - self->settings = settings; +void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { + self->anm2 = anm2; + self->reference = reference; + self->resources = resources; + self->settings = settings; - canvas_init(&self->canvas, GENERATE_PREVIEW_SIZE); + canvas_init(&self->canvas, GENERATE_PREVIEW_SIZE); } -void generate_preview_draw(GeneratePreview* self) -{ - static auto& columns = self->settings->generateColumns; - static auto& count = self->settings->generateCount; - static GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - const mat4 transform = canvas_transform_get(&self->canvas, {}, CANVAS_ZOOM_DEFAULT, ORIGIN_CENTER); - - vec2 startPosition = {self->settings->generateStartPosition.x, self->settings->generateStartPosition.y}; - vec2 size = {self->settings->generateSize.x, self->settings->generateSize.y}; - vec2 pivot = {self->settings->generatePivot.x, self->settings->generatePivot.y}; +void generate_preview_draw(GeneratePreview* self) { + static auto& columns = self->settings->generateColumns; + static auto& count = self->settings->generateCount; + static GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + const mat4 transform = canvas_transform_get(&self->canvas, {}, CANVAS_ZOOM_DEFAULT, ORIGIN_CENTER); - canvas_bind(&self->canvas); - canvas_viewport_set(&self->canvas); - canvas_clear(self->settings->previewBackgroundColor); - - Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); + vec2 startPosition = {self->settings->generateStartPosition.x, self->settings->generateStartPosition.y}; + vec2 size = {self->settings->generateSize.x, self->settings->generateSize.y}; + vec2 pivot = {self->settings->generatePivot.x, self->settings->generatePivot.y}; - if (item) - { - if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[self->reference->itemID].spritesheetID)) - { - Texture& texture = spritesheet->texture; + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->previewBackgroundColor); - const s32 index = std::clamp((s32)(self->time * count), 0, count); - const s32 row = index / columns; - const s32 column = index % columns; - vec2 crop = startPosition + vec2(size.x * column, size.y * row); + Anm2Item* item = anm2_item_from_reference(self->anm2, *self->reference); - vec2 textureSize = vec2(texture.size); - vec2 uvMin = (crop + vec2(0.5f)) / textureSize; - vec2 uvMax = (crop + size - vec2(0.5f)) / textureSize; - f32 vertices[] = UV_VERTICES(uvMin, uvMax); + if (item) { + if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[self->reference->itemID].spritesheetID)) { + Texture& texture = spritesheet->texture; - mat4 generateTransform = transform * quad_model_get(size, {}, pivot); - canvas_texture_draw(&self->canvas, shaderTexture, texture.id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE); - } + const int index = std::clamp((int)(self->time * count), 0, count); + const int row = index / columns; + const int column = index % columns; + vec2 crop = startPosition + vec2(size.x * column, size.y * row); + + vec2 textureSize = vec2(texture.size); + vec2 uvMin = (crop + vec2(0.5f)) / textureSize; + vec2 uvMax = (crop + size - vec2(0.5f)) / textureSize; + float vertices[] = UV_VERTICES(uvMin, uvMax); + + mat4 generateTransform = transform * quad_model_get(size, {}, pivot); + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE); } - - canvas_unbind(); + } + + canvas_unbind(); } -void generate_preview_free(GeneratePreview* self) -{ - canvas_free(&self->canvas); -} \ No newline at end of file +void generate_preview_free(GeneratePreview* self) { canvas_free(&self->canvas); } \ No newline at end of file diff --git a/src/generate_preview.h b/src/generate_preview.h index fa1b576..6539991 100644 --- a/src/generate_preview.h +++ b/src/generate_preview.h @@ -1,23 +1,22 @@ #pragma once #include "anm2.h" +#include "canvas.h" #include "resources.h" #include "settings.h" -#include "canvas.h" #define GENERATE_PREVIEW_TIME_MIN 0.0f #define GENERATE_PREVIEW_TIME_MAX 1.0f const vec2 GENERATE_PREVIEW_SIZE = {325, 215}; -struct GeneratePreview -{ - Anm2* anm2 = nullptr; - Anm2Reference* reference = nullptr; - Resources* resources = nullptr; - Settings* settings = nullptr; - Canvas canvas; - f32 time{}; +struct GeneratePreview { + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; + Canvas canvas; + float time{}; }; void generate_preview_init(GeneratePreview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); diff --git a/src/imgui.cpp b/src/imgui.cpp index c6203fa..c8b420f 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,3246 +1,3059 @@ #include "imgui.h" -static bool _imgui_chord_pressed(ImGuiKeyChord chord) -{ - if (chord == IMGUI_CHORD_NONE) return false; - ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); - if (key == ImGuiKey_None) return false; - if (key < ImGuiKey_NamedKey_BEGIN || key >= ImGuiKey_NamedKey_END) return false; +static void _imgui_anm2_open(Imgui* self, const std::string& path) { + imgui_anm2_new(self); - return ImGui::IsKeyChordPressed(chord); + if (anm2_deserialize(self->anm2, path)) { + window_title_from_path_set(self->window, path); + imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path)); + } else + imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path)); } -static const char* _imgui_f32_format_get(const ImguiItem& item, f32& value) -{ - if (item.isEmptyFormat) return ""; - return f32_format_get(value); +static void _imgui_spritesheet_add(Imgui* self, const std::string& path) { + imgui_snapshot(self, IMGUI_ACTION_ADD_SPRITESHEET); + WorkingDirectory workingDirectory(self->anm2->path); + std::string spritesheetPath = path; + std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path); + spritesheetPath = std::filesystem::relative(path, anm2WorkingPath).string(); + Texture texture; + if (texture_from_path_init(&texture, spritesheetPath)) { + int id = map_next_id_get(self->anm2->spritesheets); + self->anm2->spritesheets[id] = Anm2Spritesheet{}; + self->anm2->spritesheets[id].path = spritesheetPath; + self->anm2->spritesheets[id].texture = texture; + } else + imgui_log_push(self, std::format(IMGUI_LOG_ADD_SPRITESHEET_ERROR, path)); } -static const char* _imgui_vec2_format_get(const ImguiItem& item, vec2& value) -{ - if (item.isEmptyFormat) return ""; - return vec2_format_get(value); +static bool _imgui_is_window_hovered(void) { return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } +static bool _imgui_is_window_hovered_and_click(void) { return _imgui_is_window_hovered() && ImGui::IsMouseClicked(0); } +static bool _imgui_is_no_click_on_item(void) { return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered(); } + +static bool _imgui_is_input_begin(void) { + return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_RENAME) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)); } -static bool _imgui_window_color_from_position_get(SDL_Window* self, const vec2& position, vec4& color) -{ - ImGuiIO& io = ImGui::GetIO(); - ivec2 fbPosition = {(s32)(position.x * io.DisplayFramebufferScale.x), (s32)(position.y * io.DisplayFramebufferScale.y)}; - ivec2 size{}; - SDL_GetWindowSizeInPixels(self, &size.x, &size.y); - - if (fbPosition.x < 0 || fbPosition.y < 0 || fbPosition.x >= size.x || fbPosition.y >= size.y) return false; - - uint8_t rgba[4]; - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(fbPosition.x, size.y - 1 - fbPosition.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); - - color = vec4(U8_TO_FLOAT(rgba[0]), U8_TO_FLOAT(rgba[1]), U8_TO_FLOAT(rgba[2]), U8_TO_FLOAT(rgba[3])); - - return true; +static bool _imgui_is_input_default(void) { + return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_DEFAULT) || ImGui::IsMouseClicked(IMGUI_MOUSE_DEFAULT)); } -static void _imgui_anm2_open(Imgui* self, const std::string& path) -{ - imgui_anm2_new(self); +static bool _imgui_chord_pressed(ImGuiKeyChord chord) { + if (chord == IMGUI_CHORD_NONE) + return false; + ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); + if (key == ImGuiKey_None) + return false; + if (key < ImGuiKey_NamedKey_BEGIN || key >= ImGuiKey_NamedKey_END) + return false; - if (anm2_deserialize(self->anm2, path)) - { - window_title_from_path_set(self->window, path); - imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path)); - } - else - imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path)); + return ImGui::IsKeyChordPressed(chord); } -static void _imgui_spritesheet_add(Imgui* self, const std::string& path) -{ - if (self->anm2->path.empty()) - { - imgui_log_push(self, IMGUI_LOG_NO_ANM2_PATH); - return; - } +static bool _imgui_is_focus_window(const std::string& focus) { + ImGuiContext* context = ImGui::GetCurrentContext(); + if (!context || !context->NavWindow) + return false; - imgui_snapshot(self, IMGUI_ACTION_ADD_SPRITESHEET); - - std::filesystem::path workingPath = std::filesystem::current_path(); - std::string spritesheetPath = path; - std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path); - spritesheetPath = std::filesystem::relative(path, anm2WorkingPath).string(); - s32 id = map_next_id_get(self->anm2->spritesheets); - self->anm2->spritesheets[id] = Anm2Spritesheet{}; - self->anm2->spritesheets[id].path = spritesheetPath; - texture_from_path_init(&self->anm2->spritesheets[id].texture, spritesheetPath); - - std::filesystem::current_path(workingPath); + std::string_view name(context->NavWindow->Name); + return name.find(focus) != std::string_view::npos; } -static bool _imgui_is_window_hovered(void) -{ - return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); +static vec2 _imgui_item_spacing_get(const ImguiItem& self) { return self.itemSpacing.has_value() ? *self.itemSpacing : vec2(ImGui::GetStyle().ItemSpacing); } +static vec2 _imgui_window_padding_get(const ImguiItem& self) { + return self.windowPadding.has_value() ? *self.windowPadding : vec2(ImGui::GetStyle().WindowPadding); +} +static vec2 _imgui_cursor_position_get(const ImguiItem& self) { return self.cursorPosition.has_value() ? *self.cursorPosition : vec2(ImGui::GetCursorPos()); } + +static vec2 _imgui_item_size_get(const ImguiItem& self, ImguiItemType type) { + vec2 size{}; + + if (!self.size.has_value()) { + switch (type) { + case IMGUI_ATLAS_BUTTON: + size = self.size.has_value() ? *self.size : vec2(ATLAS_SIZE(self.atlas)); + break; + default: + if (self.rowCount.has_value()) + size.x = (ImGui::GetWindowSize().x - (_imgui_item_spacing_get(self).x * (*self.rowCount + 1))) / *self.rowCount; + else if (self.isSizeToText) + size.x = (ImGui::CalcTextSize(self.label_get().c_str()).x + ImGui::GetStyle().FramePadding.x); + else if (self.isSizeToRegion) + size.x = ImGui::GetContentRegionAvail().x; + else + size.x = ImGui::CalcItemWidth(); + + if (type != IMGUI_BUTTON) + size.y = ImGui::GetTextLineHeight() + IMGUI_TEXT_HEIGHT_PADDING; + break; + } + } else + size = *self.size; + + return size; } -static bool _imgui_is_window_hovered_and_click(void) -{ - return _imgui_is_window_hovered() && ImGui::IsMouseClicked(0); +static void _imgui_item_style_push(const ImguiItem& self) { + if (self.windowPadding.has_value()) + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, *self.windowPadding); + if (self.itemSpacing.has_value()) + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, *self.itemSpacing); } -static bool _imgui_is_window_hovered_and_click_no_anm2_path(Imgui* self) -{ - return _imgui_is_window_hovered_and_click() && self->anm2->path.empty(); +static void _imgui_item_style_pop(const ImguiItem& self) { + if (self.windowPadding.has_value()) + ImGui::PopStyleVar(); + if (self.itemSpacing.has_value()) + ImGui::PopStyleVar(); } -static bool _imgui_is_no_click_on_item(void) -{ - return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered(); +static const char* _imgui_float_format_get(const ImguiItem& item, float& value) { + if (item.isEmptyFormat) + return ""; + return float_format_get(value); } -static bool _imgui_is_input_begin(void) -{ - return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_RENAME) || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)); +static const char* _imgui_vec2_format_get(const ImguiItem& item, vec2& value) { + if (item.isEmptyFormat) + return ""; + return vec2_format_get(value); } -static bool _imgui_is_input_default(void) -{ - return ImGui::IsItemHovered() && (ImGui::IsKeyPressed(IMGUI_INPUT_DEFAULT) || ImGui::IsMouseClicked(IMGUI_MOUSE_DEFAULT)); +static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) { + switch (type) { + case IMGUI_ITEM: + case IMGUI_TEXT: + case IMGUI_WINDOW: + case IMGUI_DOCKSPACE: + case IMGUI_BEGIN_CHILD: + case IMGUI_END_CHILD: + case IMGUI_CONFIRM_POPUP: + case IMGUI_TABLE: + break; + default: + ImGui::BeginDisabled(self.isDisabled); + break; + } + + switch (type) { + case IMGUI_SELECTABLE: + case IMGUI_INPUT_INT: + case IMGUI_INPUT_TEXT: + case IMGUI_INPUT_FLOAT: + case IMGUI_DRAG_FLOAT: + case IMGUI_SLIDER_FLOAT: + case IMGUI_COLOR_EDIT: + ImGui::SetNextItemWidth(_imgui_item_size_get(self, type).x); + break; + default: + break; + } + + switch (type) { + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.border.has_value()) { + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, *self.border); + if (self.color.border.has_value()) + ImGui::PushStyleColor(ImGuiCol_Border, *self.color.border); + } + break; + default: + break; + } + + switch (type) { + case IMGUI_BEGIN_CHILD: + if (self.color.normal.has_value()) + ImGui::PushStyleColor(ImGuiCol_ChildBg, *self.color.normal); + break; + case IMGUI_SELECTABLE: + if (self.isSelected && self.color.normal.has_value()) + ImGui::PushStyleColor(ImGuiCol_Header, *self.color.normal); + break; + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.color.normal.has_value()) + ImGui::PushStyleColor(ImGuiCol_Button, *self.color.normal); + if (self.color.active.has_value()) + ImGui::PushStyleColor(ImGuiCol_ButtonActive, *self.color.active); + if (self.color.hovered.has_value()) + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, *self.color.hovered); + if (self.isSelected) { + if (self.color.active.has_value()) + ImGui::PushStyleColor(ImGuiCol_Button, *self.color.active); + else + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + } + break; + default: + break; + } + + switch (type) { + case IMGUI_END_CHILD: + break; + default: + _imgui_item_style_push(self); + break; + } + + switch (type) { + case IMGUI_WINDOW: + break; + default: + ImGui::SetCursorPos(self.cursorPosition.value_or(vec2(ImGui::GetCursorPos())) + self.cursorOffset); + break; + } } -static bool _imgui_is_focus_window(const std::string& focus) -{ - ImGuiContext* ctx = ImGui::GetCurrentContext(); - if (!ctx || !ctx->NavWindow) return false; +static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType type, bool& isActivated) { + if (self.is_mnemonic() && !self.isMnemonicDisabled) { + ImVec2 position = ImGui::GetItemRectMin(); + ImFont* font = ImGui::GetFont(); + float fontSize = ImGui::GetFontSize(); + const char* start = self.label.c_str(); + const char* charPointer = start + self.mnemonicIndex; - std::string_view name(ctx->NavWindow->Name); - return name.find(focus) != std::string_view::npos; + position.x += ImGui::GetStyle().FramePadding.x; + + float offset = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, start, charPointer).x; + float charWidth = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, charPointer, charPointer + 1).x; + + ImVec2 lineStart = ImVec2(position.x + offset, position.y + fontSize + 1.0f); + ImVec2 lineEnd = ImVec2(lineStart.x + charWidth, lineStart.y); + + ImU32 color = ImGui::GetColorU32(ImGuiCol_Text); + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, color, 1.0f); + + if (_imgui_chord_pressed(ImGuiMod_Alt | self.mnemonicKey)) { + if (!self.isDisabled) + isActivated = true; + imgui_close_current_popup(imgui); + } + } + + if (self.isUseItemActivated && !self.isDisabled) + isActivated = ImGui::IsItemActivated(); + + if (imgui->isContextualActionsEnabled && _imgui_chord_pressed(self.chord_get()) && + (self.focusWindow.has_value() && _imgui_is_focus_window(*self.focusWindow))) + if (!self.isDisabled) + isActivated = true; + + if (!self.tooltip.empty() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, imgui->style.WindowPadding); + ImGui::SetTooltip("%s", self.tooltip_get().c_str()); + ImGui::PopStyleVar(); + } + + if (isActivated) { + if (self.snapshotAction.has_value()) + imgui_snapshot(imgui, *self.snapshotAction); + if (self.function) + self.function(imgui); + + if (!self.popup.empty()) { + imgui->pendingPopup = self.popup; + imgui->pendingPopupType = self.popupType; + imgui->pendingPopupPosition = ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y); + } + } + + switch (type) { + case IMGUI_END_CHILD: + if (self.color.normal.has_value()) + ImGui::PopStyleColor(); + break; + case IMGUI_SELECTABLE: + if (self.isSelected && self.color.normal.has_value()) + ImGui::PopStyleColor(); + break; + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.color.normal.has_value()) + ImGui::PopStyleColor(); + if (self.color.active.has_value()) + ImGui::PopStyleColor(); + if (self.color.hovered.has_value()) + ImGui::PopStyleColor(); + if (self.isSelected) + ImGui::PopStyleColor(); + break; + default: + break; + } + + switch (type) { + case IMGUI_BUTTON: + case IMGUI_ATLAS_BUTTON: + if (self.border.has_value()) { + ImGui::PopStyleVar(); + if (self.color.border.has_value()) + ImGui::PopStyleColor(); + } + default: + break; + } + + switch (type) { + case IMGUI_ITEM: + case IMGUI_TEXT: + case IMGUI_WINDOW: + case IMGUI_DOCKSPACE: + case IMGUI_BEGIN_CHILD: + case IMGUI_END_CHILD: + case IMGUI_CONFIRM_POPUP: + case IMGUI_TABLE: + break; + default: + ImGui::EndDisabled(); + } + + if (self.isSameLine) + ImGui::SameLine(); + if (self.isSeparator) + ImGui::Separator(); + + switch (type) { + case IMGUI_BEGIN_CHILD: + break; + default: + _imgui_item_style_pop(self); + break; + } } -static void _imgui_atlas(const AtlasType& self, Imgui* imgui) -{ - ImGui::Image(imgui->resources->atlas.id, ATLAS_SIZE(self), ATLAS_UV_ARGS(self)); -} +#define IMGUI_ITEM_BASE(NAME, TYPE, BODY) \ + static bool NAME(const ImguiItem& self, Imgui* imgui) { \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = false; \ + do { \ + BODY \ + } while (0); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ + } -static ImVec2 _imgui_item_size_get(const ImguiItem& self, ImguiItemType type) -{ - ImVec2 size = self.size; +#define IMGUI_ITEM_VALUE_BASE(NAME, TYPE, VALUE_TYPE, BODY) \ + static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE_TYPE& inValue) { \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = false; \ + do { \ + VALUE_TYPE value = inValue; \ + BODY if (_imgui_is_input_default()) { \ + value = VALUE_TYPE(self.value); \ + isActivated = true; \ + } \ + inValue = value; \ + } while (0); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ + } - switch (type) - { - case IMGUI_ATLAS_BUTTON: - size = self.is_size() ? self.size : ImVec2(ATLAS_SIZE(self.atlas)); - break; - default: - if (self.is_row()) - size.x = (ImGui::GetWindowSize().x - (ImGui::GetStyle().ItemSpacing.x * (self.rowCount + 1))) / self.rowCount; - else if (self.isSizeToText) - size.x = (ImGui::CalcTextSize(self.label_get().c_str()).x + ImGui::GetStyle().FramePadding.x); - else if (!self.is_size()) - size.x = ImGui::CalcItemWidth(); - break; - } +#define IMGUI_ITEM_VOID_FUNCTION(NAME, TYPE, BODY) IMGUI_ITEM_BASE(NAME, TYPE, { ([&] { BODY; })(); }) +#define IMGUI_ITEM_BOOL_FUNCTION(NAME, TYPE, BODY) IMGUI_ITEM_BASE(NAME, TYPE, { isActivated = ([&] { return BODY; })(); }) +#define IMGUI_ITEM_VALUE_FUNCTION(NAME, TYPE, VALUE_TYPE, BODY) IMGUI_ITEM_VALUE_BASE(NAME, TYPE, VALUE_TYPE, { isActivated = ([&] { return BODY; })(); }) - return size; -} +#define IMGUI_ITEM_VALUE_CLAMP_FUNCTION(NAME, TYPE, VALUE_TYPE, BODY) \ + IMGUI_ITEM_VALUE_BASE(NAME, TYPE, VALUE_TYPE, { \ + isActivated = ([&] { return BODY; })(); \ + if (self.is_range()) \ + value = glm::clamp(value, VALUE_TYPE(*self.min), VALUE_TYPE(*self.max)); \ + }) -static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) -{ - ImVec2 size = _imgui_item_size_get(self, type); +#define IMGUI_ITEM_STRING_FUNCTION(NAME, TYPE, BODY) \ + static bool NAME(const ImguiItem& self, Imgui* imgui, std::string& inValue) { \ + ImguiItemType type = TYPE; \ + _imgui_item_pre(self, type); \ + bool isActivated = false; \ + do { \ + std::string value = inValue; \ + isActivated = ([&] { return BODY; })(); \ + /* no VALUE_TYPE(self.value) here, strings don’t come from self.value */ \ + inValue = value; \ + } while (0); \ + _imgui_item_post(self, imgui, type, isActivated); \ + return isActivated; \ + } - switch (type) - { - case IMGUI_ITEM: - case IMGUI_TEXT: - case IMGUI_WINDOW: - case IMGUI_DOCKSPACE: - case IMGUI_CHILD: - case IMGUI_CONFIRM_POPUP: - break; - default: - ImGui::BeginDisabled(self.isDisabled); - } - - switch (type) - { - case IMGUI_CHILD: - if (self.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_ChildBg, self.color.normal); - break; - case IMGUI_INPUT_INT: - case IMGUI_INPUT_TEXT: - case IMGUI_INPUT_FLOAT: - case IMGUI_DRAG_FLOAT: - case IMGUI_SLIDER_FLOAT: - case IMGUI_COLOR_EDIT: - ImGui::SetNextItemWidth(size.x); - break; - case IMGUI_BUTTON: - case IMGUI_ATLAS_BUTTON: - if (self.is_border()) - { - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, self.border); - if (self.color.is_border()) ImGui::PushStyleColor(ImGuiCol_Border, self.color.border); - } - if (self.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_Button, self.color.normal); - if (self.color.is_active()) ImGui::PushStyleColor(ImGuiCol_ButtonActive, self.color.active); - if (self.color.is_hovered()) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, self.color.hovered); - if (self.isSelected) - { - if (self.color.is_active()) - ImGui::PushStyleColor(ImGuiCol_Button, self.color.active); - else - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); - } - break; - default: - break; - } -} +IMGUI_ITEM_VOID_FUNCTION(_imgui_dockspace, IMGUI_DOCKSPACE, ImGui::DockSpace(ImGui::GetID(self.label_get().c_str()), *self.size, self.flags)); +IMGUI_ITEM_VOID_FUNCTION(_imgui_begin_child, IMGUI_BEGIN_CHILD, ImGui::BeginChild(self.label_get().c_str(), *self.size, self.flags, self.windowFlags)); +IMGUI_ITEM_VOID_FUNCTION(_imgui_end_child, IMGUI_END_CHILD, ImGui::EndChild()); +IMGUI_ITEM_VOID_FUNCTION(_imgui_text, IMGUI_TEXT, ImGui::Text("%s", self.label_get().c_str())); +IMGUI_ITEM_BOOL_FUNCTION(_imgui_button, IMGUI_BUTTON, ImGui::Button(self.label_get().c_str(), _imgui_item_size_get(self, type))); +IMGUI_ITEM_BOOL_FUNCTION(_imgui_begin_table, IMGUI_TABLE, ImGui::BeginTable(self.label_get().c_str(), self.value, self.flags)); +IMGUI_ITEM_BOOL_FUNCTION(_imgui_selectable, IMGUI_SELECTABLE, + ImGui::Selectable(self.label_get().c_str(), self.isSelected, self.flags, _imgui_item_size_get(self, type))); +IMGUI_ITEM_BOOL_FUNCTION(_imgui_begin, IMGUI_WINDOW, ImGui::Begin(self.label_get().c_str(), nullptr, self.flags)); +IMGUI_ITEM_VOID_FUNCTION(_imgui_image, IMGUI_IMAGE, ImGui::Image(self.textureID, _imgui_item_size_get(self, IMGUI_IMAGE), self.uvMin, self.uvMax)); -static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType type, bool& isActivated) -{ - if (self.is_mnemonic() && !self.isMnemonicDisabled) - { - ImVec2 position = ImGui::GetItemRectMin(); - ImFont* font = ImGui::GetFont(); - f32 fontSize = ImGui::GetFontSize(); - const char* start = self.label.c_str(); - const char* charPointer = start + self.mnemonicIndex; - - position.x += ImGui::GetStyle().FramePadding.x; - - f32 offset = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, start, charPointer).x; - f32 charWidth = font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, charPointer, charPointer + 1).x; - - ImVec2 lineStart = ImVec2(position.x + offset, position.y + fontSize + 1.0f); - ImVec2 lineEnd = ImVec2(lineStart.x + charWidth, lineStart.y); - - ImU32 color = ImGui::GetColorU32(ImGuiCol_Text); - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, color, 1.0f); - - if (_imgui_chord_pressed(ImGuiMod_Alt | self.mnemonicKey)) - { - if (!self.isDisabled) isActivated = true; - imgui_close_current_popup(imgui); - } - } - - if (self.isUseItemActivated && !self.isDisabled) isActivated = ImGui::IsItemActivated(); - - if - ( - imgui->isContextualActionsEnabled && _imgui_chord_pressed(self.chord_get()) && - (self.is_focus_window() && _imgui_is_focus_window(self.focusWindow)) - ) - if (!self.isDisabled) isActivated = true; - - if (self.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) - ImGui::SetTooltip(self.tooltip_get().c_str()); - - if (isActivated) - { - if (self.is_undoable()) imgui_snapshot(imgui, self.snapshotAction); - if (self.is_function()) self.function(imgui); - - if (self.is_popup()) - { - imgui->pendingPopup = self.popup; - imgui->pendingPopupType = self.popupType; - imgui->pendingPopupPosition = ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y); - } - } - - switch (type) - { - case IMGUI_CHILD: - if (self.color.is_normal()) ImGui::PopStyleColor(); - break; - case IMGUI_BUTTON: - case IMGUI_ATLAS_BUTTON: - if (self.is_border()) - { - ImGui::PopStyleVar(); - if (self.color.is_border()) ImGui::PopStyleColor(); - } - if (self.color.is_normal()) ImGui::PopStyleColor(); - if (self.color.is_active()) ImGui::PopStyleColor(); - if (self.color.is_hovered()) ImGui::PopStyleColor(); - if (self.isSelected) ImGui::PopStyleColor(); - break; - default: - break; - } - - switch (type) - { - case IMGUI_ITEM: - case IMGUI_TEXT: - case IMGUI_WINDOW: - case IMGUI_DOCKSPACE: - case IMGUI_CHILD: - case IMGUI_CONFIRM_POPUP: - break; - default: - ImGui::EndDisabled(); - } - - if (self.isSameLine) ImGui::SameLine(); - if (self.isSeparator) ImGui::Separator(); -} - -#define IMGUI_ITEM_FUNCTION(NAME, TYPE, FUNCTION) \ -static bool NAME(ImguiItem self, Imgui* imgui) \ -{ \ - ImguiItemType type = TYPE; \ - _imgui_item_pre(self, type); \ - bool isActivated = ([&] { return FUNCTION; })(); \ - _imgui_item_post(self, imgui, type, isActivated); \ - return isActivated; \ -} - -#define IMGUI_ITEM_VOID_FUNCTION(NAME, TYPE, FUNCTION) \ -static void NAME(const ImguiItem& self, Imgui* imgui) \ -{ \ - ImguiItemType type = TYPE; \ - bool isActivated = false; \ - _imgui_item_pre(self, type); \ - ([&] { FUNCTION; })(); \ - _imgui_item_post(self, imgui, type, isActivated); \ -} - -#define IMGUI_ITEM_VALUE_FUNCTION(NAME, TYPE, VALUE, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ -{ \ - ImguiItemType type = TYPE; \ - VALUE value = inValue; \ - _imgui_item_pre(self, type); \ - bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ - if (_imgui_is_input_default()) \ - { \ - value = VALUE(self.value); \ - isActivated = true; \ - } \ - _imgui_item_post(self, imgui, type, isActivated); \ - inValue = value; \ - return isActivated; \ -} - -#define IMGUI_ITEM_VALUE_CLAMP_FUNCTION(NAME, TYPE, VALUE, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ -{ \ - ImguiItemType type = TYPE; \ - VALUE value = inValue; \ - _imgui_item_pre(self, type); \ - bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ - if (_imgui_is_input_default()) \ - { \ - value = VALUE(self.value); \ - isActivated = true; \ - } \ - if (self.is_range()) value = glm::clamp(value, VALUE(self.min), VALUE(self.max)); \ - _imgui_item_post(self, imgui, type, isActivated); \ - inValue = value; \ - return isActivated; \ -} - -#define IMGUI_ITEM_CUSTOM_FUNCTION(NAME, TYPE, BODY) \ -static bool NAME(const ImguiItem& self, Imgui* imgui) \ -{ \ - ImguiItemType type = TYPE; \ - _imgui_item_pre(self, type); \ - bool isActivated = false; \ - do { BODY } while (0); \ - _imgui_item_post(self, imgui, type, isActivated); \ - return isActivated; \ -} - -#define IMGUI_ITEM_CUSTOM_FUNCTION_WITH_VALUE(NAME, TYPE, VALUE, BODY) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& inValue) \ -{ \ - ImguiItemType type = TYPE; \ - _imgui_item_pre(self, type); \ - bool isActivated = false; \ - do { BODY } while (0); \ - _imgui_item_post(self, imgui, type, isActivated); \ - return isActivated; \ -} - -#define IMGUI_ITEM_ATLAS_FUNCTION(NAME, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui) \ -{ \ - _imgui_atlas(self.atlas, imgui); \ - ImGui::SameLine(); \ - bool isActivated = ([&] { return FUNCTION; })(); \ - return isActivated; \ -} - -#define IMGUI_ITEM_ATLAS_VALUE_FUNCTION(NAME, VALUE, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value) \ -{ \ - _imgui_atlas(self.atlas, imgui); \ - ImGui::SameLine(); \ - bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ - return isActivated; \ -} - -#define IMGUI_ITEM_CHECKBOX_FUNCTION(NAME, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, bool& boolValue) \ -{ \ - ImguiItem checkboxItem = self.copy \ - ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ - checkboxItem.isDisabled = false; \ - checkboxItem.isSeparator = false; \ - checkboxItem.value = 0; \ - bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ - ImGui::SameLine(); \ - bool isActivated = ([&] { return FUNCTION; })(); \ - if (isActivated) boolValue = !boolValue; \ - if (isCheckboxActivated) isActivated = true; \ - return isActivated; \ -} - -#define IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(NAME, VALUE, FUNCTION) \ -static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value, bool& boolValue) \ -{ \ - ImguiItem checkboxItem = self.copy \ - ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ - checkboxItem.isDisabled = false; \ - checkboxItem.isSeparator = false; \ - checkboxItem.value = 0; \ - bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ - ImGui::SameLine(); \ - bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ - if (isCheckboxActivated) isActivated = true; \ - return isActivated; \ -} - -#define IMGUI_ITEM_AND_CHECKBOX_FUNCTION(NAME, VALUE, FUNCTION) \ -static bool NAME(const ImguiItem& self, const ImguiItem& checkboxItem, Imgui* imgui, VALUE& value, bool& boolValue) \ -{ \ - bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ - ImGui::SameLine(); \ - bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ - if (isCheckboxActivated) isActivated = true; \ - return isActivated; \ -} - -#define IMGUI_ITEM_DISABLED_GET(VALUE, ITEM, CONDITION) ImguiItem VALUE = ITEM; VALUE.isDisabled = CONDITION; - -IMGUI_ITEM_FUNCTION(_imgui_begin, IMGUI_WINDOW, ImGui::Begin(self.label_get().c_str(), nullptr, self.flags)); -#define IMGUI_BEGIN_OR_RETURN(item, imgui) if (!_imgui_begin(item, imgui)) { _imgui_end(); return; } -static void _imgui_end(void){ImGui::End();} -IMGUI_ITEM_VOID_FUNCTION(_imgui_dockspace, IMGUI_DOCKSPACE, ImGui::DockSpace(ImGui::GetID(self.label_get().c_str()), self.size, self.flags)); -IMGUI_ITEM_FUNCTION(_imgui_begin_child, IMGUI_CHILD, ImGui::BeginChild(self.label_get().c_str(), self.size, self.flags, self.windowFlags)); -static void _imgui_end_child(void) {ImGui::EndChild(); } -IMGUI_ITEM_VOID_FUNCTION(_imgui_text, IMGUI_TEXT, ImGui::Text(self.label_get().c_str())); -IMGUI_ITEM_FUNCTION(_imgui_button, IMGUI_BUTTON, ImGui::Button(self.label_get().c_str(), _imgui_item_size_get(self, type))); -IMGUI_ITEM_FUNCTION(_imgui_begin_table, IMGUI_TABLE, ImGui::BeginTable(self.label_get().c_str(), self.value, self.flags)); -static void _imgui_end_table(void) {ImGui::EndTable(); } -static void _imgui_table_setup_column(const char* text) {ImGui::TableSetupColumn(text); } -static void _imgui_table_headers_row(void) {ImGui::TableHeadersRow(); } -static void _imgui_table_next_row(void) {ImGui::TableNextRow(); } -static void _imgui_table_set_column_index(s32 index) {ImGui::TableSetColumnIndex(index); } -IMGUI_ITEM_FUNCTION(_imgui_selectable, IMGUI_SELECTABLE, ImGui::Selectable(self.label_get().c_str(), self.isSelected, self.flags, _imgui_item_size_get(self, type))); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_radio_button, IMGUI_RADIO_BUTTON, s32, ImGui::RadioButton(self.label_get().c_str(), &value, self.value)); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_button, IMGUI_COLOR_BUTTON, vec4, ImGui::ColorButton(self.label_get().c_str(), ImVec4(value), self.flags)); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_checkbox, IMGUI_CHECKBOX, bool, ImGui::Checkbox(self.label_get().c_str(), &value)); -IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int, IMGUI_INPUT_INT, s32, ImGui::InputInt(self.label_get().c_str(), &value, self.step, self.stepFast, self.flags)); +IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int, IMGUI_INPUT_INT, int, + ImGui::InputInt(self.label_get().c_str(), &value, self.step, self.stepFast, self.flags)); IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_int2, IMGUI_INPUT_INT, ivec2, ImGui::InputInt2(self.label_get().c_str(), value_ptr(value), self.flags)); -IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_float, IMGUI_INPUT_FLOAT, f32, ImGui::InputFloat(self.label_get().c_str(), &value, self.step, self.stepFast, _imgui_f32_format_get(self, value), self.flags)); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_slider_float, IMGUI_SLIDER_FLOAT, f32, ImGui::SliderFloat(self.label_get().c_str(), &value, self.min, self.max, _imgui_f32_format_get(self, value), self.flags)); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float, IMGUI_DRAG_FLOAT, f32, ImGui::DragFloat(self.label_get().c_str(), &value, self.speed, self.min, self.max, _imgui_f32_format_get(self, value))); -IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float2, IMGUI_DRAG_FLOAT, vec2, ImGui::DragFloat2(self.label_get().c_str(), value_ptr(value), self.speed, self.min, self.max, _imgui_vec2_format_get(self, value))); +IMGUI_ITEM_VALUE_CLAMP_FUNCTION(_imgui_input_float, IMGUI_INPUT_FLOAT, float, + ImGui::InputFloat(self.label_get().c_str(), &value, self.step, self.stepFast, _imgui_float_format_get(self, value), + self.flags)); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_checkbox, IMGUI_CHECKBOX, bool, ImGui::Checkbox(self.label_get().c_str(), &value)); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_slider_float, IMGUI_SLIDER_FLOAT, float, + ImGui::SliderFloat(self.label_get().c_str(), &value, *self.min, *self.max, _imgui_float_format_get(self, value), self.flags)); + +IMGUI_ITEM_STRING_FUNCTION(_imgui_input_text, IMGUI_INPUT_TEXT, + (value.resize(*self.max), ImGui::InputText(self.label_get().c_str(), value.data(), *self.max, self.flags))); + +#define IMGUI_BEGIN_OR_RETURN(item, imgui) \ + if (!_imgui_begin(item, imgui)) { \ + _imgui_end(); \ + return; \ + } + +/* +IMGUI_ITEM_VALUE_FUNCTION(_imgui_radio_button, IMGUI_RADIO_BUTTON, int, ImGui::RadioButton(self.label_get().c_str(), &value, self.value)); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_button, IMGUI_COLOR_BUTTON, vec4, ImGui::ColorButton(self.label_get().c_str(), ImVec4(value), self.flags)); + +IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float, IMGUI_DRAG_FLOAT, float, + ImGui::DragFloat(self.label_get().c_str(), &value, self.speed, *self.min, *self.max, _imgui_float_format_get(self, value))); +IMGUI_ITEM_VALUE_FUNCTION(_imgui_drag_float2, IMGUI_DRAG_FLOAT, vec2, + ImGui::DragFloat2(self.label_get().c_str(), value_ptr(value), self.speed, *self.min, *self.max, _imgui_vec2_format_get(self, value))); IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_edit3, IMGUI_COLOR_EDIT, vec3, ImGui::ColorEdit3(self.label_get().c_str(), value_ptr(value), self.flags)); IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_edit4, IMGUI_COLOR_EDIT, vec4, ImGui::ColorEdit4(self.label_get().c_str(), value_ptr(value), self.flags)); +*/ + +/* IMGUI_ITEM_CHECKBOX_FUNCTION(_imgui_checkbox_selectable, _imgui_selectable(self, imgui)); IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_checkbox, bool, _imgui_checkbox(self, imgui, value)); -IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_input_int, s32, _imgui_input_int(self, imgui, value)); -IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_drag_float, f32, _imgui_drag_float(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_input_int, int, _imgui_input_int(self, imgui, value)); +IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_drag_float, float, _imgui_drag_float(self, imgui, value)); IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_drag_float2, vec2, _imgui_drag_float2(self, imgui, value)); IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_color_edit3, vec3, _imgui_color_edit3(self, imgui, value)); IMGUI_ITEM_CHECKBOX_VALUE_FUNCTION(_imgui_checkbox_color_edit4, vec4, _imgui_color_edit4(self, imgui, value)); +*/ -static bool _imgui_input_text(const ImguiItem& self, Imgui* imgui, std::string& value) -{ - value.resize(self.max); - _imgui_item_pre(self, IMGUI_INPUT_TEXT); - bool isActivated = ImGui::InputText(self.label_get().c_str(), value.data(), self.max, self.flags); - _imgui_item_post(self, imgui, IMGUI_INPUT_TEXT, isActivated); - return isActivated; -} +static void _imgui_end(void) { ImGui::End(); } +static void _imgui_end_table(void) { ImGui::EndTable(); } +static void _imgui_table_setup_column(const char* text) { ImGui::TableSetupColumn(text); } +static void _imgui_table_headers_row(void) { ImGui::TableHeadersRow(); } +static void _imgui_table_next_row(void) { ImGui::TableNextRow(); } +static void _imgui_table_set_column_index(int index) { ImGui::TableSetColumnIndex(index); } -static bool _imgui_combo(ImguiItem self, Imgui* imgui, s32* value) -{ - std::vector cStrings; - cStrings.reserve(self.items.size()); - for (auto& string : self.items) - cStrings.emplace_back(string.c_str()); +static bool _imgui_combo(ImguiItem self, Imgui* imgui, int* value) { + std::vector cStrings; + cStrings.reserve(self.items.size()); + for (auto& string : self.items) + cStrings.emplace_back(string.c_str()); - _imgui_item_pre(self, IMGUI_COMBO); + _imgui_item_pre(self, IMGUI_COMBO); - bool isActivated = ImGui::Combo(self.label_get().c_str(), value, cStrings.data(), (s32)self.items.size()); - if (_imgui_is_input_default()) - { - *value = self.value; - isActivated = true; - } + bool isActivated = ImGui::Combo(self.label_get().c_str(), value, cStrings.data(), (int)self.items.size()); + if (_imgui_is_input_default()) { + *value = self.value; + isActivated = true; + } - _imgui_item_post(self, imgui, IMGUI_COMBO, isActivated); - - return isActivated; + _imgui_item_post(self, imgui, IMGUI_COMBO, isActivated); + + return isActivated; }; -IMGUI_ITEM_CUSTOM_FUNCTION(_imgui_atlas_button, IMGUI_ATLAS_BUTTON, -{ - ImVec2 size = _imgui_item_size_get(self, type); +static void _imgui_atlas_text(const ImguiItem& self, Imgui* imgui) { + _imgui_image(self.copy({.textureID = imgui->resources->atlas.id, + .size = ATLAS_SIZE(self.atlas) * imgui->settings->displayScale, + .uvMin = ATLAS_UV_MIN(self.atlas), + .uvMax = ATLAS_UV_MAX(self.atlas)}), + imgui); - if (self.is_size()) - { - isActivated = ImGui::Button(self.label_get().c_str(), size); - - ImVec2 start = ImGui::GetItemRectMin() + self.atlasOffset; - ImVec2 end = start + ImVec2(ATLAS_SIZE(self.atlas)); + _imgui_text(self.copy({.cursorPosition = _imgui_cursor_position_get(self) + + vec2(ATLAS_SIZE(self.atlas).x + (_imgui_item_spacing_get(self).x * imgui->settings->displayScale), 0.0f)}), + imgui); +} - ImGui::GetWindowDrawList()->AddImage(imgui->resources->atlas.id, start, end, ATLAS_UV_ARGS(self.atlas)); - } - else - isActivated = ImGui::ImageButton(self.label_get().c_str(), imgui->resources->atlas.id, size, ATLAS_UV_ARGS(self.atlas)); +/* +IMGUI_ITEM_CUSTOM_FUNCTION(_imgui_atlas_button, IMGUI_ATLAS_BUTTON, { + ImVec2 size = _imgui_item_size_get(self, type); + + if (self.size.has_value()) { + isActivated = ImGui::Button(self.label_get().c_str(), size); + + ImVec2 start = ImGui::GetItemRectMin() + self.atlasOffset; + ImVec2 end = start + ImVec2(ATLAS_SIZE(self.atlas)); + + ImGui::GetWindowDrawList()->AddImage(imgui->resources->atlas.id, start, end, ATLAS_UV_ARGS(self.atlas)); + } else + isActivated = ImGui::ImageButton(self.label_get().c_str(), imgui->resources->atlas.id, size, ATLAS_UV_ARGS(self.atlas)); }); +*/ -static bool _imgui_selectable_input_int(const ImguiItem& self, Imgui* imgui, s32& value) -{ - static s32 temp; - static s32 id = ID_NONE; - ImguiItem selectable = self.copy({.label = std::format(IMGUI_SELECTABLE_INPUT_INT_FORMAT, value)}); - ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); - bool isActivated = false; - - if (id == self.id) - { - isActivated = _imgui_input_int(IMGUI_CHANGE_INPUT_INT.copy({.size = size}), imgui, temp); - imgui_contextual_actions_disable(imgui); - - if (isActivated || _imgui_is_no_click_on_item()) - { - value = temp; - id = ID_NONE; - imgui_contextual_actions_enable(imgui); - } - } - else - { - isActivated = _imgui_selectable(selectable, imgui); - - if (_imgui_is_input_begin()) - { - temp = value; - id = self.id; - ImGui::SetKeyboardFocusHere(-1); - } - } +/* +static bool _imgui_selectable_input_int(const ImguiItem& self, Imgui* imgui, int& value) { + static int temp; + static int id = ID_NONE; + ImguiItem selectable = self.copy({.label = std::format(IMGUI_SELECTABLE_INPUT_INT_FORMAT, value)}); + ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); + bool isActivated = false; - return isActivated; + if (id == self.id) { + isActivated = _imgui_input_int(IMGUI_CHANGE_INPUT_INT.copy({.size = size}), imgui, temp); + imgui_contextual_actions_disable(imgui); + + if (isActivated || _imgui_is_no_click_on_item()) { + value = temp; + id = ID_NONE; + imgui_contextual_actions_enable(imgui); + } + } else { + isActivated = _imgui_selectable(selectable, imgui); + + if (_imgui_is_input_begin()) { + temp = value; + id = self.id; + ImGui::SetKeyboardFocusHere(-1); + } + } + + return isActivated; }; -static bool _imgui_selectable_input_text(const ImguiItem& self, Imgui* imgui, std::string& value) -{ - static std::string buffer{}; - static s32 id = ID_NONE; - ImguiItem selectable = self.copy({}); - ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); - bool isActivated = false; - - if (id == self.id) - { - isActivated = _imgui_input_text(IMGUI_CHANGE_INPUT_TEXT.copy({.size = size}), imgui, buffer); - imgui_contextual_actions_disable(imgui); - - if (isActivated || _imgui_is_no_click_on_item()) - { - value = buffer; - buffer.clear(); - id = ID_NONE; - imgui_contextual_actions_enable(imgui); - } - } - else - { - isActivated = _imgui_selectable(selectable, imgui); - - if (_imgui_is_input_begin()) - { - buffer = value; - buffer.resize(IMGUI_CHANGE_INPUT_TEXT.max); - id = self.id; - ImGui::SetKeyboardFocusHere(-1); - } - } +static bool _imgui_selectable_input_text(const ImguiItem& self, Imgui* imgui, std::string& value) { + static std::string buffer{}; + static int id = ID_NONE; + ImguiItem selectable = self.copy({}); + ImVec2 size = _imgui_item_size_get(selectable, IMGUI_SELECTABLE); + bool isActivated = false; - return isActivated; + if (id == self.id) { + isActivated = _imgui_input_text(IMGUI_CHANGE_INPUT_TEXT.copy({.size = size}), imgui, buffer); + imgui_contextual_actions_disable(imgui); + + if (isActivated || _imgui_is_no_click_on_item()) { + value = buffer; + buffer.clear(); + id = ID_NONE; + imgui_contextual_actions_enable(imgui); + } + } else { + isActivated = _imgui_selectable(selectable, imgui); + + if (_imgui_is_input_begin()) { + buffer = value; + buffer.resize(IMGUI_CHANGE_INPUT_TEXT.max); + id = self.id; + ImGui::SetKeyboardFocusHere(-1); + } + } + + return isActivated; }; +*/ -IMGUI_ITEM_ATLAS_FUNCTION(_imgui_atlas_selectable, _imgui_selectable(self, imgui)); -IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_int, s32, _imgui_selectable_input_int(self, imgui, value)); -IMGUI_ITEM_ATLAS_VALUE_FUNCTION(_imgui_atlas_selectable_input_text, std::string, _imgui_selectable_input_text(self, imgui, value)); +static bool _imgui_confirm_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* state = nullptr, bool isOnlyConfirm = false) { + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); -static bool _imgui_confirm_popup(ImguiItem self, Imgui* imgui, ImguiPopupState* state = nullptr, bool isOnlyConfirm = false) -{ - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - - if (state) *state = IMGUI_POPUP_STATE_CLOSED; + if (state) + *state = IMGUI_POPUP_STATE_CLOSED; - if (imgui_begin_popup_modal(self.label_get().c_str(), imgui)) - { - if (state) *state = IMGUI_POPUP_STATE_OPEN; - - ImGui::Text(self.text_get()); - ImGui::Separator(); + if (imgui_begin_popup_modal(self.label_get().c_str(), imgui)) { + if (state) + *state = IMGUI_POPUP_STATE_OPEN; - if (_imgui_button(IMGUI_POPUP_OK.copy({.rowCount = isOnlyConfirm ? 1 : IMGUI_CONFIRM_POPUP_ROW_COUNT}), imgui)) - { - imgui_close_current_popup(imgui); - imgui_end_popup(imgui); - if (state) *state = isOnlyConfirm ? IMGUI_POPUP_STATE_CANCEL : IMGUI_POPUP_STATE_CONFIRM; - return true; - } + ImGui::Text("%s", self.text_get()); + ImGui::Separator(); - ImGui::SameLine(); - - if (!isOnlyConfirm) - { - if (_imgui_button(IMGUI_POPUP_CANCEL, imgui)) - { - imgui_close_current_popup(imgui); - if (state) *state = IMGUI_POPUP_STATE_CANCEL; - } - } + if (_imgui_button(IMGUI_POPUP_OK.copy({.rowCount = isOnlyConfirm ? 1 : IMGUI_CONFIRM_POPUP_ROW_COUNT}), imgui)) { + imgui_close_current_popup(imgui); + imgui_end_popup(imgui); + if (state) + *state = isOnlyConfirm ? IMGUI_POPUP_STATE_CANCEL : IMGUI_POPUP_STATE_CONFIRM; + return true; + } - imgui_end_popup(imgui); - } + ImGui::SameLine(); - return false; + if (!isOnlyConfirm) { + if (_imgui_button(IMGUI_POPUP_CANCEL, imgui)) { + imgui_close_current_popup(imgui); + if (state) + *state = IMGUI_POPUP_STATE_CANCEL; + } + } + + imgui_end_popup(imgui); + } + + return false; } -static void _imgui_no_anm2_path_check(Imgui* self) -{ - if (_imgui_is_window_hovered_and_click_no_anm2_path(self) && !imgui_is_any_popup_open()) - imgui_open_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION.label); - _imgui_confirm_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION, self, nullptr, true); +static void _imgui_no_anm2_path_check(Imgui* self) { + if (self->anm2->path.empty()) + imgui_open_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION.label); } -static void _imgui_context_menu(Imgui* self) -{ - if (!self->isContextualActionsEnabled) return; - - if (_imgui_is_window_hovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - imgui_open_popup(IMGUI_CONTEXT_MENU.label_get()); - - if (imgui_begin_popup(IMGUI_CONTEXT_MENU.label_get(), self)) - { - _imgui_selectable(IMGUI_CUT, self); - _imgui_selectable(IMGUI_COPY, self); - _imgui_selectable(IMGUI_PASTE.copy({!clipboard_is_value()}), self); - - imgui_end_popup(self); - } +static void _imgui_no_anm2_path_click_check(Imgui* self) { + if (_imgui_is_window_hovered_and_click() && self->anm2->path.empty() && !imgui_is_any_popup_open()) + imgui_open_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION.label); } -static void _imgui_spritesheet_editor_set(Imgui* self, s32 id) -{ - if (self->anm2->spritesheets.contains(id)) self->editor->spritesheetID = id; +static void _imgui_context_menu(Imgui* self) { + if (!self->isContextualActionsEnabled) + return; + + if (_imgui_is_window_hovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + imgui_open_popup(IMGUI_CONTEXT_MENU.label_get()); + + if (imgui_begin_popup(IMGUI_CONTEXT_MENU.label_get(), self)) { + _imgui_selectable(IMGUI_CUT, self); + _imgui_selectable(IMGUI_COPY, self); + _imgui_selectable(IMGUI_PASTE.copy({.isDisabled = !clipboard_is_value()}), self); + + imgui_end_popup(self); + } } -static void _imgui_timeline(Imgui* self) -{ - static const ImU32 frameColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_COLOR); - static const ImU32 frameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_MULTIPLE_COLOR); - static const ImU32 headerFrameColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_COLOR); - static const ImU32 headerFrameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_COLOR); - static const ImU32 headerFrameInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_INACTIVE_COLOR); - static const ImU32 headerFrameMultipleInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR); - static const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); - static Anm2Reference hoverReference; - static Anm2Reference swapItemReference; - static Anm2Type& itemType = self->reference->itemType; - static ImVec2 itemMin{}; - static ImVec2 localMousePos{}; - static ImVec2 mousePos{}; - static ImVec2 playheadPos{}; - static ImVec2 scroll{}; - static bool isItemSwap = false; - static const ImVec2& frameSize = IMGUI_TIMELINE_FRAME_SIZE; - static f32& time = self->preview->time; - static s32 frameTime{}; - static s32& itemID = self->reference->itemID; - - IMGUI_BEGIN_OR_RETURN(IMGUI_TIMELINE, self); - _imgui_no_anm2_path_check(self); - - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - if (!animation) - { - ImGui::Text(IMGUI_TIMELINE_ANIMATION_NONE); - _imgui_end(); // IMGUI_TIMELINE - return; - } - - s32 length = animation->frameNum; - s32 actualLength = anm2_animation_length_get(animation); - - ImVec2 actualFramesSize = {actualLength * frameSize.x, frameSize.y}; - ImVec2 framesSize = {length * frameSize.x, frameSize.y}; - - ImVec2 defaultItemSpacing = ImGui::GetStyle().ItemSpacing; - ImVec2 defaultWindowPadding = ImGui::GetStyle().WindowPadding; - ImVec2 defaultFramePadding = ImGui::GetStyle().FramePadding; - - vec2 windowSize = ImGui::GetContentRegionAvail(); - vec2 childSize = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size.y}; - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - - _imgui_begin_child(IMGUI_TIMELINE_CHILD.copy({.size = childSize}), self); - ImVec2 clipRectMin = ImGui::GetWindowDrawList()->GetClipRectMin(); - ImVec2 clipRectMax = ImGui::GetWindowDrawList()->GetClipRectMax(); - clipRectMin.x += IMGUI_TIMELINE_ITEM_SIZE.x; - - ImVec2 scrollDelta{}; - - if (_imgui_is_window_hovered() && !imgui_is_any_popup_open()) - { - ImGuiIO& io = ImGui::GetIO(); - f32 lineHeight = ImGui::GetTextLineHeight(); - - scrollDelta.x -= io.MouseWheelH * lineHeight; - scrollDelta.y -= io.MouseWheel * lineHeight; - } - - std::function timeline_header = [&]() - { - static bool isPlayheadDrag = false; - _imgui_begin_child(IMGUI_TIMELINE_HEADER_CHILD, self); - - ImGui::SetScrollX(scroll.x); - - itemMin = ImGui::GetItemRectMin(); - mousePos = ImGui::GetMousePos(); - localMousePos = ImVec2(mousePos.x - itemMin.x + scroll.x, mousePos.y - itemMin.y); - frameTime = (s32)(localMousePos.x / frameSize.x); - - if (ImGui::IsMouseDown(0) && _imgui_is_window_hovered()) isPlayheadDrag = true; - - if (isPlayheadDrag) - { - if (self->settings->playbackIsClampPlayhead) - time = std::clamp(frameTime, 0, std::max(0, length - 1)); - else - time = std::max(frameTime, 0); - - if (ImGui::IsMouseReleased(0)) - isPlayheadDrag = false; - } - - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - playheadPos = {cursorPos.x + (time * frameSize.x), cursorPos.y}; - - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - ImVec2 dummySize = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; - ImGui::Dummy(dummySize); - - f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; - s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; - start = (start < 0) ? 0 : start; - - s32 end = (s32)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; - end = (end > ANM2_FRAME_NUM_MAX) ? ANM2_FRAME_NUM_MAX : end; - - playheadPos = ImVec2(cursorPos.x + time * frameSize.x, cursorPos.y); - - for (s32 i = start; i < end; i++) - { - bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; - bool isInactive = i >= length; - - f32 startX = cursorPos.x + i * frameSize.x; - f32 endX = startX + frameSize.x; - ImVec2 positionStart(startX, cursorPos.y); - ImVec2 positionEnd(endX, cursorPos.y + frameSize.y); - - ImU32 bgColor = isInactive - ? (isMultiple ? headerFrameMultipleInactiveColor : headerFrameInactiveColor) - : (isMultiple ? headerFrameMultipleColor : headerFrameColor); - - drawList->AddRectFilled(positionStart, positionEnd, bgColor); - - if (isMultiple) - { - std::string buffer = std::to_string(i); - ImVec2 textSize = ImGui::CalcTextSize(buffer.c_str()); - ImVec2 textPosition(startX + (frameSize.x - textSize.x) * 0.5f, cursorPos.y + (frameSize.y - textSize.y) * 0.5f); - drawList->AddText(textPosition, textColor, buffer.c_str()); - } - - drawList->AddImage(self->resources->atlas.id, positionStart, positionEnd, ATLAS_UV_ARGS(ATLAS_FRAME_ALT)); - } - - _imgui_end_child(); // IMGUI_TIMELINE_HEADER_CHILD - - ImGui::SetNextWindowPos(ImGui::GetWindowPos()); - ImGui::SetNextWindowSize(ImGui::GetWindowSize()); - _imgui_begin(IMGUI_PLAYHEAD, self); - - ImVec2& pos = playheadPos; - - ImVec2 lineStart = {pos.x + (frameSize.x * 0.5f) - (IMGUI_PLAYHEAD_LINE_WIDTH * 0.5f), pos.y + frameSize.y}; - ImVec2 lineEnd = {lineStart.x + IMGUI_PLAYHEAD_LINE_WIDTH, lineStart.y + childSize.y - frameSize.y}; - - drawList = ImGui::GetWindowDrawList(); - - drawList->PushClipRect(clipRectMin, clipRectMax, true); - drawList->AddImage(self->resources->atlas.id, pos, ImVec2(pos.x + frameSize.x, pos.y + frameSize.y), ATLAS_UV_ARGS(ATLAS_PICKER)); - drawList->AddRectFilled(lineStart, lineEnd, IMGUI_PLAYHEAD_LINE_COLOR); - drawList->PopClipRect(); - - _imgui_end(); // IMGUI_PLAYHEAD - }; - - std::function timeline_item_child = [&](Anm2Reference reference, s32& index) - { - Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); - Anm2Type& type = reference.itemType; - if (!item) return; - if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) return; - - ImVec2 buttonSize = ImVec2(ATLAS_SIZE_NORMAL) + (defaultFramePadding * ImVec2(2, 2)); - - Anm2Layer* layer = nullptr; - Anm2Null* null = nullptr; - s32 buttonCount = type == ANM2_NULL ? 2 : 1; - f32 buttonAreaWidth = buttonCount * buttonSize.x + (buttonCount - 1) * defaultItemSpacing.x; - bool isSelected = (self->reference->itemID == reference.itemID && self->reference->itemType == type); - - ImGui::PushID(reference.itemID); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); - - ImVec2 childPos = ImGui::GetCursorScreenPos(); - _imgui_begin_child(*IMGUI_TIMELINE_ITEM_CHILDS[type], self); - ImVec2 childSize = ImGui::GetContentRegionAvail(); - - std::string dragDrop = IMGUI_TIMELINE_ITEM_SELECTABLES[type]->dragDrop; - - switch (type) - { - case ANM2_ROOT: - case ANM2_TRIGGERS: - if (_imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy({.isSelected = isSelected}), self)) - *self->reference = reference; - break; - case ANM2_LAYER: - layer = &self->anm2->layers[reference.itemID]; - if - ( - _imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy - ({ - .isSelected = isSelected, - .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, layer->name) - }), - self - ) - ) - *self->reference = reference; - - break; - case ANM2_NULL: - null = &self->anm2->nulls[reference.itemID]; - if - ( - _imgui_atlas_selectable(IMGUI_TIMELINE_ITEM_SELECTABLES[type]->copy - ({ - .isSelected = isSelected, - .label = std::format(IMGUI_TIMELINE_ITEM_CHILD_FORMAT, reference.itemID, null->name) - }), - self) - ) - *self->reference = reference; - break; - default: - break; - } - - if (imgui_begin_popup_modal(IMGUI_POPUP_ITEM_PROPERTIES, self, IMGUI_POPUP_ITEM_PROPERTIES_SIZE)) - { - static s32 selectedLayerID = ID_NONE; - static s32 selectedNullID = ID_NONE; - Anm2Type& type = reference.itemType; - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); - - _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER.copy({true}), self, (s32&)type); - _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL.copy({true}), self, (s32&)type); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); - - switch (type) - { - case ANM2_LAYER: - default: - { - for (auto & [id, layer] : self->anm2->layers) - { - if (id == reference.itemID) continue; - - ImGui::PushID(id); - - ImguiItem layerItem = IMGUI_LAYER.copy - ({ - .isSelected = selectedLayerID == id, - .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), - .id = id - }); - if (_imgui_atlas_selectable(layerItem, self)) selectedLayerID = id; - - ImGui::PopID(); - }; - break; - } - case ANM2_NULL: - { - for (auto & [id, null] : self->anm2->nulls) - { - if (id == reference.itemID) continue; - - ImGui::PushID(id); - - ImguiItem nullItem = IMGUI_NULL.copy - ({ - .isSelected = selectedNullID == id, - .label = std::format(IMGUI_NULL_FORMAT, id, null.name), - .id = id - }); - if (_imgui_atlas_selectable(nullItem, self)) selectedNullID = id; - - ImGui::PopID(); - }; - break; - } - } - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self); - - if (self->anm2->layers.size() == 0) selectedLayerID = ID_NONE; - if (self->anm2->nulls.size() == 0) selectedNullID = ID_NONE; - - bool isDisabled = type == ANM2_NONE || - (type == ANM2_LAYER && selectedLayerID == ID_NONE) || - (type == ANM2_NULL && selectedNullID == ID_NONE); - - if (_imgui_button(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM.copy({isDisabled}), self)) - { - switch (type) - { - case ANM2_LAYER: - if (!map_find(animation->layerAnimations, selectedLayerID)) - { - anm2_animation_layer_animation_add(animation, selectedLayerID); - anm2_animation_layer_animation_remove(animation, reference.itemID); - } - else - map_swap(animation->layerAnimations, selectedLayerID, reference.itemID); - - *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; - break; - case ANM2_NULL: - if (!map_find(animation->nullAnimations, selectedNullID)) - { - anm2_animation_null_animation_add(animation, selectedNullID); - anm2_animation_null_animation_remove(animation, reference.itemID); - - } - else - map_swap(animation->nullAnimations, selectedNullID, reference.itemID); - - *self->reference = {self->reference->animationID, ANM2_NULL, selectedNullID}; - break; - default: break; - } - - imgui_close_current_popup(self); - } - - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD - - imgui_end_popup(self); - } - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None) && !dragDrop.empty()) - { - *self->reference = reference; - - ImGui::SetDragDropPayload(dragDrop.c_str(), &reference, sizeof(Anm2Reference)); - timeline_item_child(reference, index); - ImGui::EndDragDropSource(); - } - ImGui::PopStyleVar(); - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(dragDrop.c_str())) - { - Anm2Reference checkReference = *(Anm2Reference*)payload->Data; - if (checkReference != reference) - { - swapItemReference = reference; - isItemSwap = true; - } - } - - ImGui::EndDragDropTarget(); - } - - ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); - - if (type == ANM2_NULL) - { - const ImguiItem& rectItem = null->isShowRect ? IMGUI_TIMELINE_ITEM_SHOW_RECT : IMGUI_TIMELINE_ITEM_HIDE_RECT; - if (_imgui_atlas_button(rectItem, self)) null->isShowRect = !null->isShowRect; - ImGui::SameLine(0.0f, defaultItemSpacing.x); - } - - const ImguiItem& visibleItem = item->isVisible ? IMGUI_TIMELINE_ITEM_VISIBLE : IMGUI_TIMELINE_ITEM_INVISIBLE; - if (_imgui_atlas_button(visibleItem, self)) item->isVisible = !item->isVisible; - - ImGui::PopStyleVar(2); - - _imgui_end_child(); // itemChild - - ImGui::PopID(); - - index++; - }; - - std::function timeline_items_child = [&]() - { - static s32& animationID = self->reference->animationID; - s32 index = 0; - - _imgui_begin_child(IMGUI_TIMELINE_ITEMS_CHILD, self); - ImGui::SetScrollY(scroll.y); - - timeline_item_child({animationID, ANM2_ROOT}, index); - - for (auto& id : std::ranges::reverse_view(animation->layerOrder)) - timeline_item_child({animationID, ANM2_LAYER, id}, index); - - for (auto & [id, null] : animation->nullAnimations) - timeline_item_child({animationID, ANM2_NULL, id}, index); - - timeline_item_child({animationID, ANM2_TRIGGERS}, index); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD - - if (isItemSwap) - { - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - imgui_snapshot(self, IMGUI_ACTION_ITEM_SWAP); - - switch (swapItemReference.itemType) - { - case ANM2_LAYER: - vector_value_swap(animation->layerOrder, self->reference->itemID, swapItemReference.itemID); - break; - case ANM2_NULL: - map_swap(animation->nullAnimations, self->reference->itemID, swapItemReference.itemID); - break; - default: break; - } - - self->reference->itemID = swapItemReference.itemID; - anm2_reference_clear(&swapItemReference); - isItemSwap = false; - } - }; - - std::function timeline_item_frames = [&](Anm2Reference reference, s32& index) - { - Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); - if (!item) return; - Anm2Type& type = reference.itemType; - if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) return; - - ImGui::PushID(index); - - ImDrawList* drawList = ImGui::GetWindowDrawList(); - - f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_FRAMES_CHILD.copy({.size = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize}), self); - - ImVec2 startPos = ImGui::GetCursorPos(); - ImVec2 cursorPos = ImGui::GetCursorScreenPos(); - - if (_imgui_is_window_hovered()) - { - hoverReference = reference; - hoverReference.time = frameTime; - hoverReference.frameIndex = anm2_frame_index_from_time(self->anm2, reference, frameTime); - self->clipboard->location = hoverReference; - self->clipboard->type = CLIPBOARD_FRAME; - - if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - { - *self->reference = reference; - if (reference.itemType == ANM2_LAYER && reference.itemID != ID_NONE) - _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); - } - } - - s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; - if (start < 0) start = 0; - - s32 end = (s32)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; - if (end > ANM2_FRAME_NUM_MAX) end = ANM2_FRAME_NUM_MAX; - - for (s32 i = start; i < end; i++) - { - bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; - ImU32 bgColor = isMultiple ? frameMultipleColor : frameColor; - - f32 startX = cursorPos.x + i * frameSize.x; - f32 endX = startX + frameSize.x; - ImVec2 startPosition(startX, cursorPos.y); - ImVec2 endPosition(endX, cursorPos.y + frameSize.y); - - drawList->AddRectFilled(startPosition, endPosition, bgColor); - drawList->AddImage(self->resources->atlas.id, startPosition, endPosition, ATLAS_UV_ARGS(ATLAS_FRAME)); - } - - ImGui::SetCursorPos(startPos); - - std::function timeline_item_frame = [&](s32 i, Anm2Frame& frame) - { - static s32 frameDelayStart{}; - static f32 frameDelayTimeStart{}; - const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); - static bool isFrameSwap = false; - static bool isDrag = false; - - ImGui::PushID(i); - reference.frameIndex = i; - ImVec2 framePos = ImGui::GetCursorPos(); - AtlasType atlas = frame.isInterpolated ? ATLAS_CIRCLE : ATLAS_SQUARE; - - if (type == ANM2_TRIGGERS) - { - framePos.x = startPos.x + (frameSize.x * frame.atFrame); - atlas = ATLAS_TRIGGER; - } - - ImguiItem frameButton = IMGUI_TIMELINE_FRAMES[type]->copy - ({.isSelected = reference == *self->reference, .size = {frameSize.x * frame.delay, frameSize.y}, .id = i, .atlas = atlas}); - - ImGui::SetCursorPos(framePos); - - if (_imgui_atlas_button(frameButton, self)) - { - *self->reference = reference; - frameDelayStart = frame.delay; - frameDelayTimeStart = frameTime; - } - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoDisableHover)) - { - if (!isDrag) - { - *self->reference = reference; - frameDelayStart = frame.delay; - frameDelayTimeStart = frameTime; - isFrameSwap = false; - isDrag = true; - } - - ImGui::SetDragDropPayload(frameButton.drag_drop_get(), &reference, sizeof(Anm2Reference)); - if (!isModCtrl) timeline_item_frame(i, frame); - ImGui::EndDragDropSource(); - } - - if (isDrag) - { - if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, self->reference)) - { - switch (self->reference->itemType) - { - case ANM2_TRIGGERS: - { - referenceFrame->atFrame = std::max(frameTime, 0); - for (auto& trigger : animation->triggers.frames) - { - if (&trigger == referenceFrame) continue; - if (trigger.atFrame == referenceFrame->atFrame) - { - referenceFrame->atFrame++; - break; - } - } - break; - } - case ANM2_ROOT: - case ANM2_LAYER: - case ANM2_NULL: - { - if (isModCtrl) - referenceFrame->delay = std::max(frameDelayStart + (s32)(frameTime - frameDelayTimeStart), ANM2_FRAME_NUM_MIN); - break; - } - default: break; - } - } - } - - if (ImGui::BeginDragDropTarget()) - { - ImGui::AcceptDragDropPayload(frameButton.drag_drop_get()); - ImGui::EndDragDropTarget(); - } - - if (isDrag && ImGui::IsMouseReleased(0)) - { - if - ( !isFrameSwap && - *self->reference != hoverReference && - self->reference->itemType == hoverReference.itemType - ) - { - imgui_snapshot(self, IMGUI_ACTION_FRAME_MOVE); - if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, self->reference)) - { - Anm2Reference addReference = hoverReference; - addReference.frameIndex = hoverReference.frameIndex < self->reference->frameIndex ? - std::min(0, addReference.frameIndex - 2) : - std::min(0, addReference.frameIndex - 1); - Anm2Frame addFrame = *referenceFrame; - anm2_frame_remove(self->anm2, self->reference); - anm2_frame_add(self->anm2, &addFrame, &hoverReference); - *self->reference = hoverReference; - hoverReference = Anm2Reference(); - } - } - - isDrag = false; - } - - - if (i < (s32)item->frames.size() - 1) ImGui::SameLine(); - - ImGui::PopID(); - }; - - for (auto [i, frame] : std::views::enumerate(item->frames)) - timeline_item_frame(i, frame); - - _imgui_end_child(); // itemFramesChild - - ImGui::PopID(); - - index++; - }; - - std::function timeline_frames_child = [&]() - { - s32& animationID = self->reference->animationID; - s32 index = 0; - - _imgui_begin_child(IMGUI_TIMELINE_FRAMES_CHILD, self); - scroll.x = ImGui::GetScrollX() + scrollDelta.x; - scroll.y = ImGui::GetScrollY() + scrollDelta.y; - ImGui::SetScrollX(scroll.x); - ImGui::SetScrollY(scroll.y); - - timeline_item_frames(Anm2Reference(animationID, ANM2_ROOT), index); - - for (auto& id : std::ranges::reverse_view(animation->layerOrder)) - timeline_item_frames(Anm2Reference(animationID, ANM2_LAYER, id), index); - - for (auto & [id, null] : animation->nullAnimations) - timeline_item_frames(Anm2Reference(animationID, ANM2_NULL, id), index); - - timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGERS), index); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); - _imgui_context_menu(self); - ImGui::PopStyleVar(2); - - _imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD - }; - - // In order to set scroll properly timeline_frames_child must be called first - ImGui::SetCursorPos(ImVec2(IMGUI_TIMELINE_ITEM_SIZE)); - timeline_frames_child(); - ImGui::SetCursorPos(ImVec2()); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_CHILD, self); - - const ImguiItem& unusedItem = self->settings->timelineIsShowUnused ? IMGUI_TIMELINE_SHOW_UNUSED : IMGUI_TIMELINE_HIDE_UNUSED; - if (_imgui_atlas_button(unusedItem, self)) self->settings->timelineIsShowUnused = !self->settings->timelineIsShowUnused; - ImGui::PopStyleVar(2); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_CHILD - ImGui::SameLine(); - timeline_header(); - - timeline_items_child(); - - ImGui::PopStyleVar(2); - - _imgui_end_child(); // IMGUI_TIMELINE_CHILD - - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); - _imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self); - - _imgui_button(IMGUI_TIMELINE_ADD_ITEM, self); - - if (imgui_begin_popup_modal(IMGUI_TIMELINE_ADD_ITEM.popup, self, IMGUI_TIMELINE_ADD_ITEM.popupSize)) - { - static s32 selectedLayerID = ID_NONE; - static s32 selectedNullID = ID_NONE; - s32& type = self->settings->timelineAddItemType; - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); - - _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self, type); - _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self, type); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); - - switch (type) - { - case ANM2_LAYER: - default: - { - for (auto & [id, layer] : self->anm2->layers) - { - ImGui::PushID(id); - - ImguiItem layerItem = IMGUI_LAYER.copy - ({ - .isSelected = selectedLayerID == id, - .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), - .id = id - }); - if (_imgui_atlas_selectable(layerItem, self)) selectedLayerID = id; - - ImGui::PopID(); - }; - break; - } - case ANM2_NULL: - { - for (auto & [id, null] : self->anm2->nulls) - { - ImGui::PushID(id); - - ImguiItem nullItem = IMGUI_NULL.copy - ({ - .isSelected = selectedNullID == id, - .label = std::format(IMGUI_NULL_FORMAT, id, null.name), - .id = id - }); - if (_imgui_atlas_selectable(nullItem, self)) selectedNullID = id; - - ImGui::PopID(); - }; - break; - } - } - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD - - _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self); - - if (self->anm2->layers.size() == 0) selectedLayerID = ID_NONE; - if (self->anm2->nulls.size() == 0) selectedNullID = ID_NONE; - - bool isDisabled = type == ANM2_NONE || - (type == ANM2_LAYER && selectedLayerID == ID_NONE) || - (type == ANM2_NULL && selectedNullID == ID_NONE); - - if (_imgui_button(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM.copy({isDisabled}), self)) - { - switch (type) - { - case ANM2_LAYER: - anm2_animation_layer_animation_add(animation, selectedLayerID); - *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; - break; - case ANM2_NULL: - anm2_animation_null_animation_add(animation, selectedNullID); - *self->reference = {self->reference->animationID, ANM2_NULL, selectedNullID}; - break; - default: break; - } - - imgui_close_current_popup(self); - } - - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - - _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD - - imgui_end_popup(self); - } - - if (_imgui_button(IMGUI_TIMELINE_REMOVE_ITEM.copy({!item || itemType == ANM2_ROOT || itemType == ANM2_TRIGGERS}), self)) - { - switch (itemType) - { - case ANM2_LAYER: anm2_animation_layer_animation_remove(animation, itemID); break; - case ANM2_NULL: anm2_animation_null_animation_remove(animation, itemID); break; - default: break; - } - - anm2_reference_item_clear(self->reference); - } - - _imgui_end_child(); //IMGUI_TIMELINE_FOOTER_ITEM_CHILD - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::SameLine(); - ImGui::PopStyleVar(); - - _imgui_begin_child(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, self); - - if (_imgui_button(self->preview->isPlaying ? IMGUI_PAUSE : IMGUI_PLAY, self)) - { - if (!self->preview->isPlaying && time >= animation->frameNum - 1) - time = 0.0f; - self->preview->isPlaying = !self->preview->isPlaying; - } - - if (_imgui_button(IMGUI_ADD_FRAME.copy({!item}), self)) - { - Anm2Reference frameReference = *self->reference; - frameReference.frameIndex = item->frames.empty() ? 0 : std::clamp(frameReference.frameIndex, 0, (s32)(item->frames.size() - 1)); - Anm2Frame* addFrame = anm2_frame_from_reference(self->anm2, &frameReference); - anm2_frame_add(self->anm2, addFrame, &frameReference); - } - - if(_imgui_button(IMGUI_REMOVE_FRAME.copy({!frame}), self)) - { - anm2_frame_remove(self->anm2, self->reference); - anm2_reference_frame_clear(self->reference); - } - - _imgui_button(IMGUI_BAKE.copy({!frame || itemType == ANM2_TRIGGERS}), self); - - if (imgui_begin_popup_modal(IMGUI_BAKE.popup, self, IMGUI_BAKE.popupSize)) - { - static s32& interval = self->settings->bakeInterval; - static bool& isRoundScale = self->settings->bakeIsRoundScale; - static bool& isRoundRotation = self->settings->bakeIsRoundRotation; - - _imgui_begin_child(IMGUI_BAKE_CHILD, self); - - _imgui_input_int(IMGUI_BAKE_INTERVAL.copy({.max = frame->delay}), self, interval); - _imgui_checkbox(IMGUI_BAKE_ROUND_SCALE, self, isRoundScale); - _imgui_checkbox(IMGUI_BAKE_ROUND_ROTATION, self, isRoundRotation); - - if (_imgui_button(IMGUI_BAKE_CONFIRM, self)) - { - anm2_frame_bake(self->anm2, self->reference, interval, isRoundScale, isRoundRotation); - imgui_close_current_popup(self); - } - - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - - _imgui_end_child(); //IMGUI_BAKE_CHILD) - - imgui_end_popup(self); - } - - if (_imgui_button(IMGUI_FIT_ANIMATION_LENGTH, self)) anm2_animation_length_set(animation); - - _imgui_input_int(IMGUI_ANIMATION_LENGTH, self, animation->frameNum); - _imgui_input_int(IMGUI_FPS, self, self->anm2->fps); - _imgui_checkbox(IMGUI_LOOP, self, animation->isLoop); - _imgui_selectable_input_text(IMGUI_CREATED_BY.copy({.label = self->anm2->createdBy}), self, self->anm2->createdBy); - - _imgui_end_child(); // IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD - - _imgui_end(); // IMGUI_TIMELINE -} - -static void _imgui_onionskin(Imgui* self) -{ - IMGUI_BEGIN_OR_RETURN(IMGUI_ONIONSKIN, self); - - static auto& isEnabled = self->settings->onionskinIsEnabled; - static auto& beforeCount = self->settings->onionskinBeforeCount; - static auto& afterCount = self->settings->onionskinAfterCount; - static auto& beforeColorOffset = self->settings->onionskinBeforeColorOffset; - static auto& afterColorOffset = self->settings->onionskinAfterColorOffset; - static auto& drawOrder = self->settings->onionskinDrawOrder; - - _imgui_checkbox(IMGUI_ONIONSKIN_ENABLED, self, isEnabled); - - auto onionskin_section = [&](auto& text, auto& count, auto& colorOffset) - { - ImGui::PushID(text.label.c_str()); - _imgui_text(text, self); - _imgui_input_int(IMGUI_ONIONSKIN_COUNT.copy({!isEnabled}), self, count); - _imgui_color_edit3(IMGUI_ONIONSKIN_COLOR_OFFSET.copy({!isEnabled}), self, colorOffset); - ImGui::PopID(); +/* +static void _imgui_timeline(Imgui* self) { + static const ImU32 frameColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_COLOR); + static const ImU32 frameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_MULTIPLE_COLOR); + static const ImU32 headerFrameColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_COLOR); + static const ImU32 headerFrameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_COLOR); + static const ImU32 headerFrameInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_INACTIVE_COLOR); + static const ImU32 headerFrameMultipleInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR); + static const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); + static Anm2Reference hoverReference; + static Anm2Reference swapItemReference; + static Anm2Type& itemType = self->reference->itemType; + static ImVec2 itemMin{}; + static ImVec2 localMousePos{}; + static ImVec2 mousePos{}; + static ImVec2 playheadPos{}; + static ImVec2 scroll{}; + static bool isItemSwap = false; + static const ImVec2& frameSize = IMGUI_TIMELINE_FRAME_SIZE; + static float& time = self->preview->time; + static int frameTime{}; + static int& itemID = self->reference->itemID; + + IMGUI_BEGIN_OR_RETURN(IMGUI_TIMELINE, self); + _imgui_no_anm2_path_click_check(self); + + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, *self->reference); + + if (!animation) { + ImGui::Text(IMGUI_TIMELINE_ANIMATION_NONE); + _imgui_end(); // IMGUI_TIMELINE + return; + } + + int length = animation->frameNum; + int actualLength = anm2_animation_length_get(animation); + + ImVec2 actualFramesSize = {actualLength * frameSize.x, frameSize.y}; + ImVec2 framesSize = {length * frameSize.x, frameSize.y}; + + ImVec2 defaultItemSpacing = ImGui::GetStyle().ItemSpacing; + ImVec2 defaultWindowPadding = ImGui::GetStyle().WindowPadding; + ImVec2 defaultFramePadding = ImGui::GetStyle().FramePadding; + + vec2 windowSize = ImGui::GetContentRegionAvail(); + vec2 childSize = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size->y}; + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + _imgui_begin_child(IMGUI_TIMELINE_CHILD.copy({.size = childSize}), self); + ImVec2 clipRectMin = ImGui::GetWindowDrawList()->GetClipRectMin(); + ImVec2 clipRectMax = ImGui::GetWindowDrawList()->GetClipRectMax(); + clipRectMin.x += IMGUI_TIMELINE_ITEM_SIZE.x; + + ImVec2 scrollDelta{}; + + if (_imgui_is_window_hovered() && !imgui_is_any_popup_open()) { + ImGuiIO& io = ImGui::GetIO(); + float lineHeight = ImGui::GetTextLineHeight(); + + scrollDelta.x -= io.MouseWheelH * lineHeight; + scrollDelta.y -= io.MouseWheel * lineHeight; + } + + std::function timeline_header = [&]() { + static bool isPlayheadDrag = false; + _imgui_begin_child(IMGUI_TIMELINE_HEADER_CHILD, self); + + ImGui::SetScrollX(scroll.x); + + itemMin = ImGui::GetItemRectMin(); + mousePos = ImGui::GetMousePos(); + localMousePos = ImVec2(mousePos.x - itemMin.x + scroll.x, mousePos.y - itemMin.y); + frameTime = (int)(localMousePos.x / frameSize.x); + + if (ImGui::IsMouseDown(0) && _imgui_is_window_hovered()) + isPlayheadDrag = true; + + if (isPlayheadDrag) { + if (self->settings->playbackIsClampPlayhead) + time = std::clamp(frameTime, 0, std::max(0, length - 1)); + else + time = std::max(frameTime, 0); + + if (ImGui::IsMouseReleased(0)) + isPlayheadDrag = false; + } + + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + playheadPos = {cursorPos.x + (time * frameSize.x), cursorPos.y}; + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 dummySize = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; + ImGui::Dummy(dummySize); + + float viewWidth = ImGui::GetContentRegionAvail().x; + int start = (int)std::floor(scroll.x / frameSize.x) - 1; + start = (start < 0) ? 0 : start; + + int end = (int)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; + end = (end > ANM2_FRAME_NUM_MAX) ? ANM2_FRAME_NUM_MAX : end; + + playheadPos = ImVec2(cursorPos.x + time * frameSize.x, cursorPos.y); + + for (int i = start; i < end; i++) { + bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; + bool isInactive = i >= length; + + float startX = cursorPos.x + i * frameSize.x; + float endX = startX + frameSize.x; + ImVec2 positionStart(startX, cursorPos.y); + ImVec2 positionEnd(endX, cursorPos.y + frameSize.y); + + ImU32 bgColor = + isInactive ? (isMultiple ? headerFrameMultipleInactiveColor : headerFrameInactiveColor) : (isMultiple ? headerFrameMultipleColor : headerFrameColor); + + drawList->AddRectFilled(positionStart, positionEnd, bgColor); + + if (isMultiple) { + std::string buffer = std::to_string(i); + ImVec2 textSize = ImGui::CalcTextSize(buffer.c_str()); + ImVec2 textPosition(startX + (frameSize.x - textSize.x) * 0.5f, cursorPos.y + (frameSize.y - textSize.y) * 0.5f); + drawList->AddText(textPosition, textColor, buffer.c_str()); + } + + drawList->AddImage(self->resources->atlas.id, positionStart, positionEnd, ATLAS_UV_ARGS(ATLAS_FRAME_ALT)); + } + + _imgui_end_child(); // IMGUI_TIMELINE_HEADER_CHILD + + ImGui::SetNextWindowPos(ImGui::GetWindowPos()); + ImGui::SetNextWindowSize(ImGui::GetWindowSize()); + _imgui_begin(IMGUI_PLAYHEAD, self); + + ImVec2& pos = playheadPos; + + ImVec2 lineStart = {pos.x + (frameSize.x * 0.5f) - (IMGUI_PLAYHEAD_LINE_WIDTH * 0.5f), pos.y + frameSize.y}; + ImVec2 lineEnd = {lineStart.x + IMGUI_PLAYHEAD_LINE_WIDTH, lineStart.y + childSize.y - frameSize.y}; + + drawList = ImGui::GetWindowDrawList(); + + drawList->PushClipRect(clipRectMin, clipRectMax, true); + drawList->AddImage(self->resources->atlas.id, pos, ImVec2(pos.x + frameSize.x, pos.y + frameSize.y), ATLAS_UV_ARGS(ATLAS_PICKER)); + drawList->AddRectFilled(lineStart, lineEnd, IMGUI_PLAYHEAD_LINE_COLOR); + drawList->PopClipRect(); + + _imgui_end(); // IMGUI_PLAYHEAD + }; + + std::function timeline_item_child = [&](Anm2Reference reference, int& index) { + Anm2Item* item = anm2_item_from_reference(self->anm2, reference); + Anm2Type& type = reference.itemType; + if (!item) + return; + if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) + return; + + ImVec2 buttonSize = ImVec2(ATLAS_SIZE_NORMAL) + (defaultFramePadding * ImVec2(2, 2)); + + Anm2Layer* layer = nullptr; + Anm2Null* null = nullptr; + int buttonCount = type == ANM2_NULL ? 2 : 1; + float buttonAreaWidth = buttonCount * buttonSize.x + (buttonCount - 1) * defaultItemSpacing.x; + bool isSelected = (self->reference->itemID == reference.itemID && self->reference->itemType == type); + + ImGui::PushID(reference.itemID); + + ImVec2 childPos = ImGui::GetCursorScreenPos(); + _imgui_begin_child(*IMGUI_TIMELINE_ITEM_CHILDS[type], self); + ImVec2 childSize = ImGui::GetContentRegionAvail(); + + const ImguiItem* selectable = IMGUI_TIMELINE_ITEM_SELECTABLES[type]; + + std::string dragDrop = selectable->drag_drop_get(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); + + _imgui_selectable(selectable->copy({.isSelected = isSelected}), self); + + if (imgui_begin_popup_modal(IMGUI_POPUP_ITEM_PROPERTIES, self, IMGUI_POPUP_ITEM_PROPERTIES_SIZE)) { + static int selectedLayerID = ID_NONE; + static int selectedNullID = ID_NONE; + Anm2Type& type = reference.itemType; + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); + + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER.copy({.isDisabled = true}), self, (int&)type); + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL.copy({.isDisabled = true}), self, (int&)type); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); + + switch (type) { + case ANM2_LAYER: + default: { + for (auto& [id, layer] : self->anm2->layers) { + if (id == reference.itemID) + continue; + + ImGui::PushID(id); + + ImguiItem layerItem = IMGUI_LAYER.copy({.label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), .isSelected = selectedLayerID = id, .id = id}); + if (_imgui_atlas_selectable(layerItem, self)) + selectedLayerID = id; + + ImGui::PopID(); + }; + break; + } + case ANM2_NULL: { + for (auto& [id, null] : self->anm2->nulls) { + if (id == reference.itemID) + continue; + + ImGui::PushID(id); + + ImguiItem nullItem = IMGUI_NULL.copy({.label = std::format(IMGUI_NULL_FORMAT, id, null.name), .isSelected = selectedNullID == id, .id = id}); + if (_imgui_atlas_selectable(nullItem, self)) + selectedNullID = id; + + ImGui::PopID(); + }; + break; + } + } + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self); + + if (self->anm2->layers.size() == 0) + selectedLayerID = ID_NONE; + if (self->anm2->nulls.size() == 0) + selectedNullID = ID_NONE; + + bool isDisabled = type == ANM2_NONE || (type == ANM2_LAYER && selectedLayerID == ID_NONE) || (type == ANM2_NULL && selectedNullID == ID_NONE); + + if (_imgui_button(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM.copy({.isDisabled = isDisabled}), self)) { + switch (type) { + case ANM2_LAYER: + if (!map_find(animation->layerAnimations, selectedLayerID)) { + anm2_animation_layer_animation_add(animation, selectedLayerID); + anm2_animation_layer_animation_remove(animation, reference.itemID); + } else + map_swap(animation->layerAnimations, selectedLayerID, reference.itemID); + + *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; + selectedLayerID = ID_NONE; + break; + case ANM2_NULL: + if (!map_find(animation->nullAnimations, selectedNullID)) { + anm2_animation_null_animation_add(animation, selectedNullID); + anm2_animation_null_animation_remove(animation, reference.itemID); + + } else + map_swap(animation->nullAnimations, selectedNullID, reference.itemID); + + *self->reference = {self->reference->animationID, ANM2_NULL, selectedNullID}; + selectedNullID = ID_NONE; + break; + default: + break; + } + + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD + + imgui_end_popup(self); + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None) && !dragDrop.empty()) { + *self->reference = reference; + + ImGui::SetDragDropPayload(dragDrop.c_str(), &reference, sizeof(Anm2Reference)); + timeline_item_child(reference, index); + ImGui::EndDragDropSource(); + } + ImGui::PopStyleVar(); + + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(dragDrop.c_str())) { + Anm2Reference checkReference = *(Anm2Reference*)payload->Data; + if (checkReference != reference) { + swapItemReference = reference; + isItemSwap = true; + } + } + + ImGui::EndDragDropTarget(); + } + + ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); + + if (type == ANM2_NULL && null) { + const ImguiItem& rectItem = null->isShowRect ? IMGUI_TIMELINE_ITEM_SHOW_RECT : IMGUI_TIMELINE_ITEM_HIDE_RECT; + if (_imgui_atlas_button(rectItem, self)) + null->isShowRect = !null->isShowRect; + ImGui::SameLine(0.0f, defaultItemSpacing.x); + } + + const ImguiItem& visibleItem = item->isVisible ? IMGUI_TIMELINE_ITEM_VISIBLE : IMGUI_TIMELINE_ITEM_INVISIBLE; + if (_imgui_atlas_button(visibleItem, self)) + item->isVisible = !item->isVisible; + + ImGui::PopStyleVar(2); + + _imgui_end_child(); // itemChild + + ImGui::PopID(); + + index++; + }; + + std::function timeline_items_child = [&]() { + static int& animationID = self->reference->animationID; + int index = 0; + + _imgui_begin_child(IMGUI_TIMELINE_ITEMS_CHILD, self); + ImGui::SetScrollY(scroll.y); + + timeline_item_child({animationID, ANM2_ROOT}, index); + + for (auto& id : std::ranges::reverse_view(animation->layerOrder)) + timeline_item_child({animationID, ANM2_LAYER, id}, index); + + for (auto& [id, null] : animation->nullAnimations) + timeline_item_child({animationID, ANM2_NULL, id}, index); + + timeline_item_child({animationID, ANM2_TRIGGER}, index); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD + + if (isItemSwap) { + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, *self->reference); + + imgui_snapshot(self, IMGUI_ACTION_ITEM_SWAP); + + switch (swapItemReference.itemType) { + case ANM2_LAYER: + vector_value_swap(animation->layerOrder, self->reference->itemID, swapItemReference.itemID); + break; + case ANM2_NULL: + map_swap(animation->nullAnimations, self->reference->itemID, swapItemReference.itemID); + break; + default: + break; + } + + self->reference->itemID = swapItemReference.itemID; + swapItemReference = Anm2Reference(); + isItemSwap = false; + } + }; + + std::function timeline_item_frames = [&](Anm2Reference reference, int& index) { + Anm2Item* item = anm2_item_from_reference(self->anm2, reference); + if (!item) + return; + Anm2Type& type = reference.itemType; + if (!self->settings->timelineIsShowUnused && item->frames.empty() && (type == ANM2_LAYER || type == ANM2_NULL)) + return; + + ImGui::PushID(index); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + float viewWidth = ImGui::GetContentRegionAvail().x; + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_FRAMES_CHILD.copy({.size = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize}), self); + + ImVec2 startPos = ImGui::GetCursorPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + + if (_imgui_is_window_hovered()) { + hoverReference = reference; + hoverReference.frameIndex = anm2_frame_index_from_time(self->anm2, reference, frameTime); + self->clipboard->location = hoverReference; + self->clipboard->type = CLIPBOARD_FRAME; + + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + *self->reference = reference; + if (reference.itemType == ANM2_LAYER && reference.itemID != ID_NONE) + self->editor->spritesheetID = self->anm2->layers[self->reference->itemID].spritesheetID; + } + } + + int start = (int)std::floor(scroll.x / frameSize.x) - 1; + if (start < 0) + start = 0; + + int end = (int)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; + if (end > ANM2_FRAME_NUM_MAX) + end = ANM2_FRAME_NUM_MAX; + + for (int i = start; i < end; i++) { + bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; + ImU32 bgColor = isMultiple ? frameMultipleColor : frameColor; + + float startX = cursorPos.x + i * frameSize.x; + float endX = startX + frameSize.x; + ImVec2 startPosition(startX, cursorPos.y); + ImVec2 endPosition(endX, cursorPos.y + frameSize.y); + + drawList->AddRectFilled(startPosition, endPosition, bgColor); + drawList->AddImage(self->resources->atlas.id, startPosition, endPosition, ATLAS_UV_ARGS(ATLAS_FRAME)); + } + + ImGui::SetCursorPos(startPos); + + std::function timeline_item_frame = [&](int i, Anm2Frame& frame) { + static int frameDelayStart{}; + static float frameDelayTimeStart{}; + const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); + const bool isModAlt = ImGui::IsKeyDown(IMGUI_INPUT_ALT); + static bool isDrag = false; + + ImGui::PushID(i); + reference.frameIndex = i; + ImVec2 framePos = ImGui::GetCursorPos(); + AtlasType atlas = frame.isInterpolated ? ATLAS_CIRCLE : ATLAS_SQUARE; + + if (type == ANM2_TRIGGER) { + framePos.x = startPos.x + (frameSize.x * frame.atFrame); + atlas = ATLAS_TRIGGER; + } + + ImguiItem frameButton = IMGUI_TIMELINE_FRAMES[type]->copy( + {.atlas = atlas, .size = vec2(frameSize.x * frame.delay, frameSize.y), .isSelected = reference == *self->reference, .id = i}); + + ImGui::SetCursorPos(framePos); + + if (_imgui_atlas_button(frameButton, self)) { + if (isModAlt) + frame.isInterpolated = !frame.isInterpolated; + else { + *self->reference = reference; + frameDelayStart = frame.delay; + frameDelayTimeStart = frameTime; + self->preview->time = frameTime; + } + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) { + if (!isDrag) { + *self->reference = reference; + frameDelayStart = frame.delay; + frameDelayTimeStart = frameTime; + isDrag = true; + } + + ImGui::SetDragDropPayload(frameButton.drag_drop_get(), &reference, sizeof(Anm2Reference)); + if (!isModCtrl && type != ANM2_TRIGGER) { + ImGui::BeginTooltip(); + _imgui_atlas_button(frameButton, self); + ImGui::EndTooltip(); + } + ImGui::EndDragDropSource(); + } + + if (isDrag) { + self->pendingCursor = SDL_SYSTEM_CURSOR_POINTER; + + if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, *self->reference)) { + switch (self->reference->itemType) { + case ANM2_TRIGGER: { + referenceFrame->atFrame = std::max(frameTime, 0); + for (auto& trigger : animation->triggers.frames) { + if (&trigger == referenceFrame) + continue; + if (trigger.atFrame == referenceFrame->atFrame) { + referenceFrame->atFrame++; + break; + } + } + break; + } + case ANM2_ROOT: + case ANM2_LAYER: + case ANM2_NULL: { + if (isModCtrl) + referenceFrame->delay = std::max(frameDelayStart + (int)(frameTime - frameDelayTimeStart), ANM2_FRAME_NUM_MIN); + break; + } + default: + break; + } + } + } + + if (ImGui::BeginDragDropTarget()) { + ImGui::AcceptDragDropPayload(frameButton.drag_drop_get()); + ImGui::EndDragDropTarget(); + } + + if (isDrag && ImGui::IsMouseReleased(0)) { + if (!isModCtrl && *self->reference != hoverReference && self->reference->itemType == hoverReference.itemType) { + imgui_snapshot(self, IMGUI_ACTION_FRAME_MOVE); + if (Anm2Frame* referenceFrame = anm2_frame_from_reference(self->anm2, *self->reference)) { + Anm2Reference addReference = hoverReference; + addReference.frameIndex = + hoverReference.frameIndex < self->reference->frameIndex ? std::min(0, addReference.frameIndex - 2) : std::min(0, addReference.frameIndex - 1); + Anm2Frame addFrame = *referenceFrame; + anm2_frame_remove(self->anm2, *self->reference); + anm2_frame_add(self->anm2, &addFrame, hoverReference); + *self->reference = hoverReference; + hoverReference = Anm2Reference(); + } + } + + isDrag = false; + } + + if (i < (int)item->frames.size() - 1) + ImGui::SameLine(); + + ImGui::PopID(); }; - onionskin_section(IMGUI_ONIONSKIN_BEFORE, beforeCount, beforeColorOffset); - onionskin_section(IMGUI_ONIONSKIN_AFTER, afterCount, afterColorOffset); - - ImGui::Separator(); + for (auto [i, frame] : std::views::enumerate(item->frames)) + timeline_item_frame(i, frame); - _imgui_text(IMGUI_ONIONSKIN_DRAW_ORDER, self); - _imgui_radio_button(IMGUI_ONIONSKIN_BELOW.copy({!isEnabled}), self, drawOrder); - _imgui_radio_button(IMGUI_ONIONSKIN_ABOVE.copy({!isEnabled}), self, drawOrder); + _imgui_end_child(); // itemFramesChild - _imgui_end(); // IMGUI_ONIONSKIN + ImGui::PopID(); + + index++; + }; + + std::function timeline_frames_child = [&]() { + int& animationID = self->reference->animationID; + int index = 0; + + _imgui_begin_child(IMGUI_TIMELINE_FRAMES_CHILD, self); + scroll.x = ImGui::GetScrollX() + scrollDelta.x; + scroll.y = ImGui::GetScrollY() + scrollDelta.y; + ImGui::SetScrollX(scroll.x); + ImGui::SetScrollY(scroll.y); + + timeline_item_frames(Anm2Reference(animationID, ANM2_ROOT), index); + + for (auto& id : std::ranges::reverse_view(animation->layerOrder)) + timeline_item_frames(Anm2Reference(animationID, ANM2_LAYER, id), index); + + for (auto& [id, null] : animation->nullAnimations) + timeline_item_frames(Anm2Reference(animationID, ANM2_NULL, id), index); + + timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGER), index); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); + _imgui_context_menu(self); + ImGui::PopStyleVar(2); + + _imgui_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD + }; + + // In order to set scroll properly timeline_frames_child must be called first + ImGui::SetCursorPos(ImVec2(IMGUI_TIMELINE_ITEM_SIZE)); + timeline_frames_child(); + ImGui::SetCursorPos(ImVec2()); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_CHILD, self); + + const ImguiItem& unusedItem = self->settings->timelineIsShowUnused ? IMGUI_TIMELINE_SHOW_UNUSED : IMGUI_TIMELINE_HIDE_UNUSED; + if (_imgui_atlas_button(unusedItem, self)) + self->settings->timelineIsShowUnused = !self->settings->timelineIsShowUnused; + ImGui::PopStyleVar(2); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_CHILD + ImGui::SameLine(); + timeline_header(); + + timeline_items_child(); + + ImGui::PopStyleVar(2); + + _imgui_end_child(); // IMGUI_TIMELINE_CHILD + + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *self->reference); + Anm2Item* item = anm2_item_from_reference(self->anm2, *self->reference); + _imgui_begin_child(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self); + + _imgui_button(IMGUI_TIMELINE_ADD_ITEM, self); + + if (imgui_begin_popup_modal(IMGUI_TIMELINE_ADD_ITEM.popup, self, IMGUI_TIMELINE_ADD_ITEM.popupSize)) { + static int selectedLayerID = ID_NONE; + static int selectedNullID = ID_NONE; + int& type = self->settings->timelineAddItemType; + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self); + + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self, type); + _imgui_radio_button(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self, type); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self); + + switch (type) { + case ANM2_LAYER: + default: { + for (auto& [id, layer] : self->anm2->layers) { + ImGui::PushID(id); + + ImguiItem layerItem = IMGUI_LAYER.copy({.label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), .isSelected = selectedLayerID == id, .id = id}); + if (_imgui_atlas_selectable(layerItem, self)) + selectedLayerID = id; + + ImGui::PopID(); + }; + break; + } + case ANM2_NULL: { + for (auto& [id, null] : self->anm2->nulls) { + ImGui::PushID(id); + + ImguiItem nullItem = IMGUI_NULL.copy({.label = std::format(IMGUI_NULL_FORMAT, id, null.name), .isSelected = selectedNullID == id, .id = id}); + if (_imgui_atlas_selectable(nullItem, self)) + selectedNullID = id; + + ImGui::PopID(); + }; + break; + } + } + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD + + _imgui_begin_child(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self); + + if (self->anm2->layers.size() == 0) + selectedLayerID = ID_NONE; + if (self->anm2->nulls.size() == 0) + selectedNullID = ID_NONE; + + bool isDisabled = type == ANM2_NONE || (type == ANM2_LAYER && selectedLayerID == ID_NONE) || (type == ANM2_NULL && selectedNullID == ID_NONE); + + if (_imgui_button(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM.copy({.isDisabled = isDisabled}), self)) { + switch (type) { + case ANM2_LAYER: + anm2_animation_layer_animation_add(animation, selectedLayerID); + *self->reference = {self->reference->animationID, ANM2_LAYER, selectedLayerID}; + selectedLayerID = ID_NONE; + break; + case ANM2_NULL: + anm2_animation_null_animation_add(animation, selectedNullID); + *self->reference = {self->reference->animationID, ANM2_NULL, selectedNullID}; + selectedNullID = ID_NONE; + break; + default: + break; + } + + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + + _imgui_end_child(); // IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD + + imgui_end_popup(self); + } + + if (_imgui_button(IMGUI_TIMELINE_REMOVE_ITEM.copy({.isDisabled = !item || itemType == ANM2_ROOT || itemType == ANM2_TRIGGER}), self)) { + switch (itemType) { + case ANM2_LAYER: + anm2_animation_layer_animation_remove(animation, itemID); + break; + case ANM2_NULL: + anm2_animation_null_animation_remove(animation, itemID); + break; + default: + break; + } + + *self->reference = {self->reference->animationID}; + } + + _imgui_end_child(); // IMGUI_TIMELINE_FOOTER_ITEM_CHILD + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::SameLine(); + ImGui::PopStyleVar(); + + _imgui_begin_child(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, self); + + if (_imgui_button(self->preview->isPlaying ? IMGUI_PAUSE : IMGUI_PLAY, self)) { + if (!self->preview->isPlaying && time >= animation->frameNum - 1) + time = 0.0f; + self->preview->isPlaying = !self->preview->isPlaying; + } + + if (_imgui_button(IMGUI_INSERT_FRAME.copy({.isDisabled = !item}), self)) { + Anm2Reference frameReference = *self->reference; + Anm2Frame addFrame = Anm2Frame(); + + switch (self->reference->itemType) { + case ANM2_ROOT: + case ANM2_LAYER: + case ANM2_NULL: + frameReference.frameIndex = item->frames.empty() ? 0 : std::clamp(frameReference.frameIndex, 0, (int)(item->frames.size() - 1)); + + addFrame = *anm2_frame_from_reference(self->anm2, frameReference); + break; + case ANM2_TRIGGER: + addFrame.atFrame = (int)self->preview->time; + break; + default: + break; + } + + anm2_frame_add(self->anm2, &addFrame, frameReference); + } + + if (_imgui_button(IMGUI_REMOVE_FRAME.copy({.isDisabled = !frame}), self)) { + anm2_frame_remove(self->anm2, *self->reference); + self->reference->frameIndex = ID_NONE; + } + + _imgui_button(IMGUI_BAKE.copy({.isDisabled = !frame || itemType == ANM2_TRIGGER}), self); + + if (imgui_begin_popup_modal(IMGUI_BAKE.popup, self, IMGUI_BAKE.popupSize)) { + static int& interval = self->settings->bakeInterval; + static bool& isRoundScale = self->settings->bakeIsRoundScale; + static bool& isRoundRotation = self->settings->bakeIsRoundRotation; + + _imgui_begin_child(IMGUI_BAKE_CHILD, self); + + _imgui_input_int(IMGUI_BAKE_INTERVAL.copy({.max = frame->delay}), self, interval); + _imgui_checkbox(IMGUI_BAKE_ROUND_SCALE, self, isRoundScale); + _imgui_checkbox(IMGUI_BAKE_ROUND_ROTATION, self, isRoundRotation); + + if (_imgui_button(IMGUI_BAKE_CONFIRM, self)) { + anm2_frame_bake(self->anm2, *self->reference, interval, isRoundScale, isRoundRotation); + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + + _imgui_end_child(); // IMGUI_BAKE_CHILD) + + imgui_end_popup(self); + } + + if (_imgui_button(IMGUI_FIT_ANIMATION_LENGTH, self)) + anm2_animation_length_set(animation); + + _imgui_input_int(IMGUI_ANIMATION_LENGTH, self, animation->frameNum); + _imgui_input_int(IMGUI_FPS, self, self->anm2->fps); + _imgui_checkbox(IMGUI_LOOP, self, animation->isLoop); + _imgui_selectable_input_text(IMGUI_CREATED_BY.copy({.label = self->anm2->createdBy}), self, self->anm2->createdBy); + + _imgui_end_child(); // IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD + + _imgui_end(); // IMGUI_TIMELINE } -static void _imgui_taskbar(Imgui* self) -{ - static ImguiPopupState exitConfirmState = IMGUI_POPUP_STATE_CLOSED; +static void _imgui_onionskin(Imgui* self) { + IMGUI_BEGIN_OR_RETURN(IMGUI_ONIONSKIN, self); - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImguiItem taskbar = IMGUI_TASKBAR; - ImGui::SetNextWindowSize({viewport->Size.x, IMGUI_TASKBAR.size.y}); - ImGui::SetNextWindowPos(viewport->Pos); - _imgui_begin(taskbar, self); + static auto& isEnabled = self->settings->onionskinIsEnabled; + static auto& beforeCount = self->settings->onionskinBeforeCount; + static auto& afterCount = self->settings->onionskinAfterCount; + static auto& beforeColorOffset = self->settings->onionskinBeforeColorOffset; + static auto& afterColorOffset = self->settings->onionskinAfterColorOffset; + static auto& drawOrder = self->settings->onionskinDrawOrder; - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); + _imgui_checkbox(IMGUI_ONIONSKIN_ENABLED, self, isEnabled); - _imgui_selectable(IMGUI_FILE, self); - - if (imgui_begin_popup(IMGUI_FILE.popup, self)) - { - _imgui_selectable(IMGUI_NEW, self); - _imgui_selectable(IMGUI_OPEN, self); - _imgui_selectable(IMGUI_SAVE.copy({self->anm2->path.empty()}), self); - _imgui_selectable(IMGUI_SAVE_AS.copy({self->anm2->path.empty()}), self); - _imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION.copy({self->anm2->path.empty()}), self); - _imgui_selectable(IMGUI_EXIT, self); - imgui_end_popup(self); - } + auto onionskin_section = [&](auto& text, auto& count, auto& colorOffset) { + ImGui::PushID(text.label.c_str()); + _imgui_text(text, self); + _imgui_input_int(IMGUI_ONIONSKIN_COUNT.copy({.isDisabled = !isEnabled}), self, count); + _imgui_color_edit3(IMGUI_ONIONSKIN_COLOR_OFFSET.copy({.isDisabled = !isEnabled}), self, colorOffset); + ImGui::PopID(); + }; - if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_OPEN) - { - _imgui_anm2_open(self, self->dialog->path); - dialog_reset(self->dialog); - } + onionskin_section(IMGUI_ONIONSKIN_BEFORE, beforeCount, beforeColorOffset); + onionskin_section(IMGUI_ONIONSKIN_AFTER, afterCount, afterColorOffset); - if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_SAVE) - { - anm2_serialize(self->anm2, self->dialog->path); - window_title_from_path_set(self->window, self->dialog->path); - imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->dialog->path)); - dialog_reset(self->dialog); - } + ImGui::Separator(); - if (self->isTryQuit) imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label); + _imgui_text(IMGUI_ONIONSKIN_DRAW_ORDER, self); + _imgui_radio_button(IMGUI_ONIONSKIN_BELOW.copy({.isDisabled = !isEnabled}), self, drawOrder); + _imgui_radio_button(IMGUI_ONIONSKIN_ABOVE.copy({.isDisabled = !isEnabled}), self, drawOrder); - _imgui_confirm_popup(IMGUI_EXIT_CONFIRMATION, self, &exitConfirmState); + _imgui_end(); // IMGUI_ONIONSKIN +} +*/ - switch (exitConfirmState) - { - case IMGUI_POPUP_STATE_CONFIRM: self->isQuit = true; break; - case IMGUI_POPUP_STATE_CANCEL: self->isTryQuit = false; break; - default: break; - } +static void _imgui_taskbar(Imgui* self) { + static ImguiPopupState exitConfirmState = IMGUI_POPUP_STATE_CLOSED; + static float& displayScale = self->settings->displayScale; - _imgui_selectable(IMGUI_WIZARD.copy({}), self); - - if (imgui_begin_popup(IMGUI_WIZARD.popup, self)) - { - _imgui_selectable(IMGUI_GENERATE_ANIMATION_FROM_GRID.copy({!item || (self->reference->itemType != ANM2_LAYER)}), self); - _imgui_selectable(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.copy({!item}), self); - _imgui_selectable(IMGUI_SCALE_ANM2.copy({self->anm2->animations.empty()}), self); - _imgui_selectable(IMGUI_RENDER_ANIMATION.copy({!animation}), self); - - imgui_end_popup(self); - } + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImguiItem taskbar = IMGUI_TASKBAR; + ImGui::SetNextWindowSize({viewport->Size.x, IMGUI_TASKBAR.size->y * displayScale}); + ImGui::SetNextWindowPos(viewport->Pos); + _imgui_begin(taskbar, self); - if (imgui_begin_popup_modal(IMGUI_GENERATE_ANIMATION_FROM_GRID.popup, self, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize)) - { - static auto& startPosition = self->settings->generateStartPosition; - static auto& size = self->settings->generateSize; - static auto& pivot = self->settings->generatePivot; - static auto& rows = self->settings->generateRows; - static auto& columns = self->settings->generateColumns; - static auto& count = self->settings->generateCount; - static auto& delay = self->settings->generateDelay; - static f32& time = self->generatePreview->time; + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, *self->reference); + Anm2Item* item = anm2_item_from_reference(self->anm2, *self->reference); - _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self); - _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, startPosition); - _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, self, size); - _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, pivot); - _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, rows); - _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, columns); - _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT.copy({.max = rows * columns}), self, count); - _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, self, delay); - _imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD - - ImGui::SameLine(); + _imgui_selectable(IMGUI_FILE, self); - _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self); - - generate_preview_draw(self->generatePreview); - ImGui::Image(self->generatePreview->canvas.framebuffer, GENERATE_PREVIEW_SIZE); + if (imgui_begin_popup(IMGUI_FILE.popup, self)) { + _imgui_selectable(IMGUI_NEW, self); + _imgui_selectable(IMGUI_OPEN, self); + _imgui_selectable(IMGUI_SAVE.copy({.isDisabled = self->anm2->path.empty()}), self); + _imgui_selectable(IMGUI_SAVE_AS.copy({.isDisabled = self->anm2->path.empty()}), self); + _imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION.copy({.isDisabled = self->anm2->path.empty()}), self); + _imgui_selectable(IMGUI_EXIT, self); + imgui_end_popup(self); + } - _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, self); - _imgui_slider_float(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER, self, time); - _imgui_end_child(); // IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD + if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_OPEN) { + _imgui_anm2_open(self, self->dialog->path); + dialog_reset(self->dialog); + } - _imgui_end_child(); //IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - if (_imgui_button(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self)) - { - anm2_generate_from_grid(self->anm2, self->reference, startPosition, size, pivot, columns, count, delay); - imgui_close_current_popup(self); - } - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - _imgui_end_child(); // IMGUI_FOOTER_CHILD + if (self->dialog->isSelected && self->dialog->type == DIALOG_ANM2_SAVE) { + anm2_serialize(self->anm2, self->dialog->path); + window_title_from_path_set(self->window, self->dialog->path); + imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->dialog->path)); + dialog_reset(self->dialog); + } - imgui_end_popup(self); - } + if (self->isTryQuit && !imgui_is_popup_open(IMGUI_EXIT_CONFIRMATION.label)) + imgui_open_popup(IMGUI_EXIT_CONFIRMATION.label); - if (imgui_begin_popup_modal(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popup, self, IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize)) - { - static auto& isCrop = self->settings->changeIsCrop; - static auto& isSize = self->settings->changeIsSize; - static auto& isPosition = self->settings->changeIsPosition; - static auto& isPivot = self->settings->changeIsPivot; - static auto& isScale = self->settings->changeIsScale; - static auto& isRotation = self->settings->changeIsRotation; - static auto& isColorOffset = self->settings->changeIsColorOffset; - static auto& isTint = self->settings->changeIsTint; - static auto& isVisibleSet = self->settings->changeIsVisibleSet; - static auto& isInterpolatedSet = self->settings->changeIsInterpolatedSet; - static auto& crop = self->settings->changeCrop; - static auto& size = self->settings->changeSize; - static auto& position = self->settings->changePosition; - static auto& pivot = self->settings->changePivot; - static auto& scale = self->settings->changeScale; - static auto& rotation = self->settings->changeRotation; - static auto& isDelay = self->settings->changeIsDelay; - static auto& delay = self->settings->changeDelay; - static auto& tint = self->settings->changeTint; - static auto& colorOffset = self->settings->changeColorOffset; - static auto& isVisible = self->settings->changeIsVisible; - static auto& isInterpolated = self->settings->changeIsInterpolated; - static auto& isFromSelectedFrame = self->settings->changeIsFromSelectedFrame; - static auto& numberFrames = self->settings->changeNumberFrames; - s32 start = std::max(self->reference->frameIndex, 0); - s32 max = isFromSelectedFrame ? - std::max(ANM2_FRAME_NUM_MIN, (s32)item->frames.size() - start) : (s32)item->frames.size(); + _imgui_confirm_popup(IMGUI_EXIT_CONFIRMATION, self, &exitConfirmState); - auto change_frames = [&](Anm2ChangeType type) - { - anm2_item_frame_set - ( - self->anm2, self->reference, - Anm2FrameChange - { - isVisibleSet ? std::optional{isVisible} : std::nullopt, - isInterpolatedSet ? std::optional{isInterpolated} : std::nullopt, - isRotation ? std::optional{rotation} : std::nullopt, - isDelay ? std::optional{delay} : std::nullopt, - isCrop ? std::optional{crop} : std::nullopt, - isPivot ? std::optional{pivot} : std::nullopt, - isPosition ? std::optional{position} : std::nullopt, - isSize ? std::optional{size} : std::nullopt, - isScale ? std::optional{scale} : std::nullopt, - isColorOffset ? std::optional{colorOffset} : std::nullopt, - isTint ? std::optional{tint} : std::nullopt - }, - type, start, numberFrames - ); + switch (exitConfirmState) { + case IMGUI_POPUP_STATE_CONFIRM: + self->isQuit = true; + break; + case IMGUI_POPUP_STATE_CANCEL: + self->isTryQuit = false; + break; + default: + break; + } - imgui_close_current_popup(self); - }; + _imgui_selectable(IMGUI_WIZARD.copy({}), self); - _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, self); - _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({!isCrop}), self, crop, isCrop); - _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({!isSize}), self, size, isSize); - _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({!isPosition}), self, position, isPosition); - _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({!isPivot}), self, pivot, isPivot); - _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({!isScale}), self, scale, isScale); - _imgui_checkbox_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({!isRotation}), self, rotation, isRotation); - _imgui_checkbox_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({!isDelay}), self, delay, isDelay); - _imgui_checkbox_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({!isTint}), self, tint, isTint); - _imgui_checkbox_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({!isColorOffset}), self, colorOffset, isColorOffset); - _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({!isVisibleSet}), self, isVisible, isVisibleSet); - ImGui::NewLine(); - _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({!isInterpolatedSet}), self, isInterpolated, isInterpolatedSet); - _imgui_end_child(); // IMGUI_FOOTER_CHILD + if (imgui_begin_popup(IMGUI_WIZARD.popup, self)) { + _imgui_selectable(IMGUI_GENERATE_ANIMATION_FROM_GRID.copy({.isDisabled = !item || (self->reference->itemType != ANM2_LAYER)}), self); + _imgui_selectable(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.copy({.isDisabled = !item}), self); + _imgui_selectable(IMGUI_SCALE_ANM2.copy({.isDisabled = self->anm2->animations.empty()}), self); + _imgui_selectable(IMGUI_RENDER_ANIMATION.copy({.isDisabled = !animation}), self); - _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, self); - _imgui_text(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS, self); - _imgui_checkbox(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, self, isFromSelectedFrame); - _imgui_input_int(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES.copy({.max = max, .value = max}), self, numberFrames); - _imgui_end_child(); // IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD + imgui_end_popup(self); + } - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, self)) change_frames(ANM2_CHANGE_ADD); - if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, self)) change_frames(ANM2_CHANGE_SUBTRACT); - if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, self)) change_frames(ANM2_CHANGE_SET); - if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, self)) imgui_close_current_popup(self); - _imgui_end_child(); // IMGUI_FOOTER_CHILD - - imgui_end_popup(self); - } + if (imgui_begin_popup_modal(IMGUI_GENERATE_ANIMATION_FROM_GRID.popup, self, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize)) { + static auto& startPosition = self->settings->generateStartPosition; + static auto& size = self->settings->generateSize; + static auto& pivot = self->settings->generatePivot; + static auto& rows = self->settings->generateRows; + static auto& columns = self->settings->generateColumns; + static auto& count = self->settings->generateCount; + static auto& delay = self->settings->generateDelay; + static float& time = self->generatePreview->time; - if (imgui_begin_popup_modal(IMGUI_SCALE_ANM2.popup, self, IMGUI_SCALE_ANM2.popupSize)) - { - _imgui_begin_child(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self); - _imgui_input_float(IMGUI_SCALE_ANM2_VALUE, self, self->settings->scaleValue); - _imgui_end_child(); // IMGUI_SCALE_ANM2_OPTIONS_CHILD - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - if (_imgui_button(IMGUI_SCALE_ANM2_SCALE, self)) - { - anm2_scale(self->anm2, self->settings->scaleValue); - imgui_close_current_popup(self); - } - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) imgui_close_current_popup(self); - _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self); + _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self, startPosition); + _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, self, size); + _imgui_input_int2(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self, pivot); + _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self, rows); + _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self, columns); + _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT.copy({.max = rows * columns}), self, count); + _imgui_input_int(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, self, delay); + _imgui_end_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self); - imgui_end_popup(self); - } + ImGui::SameLine(); - if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize)) - { - static DialogType& dialogType = self->dialog->type; - static bool& dialogIsSelected = self->dialog->isSelected; - static s32& type = self->settings->renderType; - static f32& scale = self->settings->renderScale; - static bool& isUseAnimationBounds = self->settings->renderIsUseAnimationBounds; - static std::string& dialogPath = self->dialog->path; - static std::string& ffmpegPath = self->settings->renderFFmpegPath; - static std::string& format = self->settings->renderFormat; - static std::string& path = self->settings->renderPath; + _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self); - _imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self); + generate_preview_draw(self->generatePreview); + ImGui::Image(self->generatePreview->canvas.framebuffer, GENERATE_PREVIEW_SIZE); - if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self)) - dialog_render_path_set(self->dialog, (RenderType)type); + _imgui_begin_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, self); + _imgui_slider_float(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER, self, time); + _imgui_end_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, self); + _imgui_end_child(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self); - if (dialogIsSelected && (dialogType == DIALOG_RENDER_PATH_SET)) - { - path = path_extension_change(dialogPath, RENDER_EXTENSIONS[type]); - dialog_reset(self->dialog); - } + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + if (_imgui_button(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self)) { + anm2_generate_from_grid(self->anm2, *self->reference, startPosition, size, pivot, columns, count, delay); + imgui_close_current_popup(self); + } + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + _imgui_end_child(IMGUI_FOOTER_CHILD, self); - _imgui_input_text(IMGUI_RENDER_ANIMATION_LOCATION, self, path); - - if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self)) - dialog_ffmpeg_path_set(self->dialog); + imgui_end_popup(self); + } - if (dialogIsSelected && dialogType == DIALOG_FFMPEG_PATH_SET) - { - ffmpegPath = self->dialog->path; - dialog_reset(self->dialog); - } - - _imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, ffmpegPath); - _imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &type); - _imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT.copy({type != RENDER_PNG}), self, format); - _imgui_checkbox(IMGUI_RENDER_ANIMATION_IS_USE_ANIMATION_BOUNDS, self, isUseAnimationBounds); - _imgui_input_float(IMGUI_RENDER_ANIMATION_SCALE.copy({!isUseAnimationBounds}), self, scale); + if (imgui_begin_popup_modal(IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popup, self, IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize)) { + static auto& isCrop = self->settings->changeIsCrop; + static auto& isSize = self->settings->changeIsSize; + static auto& isPosition = self->settings->changeIsPosition; + static auto& isPivot = self->settings->changeIsPivot; + static auto& isScale = self->settings->changeIsScale; + static auto& isRotation = self->settings->changeIsRotation; + static auto& isColorOffset = self->settings->changeIsColorOffset; + static auto& isTint = self->settings->changeIsTint; + static auto& isVisibleSet = self->settings->changeIsVisibleSet; + static auto& isInterpolatedSet = self->settings->changeIsInterpolatedSet; + static auto& crop = self->settings->changeCrop; + static auto& size = self->settings->changeSize; + static auto& position = self->settings->changePosition; + static auto& pivot = self->settings->changePivot; + static auto& scale = self->settings->changeScale; + static auto& rotation = self->settings->changeRotation; + static auto& isDelay = self->settings->changeIsDelay; + static auto& delay = self->settings->changeDelay; + static auto& tint = self->settings->changeTint; + static auto& colorOffset = self->settings->changeColorOffset; + static auto& isVisible = self->settings->changeIsVisible; + static auto& isInterpolated = self->settings->changeIsInterpolated; + static auto& isFromSelectedFrame = self->settings->changeIsFromSelectedFrame; + static auto& numberFrames = self->settings->changeNumberFrames; + int start = std::max(self->reference->frameIndex, 0); + int max = isFromSelectedFrame ? std::max(ANM2_FRAME_NUM_MIN, (int)item->frames.size() - start) : (int)item->frames.size(); - _imgui_end_child(); // IMGUI_RENDER_ANIMATION_CHILD - - _imgui_begin_child(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self); + auto change_frames = [&](Anm2ChangeType type) { + Anm2FrameChange frameChange = { + .isVisible = isVisibleSet ? std::optional{isVisible} : std::nullopt, + .isInterpolated = isInterpolatedSet ? std::optional{isInterpolated} : std::nullopt, + .rotation = isRotation ? std::optional{rotation} : std::nullopt, + .delay = isDelay ? std::optional{delay} : std::nullopt, + .crop = isCrop ? std::optional{crop} : std::nullopt, + .pivot = isPivot ? std::optional{pivot} : std::nullopt, + .position = isPosition ? std::optional{position} : std::nullopt, + .size = isSize ? std::optional{size} : std::nullopt, + .scale = isScale ? std::optional{scale} : std::nullopt, + .offsetRGB = isColorOffset ? std::optional{colorOffset} : std::nullopt, + .tintRGBA = isTint ? std::optional{tint} : std::nullopt, + }; + anm2_item_frame_set(self->anm2, *self->reference, frameChange, type, start, numberFrames); - if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self)) - { - bool isRenderStart = true; + imgui_close_current_popup(self); + }; - if (!std::filesystem::exists(ffmpegPath)) - { - imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR); - isRenderStart = false; - } + _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, self); + /* + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({.isDisabled = !isCrop}), self, crop, isCrop); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({.isDisabled = !isSize}), self, size, isSize); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({.isDisabled = !isPosition}), self, position, isPosition); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({.isDisabled = !isPivot}), self, pivot, isPivot); + _imgui_checkbox_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({.isDisabled = !isScale}), self, scale, isScale); + _imgui_checkbox_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({.isDisabled = !isRotation}), self, rotation, isRotation); + _imgui_checkbox_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({.isDisabled = !isDelay}), self, delay, isDelay); + _imgui_checkbox_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({.isDisabled = !isTint}), self, tint, isTint); + _imgui_checkbox_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({.isDisabled = !isColorOffset}), self, colorOffset, isColorOffset); + _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({.isDisabled = !isVisibleSet}), self, isVisible, isVisibleSet); + ImGui::NewLine(); + _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({.isDisabled = !isInterpolatedSet}), self, isInterpolated, isInterpolatedSet); + */ + _imgui_end_child(IMGUI_FOOTER_CHILD, self); - if (isRenderStart) - { - switch (self->settings->renderType) - { - case RENDER_PNG: - if (!std::filesystem::is_directory(path)) - { - imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR); - isRenderStart = false; - } - break; - case RENDER_GIF: - case RENDER_WEBM: - case RENDER_MP4: - if (!path_is_valid(path)) - { - imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR); - isRenderStart = false; - } - default: - break; - } - } + _imgui_begin_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, self); + _imgui_text(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS, self); + _imgui_checkbox(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, self, isFromSelectedFrame); + _imgui_input_int(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES.copy({.max = max, .value = max}), self, numberFrames); + _imgui_end_child(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, self); - if (isRenderStart) - preview_render_start(self->preview); - else - self->preview->isRenderCancelled = true; - - imgui_close_current_popup(self); - } - - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) - imgui_close_current_popup(self); + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, self)) + change_frames(ANM2_CHANGE_ADD); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, self)) + change_frames(ANM2_CHANGE_SUBTRACT); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, self)) + change_frames(ANM2_CHANGE_SET); + if (_imgui_button(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, self)) + imgui_close_current_popup(self); + _imgui_end_child(IMGUI_FOOTER_CHILD, self); - _imgui_end_child(); // IMGUI_RENDER_ANIMATION_FOOTER_CHILD - - imgui_end_popup(self); - } + imgui_end_popup(self); + } - if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION_CONFIRM.popup, self, IMGUI_RENDER_ANIMATION_CONFIRM.popupSize)) - { - static s32& type = self->settings->renderType; - static std::string& format = self->settings->renderFormat; + if (imgui_begin_popup_modal(IMGUI_SCALE_ANM2.popup, self, IMGUI_SCALE_ANM2.popupSize)) { + _imgui_begin_child(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self); + _imgui_input_float(IMGUI_SCALE_ANM2_VALUE, self, self->settings->scaleValue); + _imgui_end_child(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self); - auto rendering_end = [&]() - { - preview_render_end(self->preview); - imgui_close_current_popup(self); - }; - - std::vector& frames = self->preview->renderFrames; - std::string path = std::string(self->settings->renderPath.c_str()); - - if (self->preview->isRenderCancelled) - { - rendering_end(); - self->preview->isRenderCancelled = false; - } + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + if (_imgui_button(IMGUI_SCALE_ANM2_SCALE, self)) { + anm2_scale(self->anm2, self->settings->scaleValue); + imgui_close_current_popup(self); + } + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); + _imgui_end_child(IMGUI_FOOTER_CHILD, self); - if (!animation) - { - imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR); - rendering_end(); - } + imgui_end_popup(self); + } - _imgui_begin_child(IMGUI_RENDERING_ANIMATION_CHILD, self); + if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION.popup, self, IMGUI_RENDER_ANIMATION.popupSize)) { + static DialogType& dialogType = self->dialog->type; + static bool& dialogIsSelected = self->dialog->isSelected; + static int& type = self->settings->renderType; + static float& scale = self->settings->renderScale; + static bool& isUseAnimationBounds = self->settings->renderIsUseAnimationBounds; + static std::string& dialogPath = self->dialog->path; + static std::string& ffmpegPath = self->settings->renderFFmpegPath; + static std::string& format = self->settings->renderFormat; + static std::string& path = self->settings->renderPath; - f32 progress = self->preview->time / (animation->frameNum - 1); - ImGui::ProgressBar(progress); + _imgui_begin_child(IMGUI_RENDER_ANIMATION_CHILD, self); - _imgui_text(IMGUI_RENDERING_ANIMATION_INFO, self); + /* + if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self)) + dialog_render_path_set(self->dialog, (RenderType)type); + */ - _imgui_end_child(); //IMGUI_RENDERING_ANIMATION_CHILD + if (dialogIsSelected && (dialogType == DIALOG_RENDER_PATH_SET)) { + path = path_extension_change(dialogPath, RENDER_EXTENSIONS[type]); + dialog_reset(self->dialog); + } - if (_imgui_button(IMGUI_RENDERING_ANIMATION_CANCEL, self)) - self->preview->isRenderCancelled = true; + _imgui_input_text(IMGUI_RENDER_ANIMATION_LOCATION, self, path); - if (self->preview->isRenderFinished && frames.empty()) - { - imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR); - rendering_end(); - } - - if (self->preview->isRenderFinished) - { - switch (type) - { - case RENDER_PNG: - { - std::filesystem::path workingPath = std::filesystem::current_path(); - std::filesystem::current_path(path); + /* + if (_imgui_atlas_button(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self)) + dialog_ffmpeg_path_set(self->dialog); + */ - for (auto [i, frame] : std::views::enumerate(frames)) - { - std::string framePath = std::vformat(format, std::make_format_args(i)); - framePath = path_extension_change(framePath, RENDER_EXTENSIONS[type]); - if (!frame.isInvalid) texture_from_gl_write(&frame, framePath); - } + if (dialogIsSelected && dialogType == DIALOG_FFMPEG_PATH_SET) { + ffmpegPath = self->dialog->path; + dialog_reset(self->dialog); + } - std::filesystem::current_path(workingPath); - imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT, path)); - break; - } - case RENDER_GIF: - case RENDER_WEBM: - case RENDER_MP4: - { - std::string ffmpegPath = std::string(self->settings->renderFFmpegPath.c_str()); - path = path_extension_change(path, RENDER_EXTENSIONS[self->settings->renderType]); - - if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, (RenderType)type)) - imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT, path)); - else - imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR, path)); - break; - } - default: - break; - } - - rendering_end(); - } + _imgui_input_text(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self, ffmpegPath); + _imgui_combo(IMGUI_RENDER_ANIMATION_OUTPUT, self, &type); + _imgui_input_text(IMGUI_RENDER_ANIMATION_FORMAT.copy({.isDisabled = type != RENDER_PNG}), self, format); + _imgui_checkbox(IMGUI_RENDER_ANIMATION_IS_USE_ANIMATION_BOUNDS, self, isUseAnimationBounds); + _imgui_input_float(IMGUI_RENDER_ANIMATION_SCALE.copy({.isDisabled = !isUseAnimationBounds}), self, scale); - imgui_end_popup(self); - } + _imgui_end_child(IMGUI_RENDER_ANIMATION_CHILD, self); - _imgui_selectable(IMGUI_PLAYBACK.copy({}), self); + _imgui_begin_child(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self); - if (imgui_begin_popup(IMGUI_PLAYBACK.popup, self, IMGUI_PLAYBACK.popupSize)) - { - _imgui_checkbox_selectable(IMGUI_ALWAYS_LOOP, self, self->settings->playbackIsLoop); - _imgui_checkbox_selectable(IMGUI_CLAMP_PLAYHEAD, self, self->settings->playbackIsClampPlayhead); - imgui_end_popup(self); - } + if (_imgui_button(IMGUI_RENDER_ANIMATION_CONFIRM, self)) { + bool isRenderStart = true; - _imgui_selectable(IMGUI_SETTINGS.copy({}), self); + if (!std::filesystem::exists(ffmpegPath)) { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR); + isRenderStart = false; + } - if (imgui_begin_popup(IMGUI_SETTINGS.popup, self, IMGUI_SETTINGS.popupSize)) - { - if (_imgui_checkbox_selectable(IMGUI_VSYNC, self, self->settings->isVsync)) window_vsync_set(self->settings->isVsync); - _imgui_selectable(IMGUI_HOTKEYS, self); - if (_imgui_selectable(IMGUI_DEFAULT_SETTINGS, self)) *self->settings = Settings(); - imgui_end_popup(self); - } + if (isRenderStart) { + switch (self->settings->renderType) { + case RENDER_PNG: + if (!std::filesystem::is_directory(path)) { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR); + isRenderStart = false; + } + break; + case RENDER_GIF: + case RENDER_WEBM: + case RENDER_MP4: + if (!path_is_valid(path)) { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR); + isRenderStart = false; + } + default: + break; + } + } - if (imgui_begin_popup_modal(IMGUI_HOTKEYS.popup, self, IMGUI_HOTKEYS.popupSize)) - { - _imgui_begin_child(IMGUI_HOTKEYS_CHILD, self); + if (isRenderStart) + preview_render_start(self->preview); + else + self->preview->isRenderCancelled = true; - if (_imgui_begin_table(IMGUI_HOTKEYS_TABLE, self)) - { - static s32 selectedIndex = INDEX_NONE; + imgui_close_current_popup(self); + } - _imgui_table_setup_column(IMGUI_HOTKEYS_FUNCTION); - _imgui_table_setup_column(IMGUI_HOTKEYS_HOTKEY); - _imgui_table_headers_row(); + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) + imgui_close_current_popup(self); - for (s32 i = 0; i < HOTKEY_COUNT; i++) - { - if (!SETTINGS_HOTKEY_MEMBERS[i]) continue; + _imgui_end_child(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self); - bool isSelected = selectedIndex == i; - const char* string = HOTKEY_STRINGS[i]; - std::string* settingString = &(self->settings->*SETTINGS_HOTKEY_MEMBERS[i]); - std::string chordString = isSelected ? IMGUI_HOTKEY_CHANGE : *settingString; - - ImGui::PushID(i); - _imgui_table_next_row(); - _imgui_table_set_column_index(0); - ImGui::TextUnformatted(string); - _imgui_table_set_column_index(1); + imgui_end_popup(self); + } - if (ImGui::Selectable(chordString.c_str(), isSelected)) selectedIndex = i; - ImGui::PopID(); + if (imgui_begin_popup_modal(IMGUI_RENDER_ANIMATION_CONFIRM.popup, self, IMGUI_RENDER_ANIMATION_CONFIRM.popupSize)) { + static int& type = self->settings->renderType; + static std::string& format = self->settings->renderFormat; - if (isSelected) - { - ImGuiKeyChord chord = IMGUI_CHORD_NONE; + auto rendering_end = [&]() { + preview_render_end(self->preview); + imgui_close_current_popup(self); + }; - if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) chord |= ImGuiMod_Ctrl; - if (ImGui::IsKeyDown(ImGuiMod_Shift)) chord |= ImGuiMod_Shift; - if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt; - if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super; + std::vector& frames = self->preview->renderFrames; + std::string path = std::string(self->settings->renderPath.c_str()); - for (auto& [_, key] : IMGUI_KEY_MAP) - { - if (ImGui::IsKeyPressed(key)) - { - chord |= key; - imgui_hotkey_chord_registry()[i] = chord; - *settingString = imgui_string_from_chord_get(chord); - selectedIndex = INDEX_NONE; - break; - } - } - } - } + if (self->preview->isRenderCancelled) { + rendering_end(); + self->preview->isRenderCancelled = false; + } - _imgui_end_table(); - } + if (!animation) { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR); + rendering_end(); + } - _imgui_end_child(); // IMGUI_HOTKEYS_CHILD; + _imgui_begin_child(IMGUI_RENDERING_ANIMATION_CHILD, self); - _imgui_begin_child(IMGUI_HOTKEYS_OPTIONS_CHILD, self); - if (_imgui_button(IMGUI_HOTKEYS_CONFIRM, self)) imgui_close_current_popup(self); - _imgui_end_child(); // IMGUI_HOTKEYS_OPTIONS_CHILD - - imgui_end_popup(self); - } - - _imgui_end(); + float progress = self->preview->time / (animation->frameNum - 1); + ImGui::ProgressBar(progress); + + _imgui_text(IMGUI_RENDERING_ANIMATION_INFO, self); + + _imgui_end_child(IMGUI_RENDERING_ANIMATION_CHILD, self); + + if (_imgui_button(IMGUI_RENDERING_ANIMATION_CANCEL, self)) + self->preview->isRenderCancelled = true; + + if (self->preview->isRenderFinished && frames.empty()) { + imgui_log_push(self, IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR); + rendering_end(); + } + + if (self->preview->isRenderFinished) { + switch (type) { + case RENDER_PNG: { + std::filesystem::path workingPath = std::filesystem::current_path(); + std::filesystem::current_path(path); + + for (auto [i, frame] : std::views::enumerate(frames)) { + std::string framePath = std::vformat(format, std::make_format_args(i)); + framePath = path_extension_change(framePath, RENDER_EXTENSIONS[type]); + if (!frame.isInvalid) + texture_from_gl_write(&frame, framePath); + } + + std::filesystem::current_path(workingPath); + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT, path)); + break; + } + case RENDER_GIF: + case RENDER_WEBM: + case RENDER_MP4: { + std::string ffmpegPath = std::string(self->settings->renderFFmpegPath.c_str()); + path = path_extension_change(path, RENDER_EXTENSIONS[self->settings->renderType]); + + if (ffmpeg_render(ffmpegPath, path, frames, self->preview->canvas.size, self->anm2->fps, (RenderType)type)) + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT, path)); + else + imgui_log_push(self, std::format(IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR, path)); + break; + } + default: + break; + } + + rendering_end(); + } + + imgui_end_popup(self); + } + + _imgui_selectable(IMGUI_PLAYBACK.copy({}), self); + + if (imgui_begin_popup(IMGUI_PLAYBACK.popup, self, IMGUI_PLAYBACK.popupSize)) { + /* + _imgui_checkbox_selectable(IMGUI_ALWAYS_LOOP, self, self->settings->playbackIsLoop); + _imgui_checkbox_selectable(IMGUI_CLAMP_PLAYHEAD, self, self->settings->playbackIsClampPlayhead); + */ + imgui_end_popup(self); + } + + _imgui_selectable(IMGUI_SETTINGS.copy({}), self); + + /* + if (imgui_begin_popup(IMGUI_SETTINGS.popup, self, IMGUI_SETTINGS.popupSize)) { + if (_imgui_checkbox_selectable(IMGUI_VSYNC, self, self->settings->isVsync)) + window_vsync_set(self->settings->isVsync); + _imgui_selectable(IMGUI_HOTKEYS, self); + if (_imgui_selectable(IMGUI_DEFAULT_SETTINGS, self)) + *self->settings = Settings(); + imgui_end_popup(self); + } + */ + + if (imgui_begin_popup_modal(IMGUI_HOTKEYS.popup, self, IMGUI_HOTKEYS.popupSize)) { + _imgui_begin_child(IMGUI_HOTKEYS_CHILD, self); + + if (_imgui_begin_table(IMGUI_HOTKEYS_TABLE, self)) { + static int selectedIndex = INDEX_NONE; + + ImGui::TableSetupScrollFreeze(0, 1); + _imgui_table_setup_column(IMGUI_HOTKEYS_FUNCTION); + _imgui_table_setup_column(IMGUI_HOTKEYS_HOTKEY); + _imgui_table_headers_row(); + + for (int i = 0; i < HOTKEY_COUNT; i++) { + if (!SETTINGS_HOTKEY_MEMBERS[i]) + continue; + + bool isSelected = selectedIndex == i; + const char* string = HOTKEY_STRINGS[i]; + std::string* settingString = &(self->settings->*SETTINGS_HOTKEY_MEMBERS[i]); + std::string chordString = isSelected ? IMGUI_HOTKEY_CHANGE : *settingString; + + ImGui::PushID(i); + _imgui_table_next_row(); + _imgui_table_set_column_index(0); + ImGui::TextUnformatted(string); + _imgui_table_set_column_index(1); + + if (ImGui::Selectable(chordString.c_str(), isSelected)) + selectedIndex = i; + ImGui::PopID(); + + if (isSelected) { + ImGuiKeyChord chord = IMGUI_CHORD_NONE; + + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) + chord |= ImGuiMod_Ctrl; + if (ImGui::IsKeyDown(ImGuiMod_Shift)) + chord |= ImGuiMod_Shift; + if (ImGui::IsKeyDown(ImGuiMod_Alt)) + chord |= ImGuiMod_Alt; + if (ImGui::IsKeyDown(ImGuiMod_Super)) + chord |= ImGuiMod_Super; + + for (auto& [_, key] : IMGUI_KEY_MAP) { + if (ImGui::IsKeyPressed(key)) { + chord |= key; + imgui_hotkey_chord_registry()[i] = chord; + *settingString = imgui_string_from_chord_get(chord); + selectedIndex = INDEX_NONE; + break; + } + } + } + } + + _imgui_end_table(); + } + + _imgui_end_child(IMGUI_HOTKEYS_CHILD, self); + + _imgui_begin_child(IMGUI_HOTKEYS_OPTIONS_CHILD, self); + if (_imgui_button(IMGUI_HOTKEYS_CONFIRM, self)) + imgui_close_current_popup(self); + _imgui_end_child(IMGUI_HOTKEYS_OPTIONS_CHILD, self); + + imgui_end_popup(self); + } + + _imgui_end(); } -static void _imgui_tools(Imgui* self) -{ - ImGuiStyle style = ImGui::GetStyle(); +/* +static void _imgui_tools(Imgui* self) { + ImGuiStyle style = ImGui::GetStyle(); - IMGUI_BEGIN_OR_RETURN(IMGUI_TOOLS, self); + IMGUI_BEGIN_OR_RETURN(IMGUI_TOOLS, self); - f32 availableWidth = ImGui::GetContentRegionAvail().x; - f32 usedWidth = style.FramePadding.x; + float availableWidth = ImGui::GetContentRegionAvail().x; + float usedWidth = style.FramePadding.x; - for (s32 i = 0; i < TOOL_COUNT; i++) - { - ImguiItem item = *IMGUI_TOOL_ITEMS[i]; + for (int i = 0; i < TOOL_COUNT; i++) { + ImguiItem item = *IMGUI_TOOL_ITEMS[i]; - if (i > 0 && usedWidth + ImGui::GetItemRectSize().x < availableWidth) - ImGui::SameLine(); - else - usedWidth = 0; + if (i > 0 && usedWidth + ImGui::GetItemRectSize().x < availableWidth) + ImGui::SameLine(); + else + usedWidth = 0; - item.isSelected = self->settings->tool == (ToolType)i; - - switch ((ToolType)i) - { - case TOOL_UNDO: item.isDisabled = self->snapshots->undoStack.is_empty(); break; - case TOOL_REDO: item.isDisabled = self->snapshots->redoStack.is_empty(); break; - default: break; - } + item.isSelected = self->settings->tool == (ToolType)i; - if (i != TOOL_COLOR) - _imgui_atlas_button(item, self); - else - _imgui_color_edit4(item, self, self->settings->toolColor); - - usedWidth += ImGui::GetItemRectSize().x + style.ItemSpacing.x; - } + switch ((ToolType)i) { + case TOOL_UNDO: + item.isDisabled = self->snapshots->undoStack.is_empty(); + break; + case TOOL_REDO: + item.isDisabled = self->snapshots->redoStack.is_empty(); + break; + default: + break; + } - _imgui_end(); // IMGUI_TOOLS + if (i != TOOL_COLOR) + _imgui_atlas_button(item, self); + else + _imgui_color_edit4(item, self, self->settings->toolColor); + + usedWidth += ImGui::GetItemRectSize().x + style.ItemSpacing.x; + } + + _imgui_end(); // IMGUI_TOOLS } -static void _imgui_layers(Imgui* self) -{ - static s32 selectedLayerID = ID_NONE; +static void _imgui_layers(Imgui* self) { + static int selectedLayerID = ID_NONE; - IMGUI_BEGIN_OR_RETURN(IMGUI_LAYERS, self); - _imgui_no_anm2_path_check(self); - - ImVec2 size = ImGui::GetContentRegionAvail(); + IMGUI_BEGIN_OR_RETURN(IMGUI_LAYERS, self); + _imgui_no_anm2_path_click_check(self); - _imgui_begin_child(IMGUI_LAYERS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); - ImGui::SetScrollX(0.0f); + ImVec2 size = ImGui::GetContentRegionAvail(); - for (auto & [id, layer] : self->anm2->layers) - { - ImGui::PushID(id); + _imgui_begin_child(IMGUI_LAYERS_CHILD.copy({.size = vec2(size.x, size.y - IMGUI_FOOTER_CHILD.size->y)}), self); - ImguiItem layerItem = IMGUI_LAYER.copy - ({ - .isSelected = selectedLayerID == id, - .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), - .size = {ImGui::GetContentRegionAvail().x - (IMGUI_LAYER_SPRITESHEET_ID.size.x * 2.0f), 0}, - .id = id - }); - if (_imgui_atlas_selectable_input_text(layerItem, self, layer.name)) selectedLayerID = id; + for (auto& [id, layer] : self->anm2->layers) { + ImGui::PushID(id); - ImGui::SameLine(); + ImguiItem layerItem = IMGUI_LAYER.copy({.label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), + .size = vec2(ImGui::GetContentRegionAvail().x - (IMGUI_LAYER_SPRITESHEET_ID.size->x * 2.0f), 0), + .isSelected = selectedLayerID == id, + .id = id}); + if (_imgui_atlas_selectable_input_text(layerItem, self, layer.name)) + selectedLayerID = id; - ImguiItem spritesheetItem = IMGUI_LAYER_SPRITESHEET_ID.copy - ({ - .isSelected = selectedLayerID == id, - .label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), - .id = id - }); - _imgui_atlas_selectable_input_int(spritesheetItem, self, layer.spritesheetID); - - ImGui::PopID(); - }; + ImGui::SameLine(); - _imgui_end_child(); // layersChild - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - - if (_imgui_button(IMGUI_LAYER_ADD.copy({self->anm2->path.empty()}), self)) - selectedLayerID = anm2_layer_add(self->anm2); + ImguiItem spritesheetItem = + IMGUI_LAYER_SPRITESHEET_ID.copy({.label = std::format(IMGUI_LAYER_FORMAT, id, layer.name), .isSelected = selectedLayerID == id, .id = id}); + _imgui_atlas_selectable_input_int(spritesheetItem, self, layer.spritesheetID); - if (_imgui_button(IMGUI_LAYER_REMOVE.copy({selectedLayerID == ID_NONE}), self)) - { - anm2_layer_remove(self->anm2, selectedLayerID); - selectedLayerID = ID_NONE; - } - - _imgui_end_child(); // IMGUI_FOOTER_CHILD - _imgui_end(); // IMGUI_LAYERS + ImGui::PopID(); + }; + + _imgui_end_child(); // layersChild + + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + + if (_imgui_button(IMGUI_LAYER_ADD.copy({.isDisabled = self->anm2->path.empty()}), self)) + selectedLayerID = anm2_layer_add(self->anm2); + + if (_imgui_button(IMGUI_LAYER_REMOVE.copy({.isDisabled = selectedLayerID == ID_NONE}), self)) { + anm2_layer_remove(self->anm2, selectedLayerID); + selectedLayerID = ID_NONE; + } + + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_LAYERS } -static void _imgui_nulls(Imgui* self) -{ - static s32 selectedNullID = ID_NONE; +static void _imgui_nulls(Imgui* self) { + static int selectedNullID = ID_NONE; - IMGUI_BEGIN_OR_RETURN(IMGUI_NULLS, self); - _imgui_no_anm2_path_check(self); - - ImVec2 size = ImGui::GetContentRegionAvail(); + IMGUI_BEGIN_OR_RETURN(IMGUI_NULLS, self); + _imgui_no_anm2_path_click_check(self); - _imgui_begin_child(IMGUI_NULLS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); + ImVec2 size = ImGui::GetContentRegionAvail(); - for (auto & [id, null] : self->anm2->nulls) - { - ImGui::PushID(id); + _imgui_begin_child(IMGUI_NULLS_CHILD.copy({.size = vec2(size.x, size.y - IMGUI_FOOTER_CHILD.size->y)}), self); - ImguiItem nullItem = IMGUI_NULL.copy - ({ - .isSelected = selectedNullID == id, - .label = std::format(IMGUI_NULL_FORMAT, id, null.name), - .id = id - }); + for (auto& [id, null] : self->anm2->nulls) { + ImGui::PushID(id); - if (_imgui_atlas_selectable_input_text(nullItem, self, null.name)) selectedNullID = id; - - ImGui::PopID(); - }; + ImguiItem nullItem = IMGUI_NULL.copy({.label = std::format(IMGUI_NULL_FORMAT, id, null.name), .isSelected = selectedNullID == id, .id = id}); - _imgui_end_child(); // nullsChild - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - - if (_imgui_button(IMGUI_NULL_ADD.copy({self->anm2->path.empty()}), self)) - selectedNullID = anm2_null_add(self->anm2); + if (_imgui_atlas_selectable_input_text(nullItem, self, null.name)) + selectedNullID = id; - if (_imgui_button(IMGUI_NULL_REMOVE.copy({selectedNullID == ID_NONE}), self)) - { - anm2_null_remove(self->anm2, selectedNullID); - selectedNullID = ID_NONE; - } - - _imgui_end_child(); // IMGUI_FOOTER_CHILD - _imgui_end(); // IMGUI_NULLS + ImGui::PopID(); + }; + + _imgui_end_child(); // nullsChild + + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + + if (_imgui_button(IMGUI_NULL_ADD.copy({.isDisabled = self->anm2->path.empty()}), self)) + selectedNullID = anm2_null_add(self->anm2); + + if (_imgui_button(IMGUI_NULL_REMOVE.copy({.isDisabled = selectedNullID == ID_NONE}), self)) { + anm2_null_remove(self->anm2, selectedNullID); + selectedNullID = ID_NONE; + } + + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_NULLS } -static void _imgui_animations(Imgui* self) -{ - IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATIONS, self); - _imgui_no_anm2_path_check(self); - - ImVec2 size = ImGui::GetContentRegionAvail(); +static void _imgui_animations(Imgui* self) { + IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATIONS, self); + _imgui_no_anm2_path_click_check(self); - _imgui_begin_child(IMGUI_ANIMATIONS_CHILD.copy({.size = {size.x, size.y - IMGUI_FOOTER_CHILD.size.y}}), self); + ImVec2 size = ImGui::GetContentRegionAvail(); - for (auto & [id, animation] : self->anm2->animations) - { - ImGui::PushID(id); + _imgui_begin_child(IMGUI_ANIMATIONS_CHILD.copy({.size = vec2(size.x, size.y - IMGUI_FOOTER_CHILD.size->y)}), self); - ImguiItem animationItem = IMGUI_ANIMATION.copy - ({ - .isSelected = self->reference->animationID == id, - .label = self->anm2->defaultAnimationID == id ? std::format(IMGUI_ANIMATION_DEFAULT_FORMAT, animation.name) : animation.name, - .id = id - }); - - if (_imgui_atlas_selectable_input_text(animationItem, self, animation.name)) - { - self->reference->animationID = id; - anm2_reference_item_clear(self->reference); - } + for (auto& [id, animation] : self->anm2->animations) { + ImGui::PushID(id); - if (ImGui::IsItemHovered()) - { - self->clipboard->type = CLIPBOARD_ANIMATION; - self->clipboard->location = (s32)id; - } - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - if (ImGui::IsDragDropActive()) ImGui::SetNextItemWidth(_imgui_item_size_get(animationItem, IMGUI_SELECTABLE).x); - ImGui::SetDragDropPayload(animationItem.drag_drop_get(), &id, sizeof(s32)); - _imgui_atlas_selectable(animationItem, self); - ImGui::EndDragDropSource(); - } + ImguiItem animationItem = + IMGUI_ANIMATION.copy({.label = self->anm2->defaultAnimationID == id ? std::format(IMGUI_ANIMATION_DEFAULT_FORMAT, animation.name) : animation.name, + .isSelected = self->reference->animationID == id, + .id = id}); - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(animationItem.drag_drop_get())) - { - s32 sourceID = *(s32*)payload->Data; - if (sourceID != id) - { - imgui_snapshot(self, IMGUI_ACTION_ANIMATION_SWAP); - map_swap(self->anm2->animations, sourceID, id); - } - } + if (_imgui_atlas_selectable_input_text(animationItem, self, animation.name)) + *self->reference = {id}; - ImGui::EndDragDropTarget(); - } - - ImGui::PopID(); - }; + if (ImGui::IsItemHovered()) { + self->clipboard->type = CLIPBOARD_ANIMATION; + self->clipboard->location = (int)id; + } - _imgui_context_menu(self); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + if (ImGui::IsDragDropActive()) + ImGui::SetNextItemWidth(_imgui_item_size_get(animationItem, IMGUI_SELECTABLE).x); + ImGui::SetDragDropPayload(animationItem.drag_drop_get(), &id, sizeof(int)); + _imgui_atlas_selectable(animationItem, self); + ImGui::EndDragDropSource(); + } - _imgui_end_child(); // animationsChild - - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - - if (_imgui_button(IMGUI_ANIMATION_ADD.copy({self->anm2->path.empty()}), self)) - { - s32 id = anm2_animation_add(self->anm2); - self->reference->animationID = id; + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(animationItem.drag_drop_get())) { + int sourceID = *(int*)payload->Data; + if (sourceID != id) { + imgui_snapshot(self, IMGUI_ACTION_ANIMATION_SWAP); + map_swap(self->anm2->animations, sourceID, id); + } + } - if (self->anm2->animations.size() == 1) - self->anm2->defaultAnimationID = id; - } + ImGui::EndDragDropTarget(); + } - if (_imgui_button(IMGUI_ANIMATION_DUPLICATE.copy({!animation}), self)) - self->reference->animationID = anm2_animation_add(self->anm2, animation, self->reference->animationID); + ImGui::PopID(); + }; - _imgui_button(IMGUI_ANIMATION_MERGE.copy({!animation}), self); - - if (imgui_begin_popup_modal(IMGUI_ANIMATION_MERGE.popup, self, IMGUI_ANIMATION_MERGE.popupSize)) - { - const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); - const bool isModShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - static bool& isDeleteAnimationsAfter = self->settings->mergeIsDeleteAnimationsAfter; - static s32 lastClickedID = ID_NONE; - static s32& mergeType = self->settings->mergeType; - static size_t lastAnimationCount = 0; - static std::vector animationIDs; - static std::vector sortedIDs; - - if (self->anm2->animations.size() != lastAnimationCount) - { - sortedIDs.clear(); - for (const auto& [id, _] : self->anm2->animations) - sortedIDs.push_back(id); - std::sort(sortedIDs.begin(), sortedIDs.end()); - lastAnimationCount = self->anm2->animations.size(); - } + _imgui_context_menu(self); - _imgui_begin_child(IMGUI_MERGE_ANIMATIONS_CHILD, self); + _imgui_end_child(); // animationsChild - for (const auto& [id, animation] : self->anm2->animations) - { - ImGui::PushID(id); + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, *self->reference); - if - ( - _imgui_atlas_selectable - ( - IMGUI_ANIMATION.copy - ({ - .isSelected = std::find(animationIDs.begin(), animationIDs.end(), id) != animationIDs.end(), - .label = animation.name - }), - self - ) - ) - { - if (isModCtrl) - { - auto it = std::find(animationIDs.begin(), animationIDs.end(), id); - if (it != animationIDs.end()) - animationIDs.erase(it); - else - animationIDs.push_back(id); + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - lastClickedID = id; - } - else if (isModShift) - { - auto it1 = std::find(sortedIDs.begin(), sortedIDs.end(), lastClickedID); - auto it2 = std::find(sortedIDs.begin(), sortedIDs.end(), id); - if (it1 != sortedIDs.end() && it2 != sortedIDs.end()) - { - auto begin = std::min(it1, it2); - auto end = std::max(it1, it2); - for (auto it = begin; it <= end; ++it) - { - if (std::find(animationIDs.begin(), animationIDs.end(), *it) == animationIDs.end()) - animationIDs.push_back(*it); - } - } - } - else - { - animationIDs.clear(); - animationIDs.push_back(id); - lastClickedID = id; - } - } + if (_imgui_button(IMGUI_ANIMATION_ADD.copy({.isDisabled = self->anm2->path.empty()}), self)) { + int id = anm2_animation_add(self->anm2); + self->reference->animationID = id; - ImGui::PopID(); - } + if (self->anm2->animations.size() == 1) + self->anm2->defaultAnimationID = id; + } - _imgui_end_child(); //IMGUI_MERGE_ANIMATIONS_CHILD - - _imgui_begin_child(IMGUI_MERGE_ON_CONFLICT_CHILD, self); - _imgui_text(IMGUI_MERGE_ON_CONFLICT, self); - _imgui_radio_button(IMGUI_MERGE_APPEND_FRAMES, self, mergeType); - _imgui_radio_button(IMGUI_MERGE_REPLACE_FRAMES, self, mergeType); - _imgui_radio_button(IMGUI_MERGE_PREPEND_FRAMES, self, mergeType); - _imgui_radio_button(IMGUI_MERGE_IGNORE, self, mergeType); - _imgui_end_child(); //IMGUI_MERGE_ON_CONFLICT_CHILD - - _imgui_begin_child(IMGUI_MERGE_OPTIONS_CHILD, self); + if (_imgui_button(IMGUI_ANIMATION_DUPLICATE.copy({.isDisabled = !animation}), self)) + self->reference->animationID = anm2_animation_add(self->anm2, animation, self->reference->animationID); - _imgui_checkbox(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, self, isDeleteAnimationsAfter); + _imgui_button(IMGUI_ANIMATION_MERGE.copy({.isDisabled = !animation}), self); - _imgui_end_child(); //IMGUI_MERGE_OPTIONS_CHILD - - if (_imgui_button(IMGUI_MERGE_CONFIRM.copy({animationIDs.empty()}), self)) - { - anm2_animation_merge(self->anm2, self->reference->animationID, animationIDs, (Anm2MergeType)mergeType); + if (imgui_begin_popup_modal(IMGUI_ANIMATION_MERGE.popup, self, IMGUI_ANIMATION_MERGE.popupSize)) { + const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); + const bool isModShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); + static bool& isDeleteAnimationsAfter = self->settings->mergeIsDeleteAnimationsAfter; + static int lastClickedID = ID_NONE; + static int& mergeType = self->settings->mergeType; + static size_t lastAnimationCount = 0; + static std::vector animationIDs; + static std::vector sortedIDs; - if (isDeleteAnimationsAfter) - for (s32 id : animationIDs) - if (id != self->reference->animationID) - self->anm2->animations.erase(id); + if (self->anm2->animations.size() != lastAnimationCount) { + sortedIDs.clear(); + for (const auto& [id, _] : self->anm2->animations) + sortedIDs.push_back(id); + std::sort(sortedIDs.begin(), sortedIDs.end()); + lastAnimationCount = self->anm2->animations.size(); + } - animationIDs.clear(); - imgui_close_current_popup(self); - } + _imgui_begin_child(IMGUI_MERGE_ANIMATIONS_CHILD, self); - if (_imgui_button(IMGUI_POPUP_CANCEL, self)) - { - animationIDs.clear(); - imgui_close_current_popup(self); - } - - imgui_end_popup(self); - } + for (const auto& [id, animation] : self->anm2->animations) { + ImGui::PushID(id); - if (_imgui_button(IMGUI_ANIMATION_REMOVE.copy({!animation}), self)) - { - anm2_animation_remove(self->anm2, self->reference->animationID); - anm2_reference_clear(self->reference); - } - - if (_imgui_button(IMGUI_ANIMATION_DEFAULT.copy({!animation}), self)) - self->anm2->defaultAnimationID = self->reference->animationID; + if (_imgui_atlas_selectable(IMGUI_ANIMATION.copy({.label = animation.name, .isSelected = vector_find(animationIDs, id)}), self)) { + if (isModCtrl) { + auto it = std::find(animationIDs.begin(), animationIDs.end(), id); + if (it != animationIDs.end()) + animationIDs.erase(it); + else + animationIDs.push_back(id); - _imgui_end_child(); // IMGUI_FOOTER_CHILD - _imgui_end(); // IMGUI_ANIMATIONS + lastClickedID = id; + } else if (isModShift) { + auto it1 = std::find(sortedIDs.begin(), sortedIDs.end(), lastClickedID); + auto it2 = std::find(sortedIDs.begin(), sortedIDs.end(), id); + if (it1 != sortedIDs.end() && it2 != sortedIDs.end()) { + auto begin = std::min(it1, it2); + auto end = std::max(it1, it2); + for (auto it = begin; it <= end; ++it) { + if (std::find(animationIDs.begin(), animationIDs.end(), *it) == animationIDs.end()) + animationIDs.push_back(*it); + } + } + } else { + animationIDs.clear(); + animationIDs.push_back(id); + lastClickedID = id; + } + } + + ImGui::PopID(); + } + + _imgui_end_child(); // IMGUI_MERGE_ANIMATIONS_CHILD + + _imgui_begin_child(IMGUI_MERGE_ON_CONFLICT_CHILD, self); + _imgui_text(IMGUI_MERGE_ON_CONFLICT, self); + _imgui_radio_button(IMGUI_MERGE_APPEND_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_REPLACE_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_PREPEND_FRAMES, self, mergeType); + _imgui_radio_button(IMGUI_MERGE_IGNORE, self, mergeType); + _imgui_end_child(); // IMGUI_MERGE_ON_CONFLICT_CHILD + + _imgui_begin_child(IMGUI_MERGE_OPTIONS_CHILD, self); + + _imgui_checkbox(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, self, isDeleteAnimationsAfter); + + _imgui_end_child(); // IMGUI_MERGE_OPTIONS_CHILD + + if (_imgui_button(IMGUI_MERGE_CONFIRM.copy({.isDisabled = animationIDs.empty()}), self)) { + anm2_animation_merge(self->anm2, self->reference->animationID, animationIDs, (Anm2MergeType)mergeType); + + if (isDeleteAnimationsAfter) + for (int id : animationIDs) + if (id != self->reference->animationID) + self->anm2->animations.erase(id); + + animationIDs.clear(); + imgui_close_current_popup(self); + } + + if (_imgui_button(IMGUI_POPUP_CANCEL, self)) { + animationIDs.clear(); + imgui_close_current_popup(self); + } + + imgui_end_popup(self); + } + + if (_imgui_button(IMGUI_ANIMATION_REMOVE.copy({.isDisabled = !animation}), self)) { + anm2_animation_remove(self->anm2, self->reference->animationID); + *self->reference = Anm2Reference(); + } + + if (_imgui_button(IMGUI_ANIMATION_DEFAULT.copy({.isDisabled = !animation}), self)) + self->anm2->defaultAnimationID = self->reference->animationID; + + _imgui_end_child(); // IMGUI_FOOTER_CHILD + _imgui_end(); // IMGUI_ANIMATIONS } -static void _imgui_events(Imgui* self) -{ - static s32 selectedID = ID_NONE; - - IMGUI_BEGIN_OR_RETURN(IMGUI_EVENTS, self); - _imgui_no_anm2_path_check(self); - - ImVec2 windowSize = ImGui::GetContentRegionAvail(); +static void _imgui_events(Imgui* self) { + static int selectedID = ID_NONE; - _imgui_begin_child(IMGUI_EVENTS_CHILD.copy({.size = {windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size.y}}), self); + IMGUI_BEGIN_OR_RETURN(IMGUI_EVENTS, self); + _imgui_no_anm2_path_click_check(self); - std::function event_item = [&](s32 id, Anm2Event& event) - { - ImGui::PushID(id); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); - if (_imgui_atlas_selectable_input_text(IMGUI_EVENT.copy({.isSelected = id == selectedID, .label = event.name, .id = id}), self, event.name)) - selectedID = id; + _imgui_begin_child(IMGUI_EVENTS_CHILD.copy({.size = vec2(windowSize.x, windowSize.y - IMGUI_FOOTER_CHILD.size->y)}), self); - ImGui::PopID(); - }; + std::function event_item = [&](int id, Anm2Event& event) { + ImGui::PushID(id); - for (auto& [id, event] : self->anm2->events) - event_item(id, event); + if (_imgui_atlas_selectable_input_text(IMGUI_EVENT.copy({.label = event.name, .isSelected = id == selectedID, .id = id}), self, event.name)) + selectedID = id; - _imgui_end_child(); // eventsChild - - _imgui_begin_child(IMGUI_FOOTER_CHILD, self); - - if (_imgui_button(IMGUI_EVENTS_ADD.copy({self->anm2->path.empty()}), self)) - { - s32 id = map_next_id_get(self->anm2->events); - self->anm2->events[id] = Anm2Event{}; - selectedID = id; - } + ImGui::PopID(); + }; - if (_imgui_button(IMGUI_EVENTS_REMOVE_UNUSED.copy({self->anm2->events.empty()}), self)) - { - std::unordered_set usedEventIDs; + for (auto& [id, event] : self->anm2->events) + event_item(id, event); - for (auto& [id, animation] : self->anm2->animations) - for (auto& trigger : animation.triggers.frames) - if (trigger.eventID != ID_NONE) - usedEventIDs.insert(trigger.eventID); + _imgui_end_child(); // eventsChild - for (auto it = self->anm2->events.begin(); it != self->anm2->events.end(); ) - { - if (!usedEventIDs.count(it->first)) - it = self->anm2->events.erase(it); - else - it++; - } - } - - _imgui_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) - _imgui_end(); // IMGUI_EVENTS + _imgui_begin_child(IMGUI_FOOTER_CHILD, self); + + if (_imgui_button(IMGUI_EVENTS_ADD.copy({.isDisabled = self->anm2->path.empty()}), self)) { + int id = map_next_id_get(self->anm2->events); + self->anm2->events[id] = Anm2Event{}; + selectedID = id; + } + + if (_imgui_button(IMGUI_EVENTS_REMOVE_UNUSED.copy({.isDisabled = self->anm2->events.empty()}), self)) { + std::unordered_set usedEventIDs; + + for (auto& [id, animation] : self->anm2->animations) + for (auto& trigger : animation.triggers.frames) + if (trigger.eventID != ID_NONE) + usedEventIDs.insert(trigger.eventID); + + for (auto it = self->anm2->events.begin(); it != self->anm2->events.end();) { + if (!usedEventIDs.count(it->first)) + it = self->anm2->events.erase(it); + else + it++; + } + } + + _imgui_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) + _imgui_end(); // IMGUI_EVENTS +} +*/ + +static void _imgui_spritesheets(Imgui* self) { + IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEETS, self); + _imgui_no_anm2_path_click_check(self); + + static std::unordered_set selectedIDs; + float displayScale = self->settings->displayScale; + vec2 childSize = ImGui::GetContentRegionAvail() - *IMGUI_SPRITESHEETS_FOOTER_CHILD.size * displayScale; + + _imgui_begin_child(IMGUI_SPRITESHEETS_CHILD.copy({.size = childSize}), self); + + std::function spritesheet_draw = [&](int id, Anm2Spritesheet& spritesheet) { + ImGui::PushID(id); + vec2 itemSize = {childSize.x, IMGUI_SPRITESHEET_PREVIEW.size->y}; + _imgui_begin_child(IMGUI_SPRITESHEET_CHILD.copy({.size = itemSize}), self); + + Texture& texture = spritesheet.texture; + bool& isInvalid = texture.isInvalid; + + ImVec2 cursorPos = ImGui::GetCursorPos(); + + if (_imgui_selectable(IMGUI_SPRITESHEET.copy({.tooltip = spritesheet.path, .size = itemSize, .isSelected = selectedIDs.contains(id)}), self)) { + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) + unordered_set_id_toggle(selectedIDs, id); + else + selectedIDs = {id}; + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { + ImGui::SetDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get(), &id, sizeof(int)); + spritesheet_draw(id, spritesheet); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get())) { + int sourceID = *(int*)payload->Data; + if (sourceID != id) { + imgui_snapshot(self, IMGUI_ACTION_SPRITESHEET_SWAP); + map_swap(self->anm2->spritesheets, sourceID, id); + } + } + ImGui::EndDragDropTarget(); + } + + ImVec2 spritesheetPreviewSize = *IMGUI_SPRITESHEET_PREVIEW.size; + float spritesheetAspect = (float)texture.size.x / texture.size.y; + + if ((IMGUI_SPRITESHEET_PREVIEW.size->x / IMGUI_SPRITESHEET_PREVIEW.size->y) > spritesheetAspect) + spritesheetPreviewSize.x = IMGUI_SPRITESHEET_PREVIEW.size->y * spritesheetAspect; + else + spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW.size->x / spritesheetAspect; + + _imgui_image(IMGUI_SPRITESHEET_PREVIEW.copy({.textureID = isInvalid ? self->resources->atlas.id : spritesheet.texture.id, + .size = spritesheetPreviewSize, + .uvMin = isInvalid ? ATLAS_UV_MIN(ATLAS_NONE) : vec2(), + .uvMax = isInvalid ? ATLAS_UV_MAX(ATLAS_NONE) : vec2(1.0f), + .cursorPosition = cursorPos}), + self); + + _imgui_atlas_text(IMGUI_SPRITESHEET_TEXT.copy({.label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path), + .cursorPosition = vec2(IMGUI_SPRITESHEET_PREVIEW.size->x, + (IMGUI_SPRITESHEET_PREVIEW.size->y * 0.5f) - (ImGui::GetTextLineHeight() * 0.5f))}), + self); + + _imgui_end_child(IMGUI_SPRITESHEET_CHILD, self); + ImGui::PopID(); + }; + + for (auto& [id, spritesheet] : self->anm2->spritesheets) + spritesheet_draw(id, spritesheet); + + _imgui_end_child(IMGUI_SPRITESHEETS_CHILD, self); + + _imgui_begin_child(IMGUI_SPRITESHEETS_FOOTER_CHILD.copy({.size = *IMGUI_SPRITESHEETS_FOOTER_CHILD.size * displayScale}), self); + + if (_imgui_button(IMGUI_SPRITESHEET_ADD.copy({.isDisabled = self->anm2->path.empty()}), self)) + dialog_spritesheet_add(self->dialog); + + if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD) { + _imgui_spritesheet_add(self, self->dialog->path); + dialog_reset(self->dialog); + } + + if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({.isDisabled = selectedIDs.empty()}), self)) { + for (auto& id : selectedIDs) { + WorkingDirectory workingDirectory(self->anm2->path); + Texture texture; + texture_from_path_init(&texture, self->anm2->spritesheets[id].path); + self->anm2->spritesheets[id].texture = texture; + } + imgui_log_push(self, IMGUI_LOG_RELOAD_SPRITESHEET); + } + + if (_imgui_button(IMGUI_SPRITESHEETS_REPLACE.copy({.isDisabled = selectedIDs.size() > 1}), self)) + dialog_spritesheet_replace(self->dialog, *selectedIDs.begin()); + + if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_REPLACE) { + + imgui_snapshot(self, IMGUI_ACTION_REPLACE_SPRITESHEET); + + WorkingDirectory workingDirectory(self->anm2->path); + std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path); + std::string spritesheetPath = std::filesystem::relative(self->dialog->path, anm2WorkingPath).string(); + self->anm2->spritesheets[self->dialog->replaceID].path = spritesheetPath; + Texture texture; + texture_from_path_init(&texture, spritesheetPath); + self->anm2->spritesheets[self->dialog->replaceID].texture = texture; + dialog_reset(self->dialog); + } + + if (_imgui_button(IMGUI_SPRITESHEETS_REMOVE_UNUSED.copy({.isDisabled = self->anm2->spritesheets.empty()}), self)) { + std::unordered_set usedSpritesheetIDs; + + for (auto& [layerID, layer] : self->anm2->layers) + if (layer.spritesheetID != ID_NONE) + usedSpritesheetIDs.insert(layer.spritesheetID); + + for (auto it = self->anm2->spritesheets.begin(); it != self->anm2->spritesheets.end();) { + if (!usedSpritesheetIDs.count(it->first)) { + texture_free(&self->anm2->spritesheets[it->first].texture); + it = self->anm2->spritesheets.erase(it); + } else + it++; + } + } + + if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_ALL.copy({.isDisabled = selectedIDs.size() == self->anm2->spritesheets.size()}), self)) + for (auto [id, _] : self->anm2->spritesheets) + selectedIDs.insert(id); + + if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_NONE.copy({.isDisabled = selectedIDs.empty()}), self)) + selectedIDs.clear(); + + if (_imgui_button(IMGUI_SPRITESHEET_SAVE.copy({.isDisabled = selectedIDs.empty()}), self)) { + for (auto& id : selectedIDs) { + WorkingDirectory workingDirectory(self->anm2->path); + Anm2Spritesheet& spritesheet = self->anm2->spritesheets[id]; + texture_from_gl_write(&spritesheet.texture, spritesheet.path); + imgui_log_push(self, std::format(IMGUI_LOG_SPRITESHEET_SAVE_FORMAT, id, spritesheet.path)); + } + } + + _imgui_end_child(IMGUI_SPRITESHEETS_FOOTER_CHILD, self); + _imgui_end(); // IMGUI_SPRITESHEETS } -static void _imgui_spritesheets(Imgui* self) -{ - static std::unordered_set selectedIDs; - static s32 highlightedID = ID_NONE; +/* +static void _imgui_animation_preview(Imgui* self) { + static int& tool = self->settings->tool; + static float& zoom = self->settings->previewZoom; + static vec2& pan = self->settings->previewPan; + static ivec2& size = self->preview->canvas.size; + static vec2 mousePos{}; - IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEETS, self); - _imgui_no_anm2_path_check(self); + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y); - ImVec2 windowSize = ImGui::GetContentRegionAvail(); + IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATION_PREVIEW, self); - _imgui_begin_child(IMGUI_SPRITESHEETS_CHILD.copy({.size = {windowSize.x, windowSize.y - IMGUI_SPRITESHEETS_FOOTER_CHILD.size.y}}), self); - - std::function spritesheet_item = [&](s32 id, Anm2Spritesheet& spritesheet) - { - ImGui::PushID(id); - - Texture& texture = spritesheet.texture; - bool isContains = selectedIDs.contains(id); - - _imgui_begin_child(IMGUI_SPRITESHEET_CHILD, self); + _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->previewIsGrid); + ImGui::SameLine(); + _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->previewGridColor); + _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, self->settings->previewGridSize); + _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, self->settings->previewGridOffset); + _imgui_end_child(); // IMGUI_CANVAS_GRID_CHILD - if (_imgui_checkbox(IMGUI_SPRITESHEET_SELECTED, self, isContains)) - { - if (isContains) - selectedIDs.insert(id); - else - selectedIDs.erase(id); - } + ImGui::SameLine(); - if (_imgui_atlas_selectable(IMGUI_SPRITESHEET.copy({.isSelected = id == highlightedID, .label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path)}), self)) - highlightedID = id; + _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); + _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); + if (_imgui_button(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW.copy({.isDisabled = pan == vec2()}), self)) + pan = vec2(); + if (_imgui_button(IMGUI_ANIMATION_PREVIEW_FIT.copy({.isDisabled = self->reference->animationID == ID_NONE}), self)) { + vec4 rect = anm2_animation_rect_get(self->anm2, *self->reference, self->settings->previewIsRootTransform); - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get(), &id, sizeof(s32)); - spritesheet_item(id, spritesheet); - ImGui::EndDragDropSource(); - } + if (rect != vec4(-1.0f) && (rect.z > 0 && rect.w > 0)) { + float scaleX = self->preview->canvas.size.x / rect.z; + float scaleY = self->preview->canvas.size.y / rect.w; + float fitScale = std::min(scaleX, scaleY); - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_SPRITESHEET.drag_drop_get())) - { - s32 sourceID = *(s32*)payload->Data; - if (sourceID != id) - map_swap(self->anm2->spritesheets, sourceID, id); - } - ImGui::EndDragDropTarget(); - } + zoom = UNIT_TO_PERCENT(fitScale); - ImVec2 spritesheetPreviewSize = IMGUI_SPRITESHEET_PREVIEW_SIZE; - f32 spritesheetAspect = (f32)texture.size.x / texture.size.y; + vec2 rectCenter = {rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f}; + pan = -rectCenter * fitScale; + } + } + ImGui::Text("%s", mousePositionString.c_str()); + _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD - if ((IMGUI_SPRITESHEET_PREVIEW_SIZE.x / IMGUI_SPRITESHEET_PREVIEW_SIZE.y) > spritesheetAspect) - spritesheetPreviewSize.x = IMGUI_SPRITESHEET_PREVIEW_SIZE.y * spritesheetAspect; - else - spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW_SIZE.x / spritesheetAspect; + ImGui::SameLine(); - if (texture.isInvalid) - _imgui_atlas(ATLAS_NONE, self); - else - ImGui::Image(texture.id, spritesheetPreviewSize); - - _imgui_end_child(); // IMGUI_SPRITESHEET_CHILD + _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); + _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->previewBackgroundColor); - ImGui::PopID(); - }; - - for (auto& [id, spritesheet] : self->anm2->spritesheets) - spritesheet_item(id, spritesheet); + std::vector animationIDs; + ImguiItem animationOverlayItem = IMGUI_CANVAS_ANIMATION_OVERLAY; - _imgui_end_child(); // spritesheetsChild - - _imgui_begin_child(IMGUI_SPRITESHEETS_FOOTER_CHILD, self); - - if (_imgui_button(IMGUI_SPRITESHEET_ADD.copy({self->anm2->path.empty()}), self)) - dialog_spritesheet_add(self->dialog); + animationIDs.emplace_back(ID_NONE); + animationOverlayItem.items.emplace_back(IMGUI_NONE); - if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_ADD) - { - _imgui_spritesheet_add(self, self->dialog->path); - dialog_reset(self->dialog); - } - - if (_imgui_button(IMGUI_SPRITESHEETS_RELOAD.copy({selectedIDs.empty()}), self)) - { - for (auto& id : selectedIDs) - { - std::filesystem::path workingPath = std::filesystem::current_path(); - working_directory_from_file_set(self->anm2->path); - Texture texture; - texture_from_path_init(&texture, self->anm2->spritesheets[id].path); - self->anm2->spritesheets[id].texture = texture; - std::filesystem::current_path(workingPath); - } + for (auto& [id, animation] : self->anm2->animations) { + animationIDs.emplace_back(id); + animationOverlayItem.items.emplace_back(animation.name); + } - imgui_log_push(self, IMGUI_LOG_RELOAD_SPRITESHEET); - } + int animationIndex = 0; - if (_imgui_button(IMGUI_SPRITESHEETS_REPLACE.copy({highlightedID == ID_NONE}), self)) - dialog_spritesheet_replace(self->dialog, highlightedID); + if (self->preview->animationOverlayID != ID_NONE) + animationIndex = std::find(animationIDs.begin(), animationIDs.end(), self->preview->animationOverlayID) - animationIDs.begin(); - if (self->dialog->isSelected && self->dialog->type == DIALOG_SPRITESHEET_REPLACE) - { - imgui_snapshot(self, IMGUI_ACTION_REPLACE_SPRITESHEET); - - std::filesystem::path workingPath = std::filesystem::current_path(); - std::string anm2WorkingPath = working_directory_from_file_set(self->anm2->path); - std::string spritesheetPath = std::filesystem::relative(self->dialog->path, anm2WorkingPath).string(); - - self->anm2->spritesheets[self->dialog->replaceID].path = spritesheetPath; - Texture texture; - texture_from_path_init(&texture, spritesheetPath); - self->anm2->spritesheets[self->dialog->replaceID].texture = texture; - dialog_reset(self->dialog); - std::filesystem::current_path(workingPath); - } + if (_imgui_combo(animationOverlayItem, self, &animationIndex)) + self->preview->animationOverlayID = animationIDs[animationIndex]; - if (_imgui_button(IMGUI_SPRITESHEETS_REMOVE_UNUSED.copy({self->anm2->spritesheets.empty()}), self)) - { - std::unordered_set usedSpritesheetIDs; + _imgui_drag_float(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, self, self->settings->previewOverlayTransparency); + _imgui_end_child(); // IMGUI_CANVAS_VISUAL_CHILD - for (auto& [layerID, layer] : self->anm2->layers) - if (layer.spritesheetID != ID_NONE) - usedSpritesheetIDs.insert(layer.spritesheetID); + ImGui::SameLine(); - for (auto it = self->anm2->spritesheets.begin(); it != self->anm2->spritesheets.end(); ) - { - if (!usedSpritesheetIDs.count(it->first)) - { - texture_free(&self->anm2->spritesheets[it->first].texture); - it = self->anm2->spritesheets.erase(it); - } - else - it++; - } - } + _imgui_begin_child(IMGUI_CANVAS_HELPER_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_AXES, self, self->settings->previewIsAxes); + ImGui::SameLine(); + _imgui_color_edit4(IMGUI_CANVAS_AXES_COLOR, self, self->settings->previewAxesColor); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_ALT_ICONS, self, self->settings->previewIsAltIcons); + _imgui_checkbox(IMGUI_CANVAS_ROOT_TRANSFORM, self, self->settings->previewIsRootTransform); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_TRIGGERS, self, self->settings->previewIsTriggers); + _imgui_checkbox(IMGUI_CANVAS_PIVOTS, self, self->settings->previewIsPivots); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_ICONS, self, self->settings->previewIsIcons); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->previewIsBorder); + _imgui_end_child(); // IMGUI_CANVAS_HELPER_CHILD - if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_ALL.copy({selectedIDs.size() == self->anm2->spritesheets.size()}), self)) - for (auto [id, _] : self->anm2->spritesheets) - selectedIDs.insert(id); + ImVec2 previewCursorScreenPos = ImGui::GetCursorScreenPos(); - if (_imgui_button(IMGUI_SPRITESHEETS_SELECT_NONE.copy({selectedIDs.empty()}), self)) - selectedIDs.clear(); + if (!self->preview->isRender) + size = ivec2(vec2(ImGui::GetContentRegionAvail())); - if (_imgui_button(IMGUI_SPRITESHEET_SAVE.copy({selectedIDs.empty()}), self)) - { - for (auto& id : selectedIDs) - { - Anm2Spritesheet& spritesheet = self->anm2->spritesheets[id]; - std::filesystem::path workingPath = std::filesystem::current_path(); - working_directory_from_file_set(self->anm2->path); - texture_from_gl_write(&spritesheet.texture, spritesheet.path); - imgui_log_push(self, std::format(IMGUI_LOG_SPRITESHEET_SAVE_FORMAT, id, spritesheet.path)); - std::filesystem::current_path(workingPath); - } - } + preview_draw(self->preview); + ImGui::Image(self->preview->canvas.framebuffer, vec2(size)); - if (_imgui_is_no_click_on_item()) highlightedID = ID_NONE; + if (self->settings->previewIsTriggers) { + Anm2Frame trigger; + anm2_frame_from_time(self->anm2, &trigger, {self->reference->animationID, ANM2_TRIGGER}, self->preview->time); - _imgui_end_child(); //IMGUI_SPRITESHEETS_FOOTER_CHILD - _imgui_end(); // IMGUI_SPRITESHEETS + if (trigger.eventID != ID_NONE) { + ImVec2 textPos = previewCursorScreenPos + ImGui::GetStyle().ItemSpacing; + ImGui::PushFont(NULL, ImGui::GetStyle().FontSizeBase * IMGUI_TRIGGERS_FONT_SCALE); + ImGui::GetWindowDrawList()->AddText(textPos, IMGUI_TRIGGERS_EVENT_COLOR, self->anm2->events[trigger.eventID].name.c_str()); + ImGui::PopFont(); + } + } + + if (ImGui::IsItemHovered()) { + self->pendingCursor = TOOL_CURSORS[tool]; + imgui_keyboard_nav_disable(); + } else { + _imgui_end(); // IMGUI_ANIMATION_EDITOR + imgui_keyboard_nav_enable(); + return; + } + + _imgui_end(); // IMGUI_ANIMATION_PREVIEW + + mousePos = vec2(ImGui::GetMousePos() - previewCursorScreenPos - pan - (vec2(size) * 0.5f)) / PERCENT_TO_UNIT(zoom); + + const bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); + const bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); + const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); + const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); + const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); + const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); + const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); + const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; + const float mouseWheel = ImGui::GetIO().MouseWheel; + + Anm2Frame* frame = nullptr; + + if (self->reference->itemType != ANM2_TRIGGER) + frame = anm2_frame_from_reference(self->anm2, *self->reference); + + float step = isMod ? TOOL_STEP_MOD : TOOL_STEP; + + if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + pan += vec2(mouseDelta.x, mouseDelta.y); + + switch (tool) { + case TOOL_MOVE: + if (!frame) + break; + + if (isMouseClick || isLeft || isRight || isUp || isDown) + imgui_snapshot(self, IMGUI_ACTION_MOVE); + + if (isMouseDown) + frame->position = vec2(mousePos); + else { + if (isLeft) + frame->position.x -= step; + if (isRight) + frame->position.x += step; + if (isUp) + frame->position.y -= step; + if (isDown) + frame->position.y += step; + } + break; + case TOOL_ROTATE: + if (!frame) + break; + + if (isMouseClick || isLeft || isRight || isUp || isDown) + imgui_snapshot(self, IMGUI_ACTION_ROTATE); + + if (isMouseDown) + frame->rotation += mouseDelta.x; + else { + if (isLeft || isUp) + frame->rotation -= step; + if (isRight || isDown) + frame->rotation += step; + } + break; + case TOOL_SCALE: + if (!frame) + break; + + if (isMouseClick || isLeft || isRight || isUp || isDown) + imgui_snapshot(self, IMGUI_ACTION_SCALE); + + if (isMouseDown) + frame->scale += vec2(mouseDelta.x, mouseDelta.y); + else { + if (isLeft) + frame->scale.x -= step; + if (isRight) + frame->scale.x += step; + if (isUp) + frame->scale.y -= step; + if (isDown) + frame->scale.y += step; + } + break; + default: + break; + } + + if (mouseWheel != 0 || isZoomIn || isZoomOut) { + float delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + } } -static void _imgui_animation_preview(Imgui* self) -{ - static s32& tool = self->settings->tool; - static f32& zoom = self->settings->previewZoom; - static vec2& pan = self->settings->previewPan; - static ivec2& size = self->preview->canvas.size; - static vec2 mousePos{}; - static vec2 previewPos{}; +static void _imgui_spritesheet_editor(Imgui* self) { + static vec2 mousePos = {0, 0}; + static int& tool = self->settings->tool; + static vec4& toolColor = self->settings->toolColor; + static ivec2& gridSize = self->settings->editorGridSize; + static ivec2& gridOffset = self->settings->editorGridOffset; + static vec2& pan = self->settings->editorPan; + static float& zoom = self->settings->editorZoom; + static ivec2& size = self->editor->canvas.size; - std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); - - IMGUI_BEGIN_OR_RETURN(IMGUI_ANIMATION_PREVIEW, self); + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y); - _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); - _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->previewIsGrid); - ImGui::SameLine(); - _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->previewGridColor); - _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, self->settings->previewGridSize); - _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, self->settings->previewGridOffset); - _imgui_end_child(); // IMGUI_CANVAS_GRID_CHILD - - ImGui::SameLine(); - - _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); - _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); - if (_imgui_button(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); - if (_imgui_button(IMGUI_ANIMATION_PREVIEW_FIT.copy({self->reference->animationID == ID_NONE}), self)) - { - vec4 rect = anm2_animation_rect_get(self->anm2, self->reference, self->settings->previewIsRootTransform); - - if (rect != vec4(-1.0f) && (rect.z > 0 && rect.w > 0)) - { - f32 scaleX = self->preview->canvas.size.x / rect.z; - f32 scaleY = self->preview->canvas.size.y / rect.w; - f32 fitScale = std::min(scaleX, scaleY); + IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEET_EDITOR, self); - zoom = UNIT_TO_PERCENT(fitScale); + _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); + _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->editorIsGrid); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_GRID_SNAP, self, self->settings->editorIsGridSnap); + ImGui::SameLine(); + _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->editorGridColor); + _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, gridSize); + _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, gridOffset); + _imgui_end_child(); - vec2 rectCenter = { rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f }; - pan = -rectCenter * fitScale; - } - } - ImGui::Text(mousePositionString.c_str()); - _imgui_end_child(); //IMGUI_CANVAS_VIEW_CHILD + ImGui::SameLine(); - ImGui::SameLine(); - - _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); - _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->previewBackgroundColor); - - std::vector animationIDs; - ImguiItem animationOverlayItem = IMGUI_CANVAS_ANIMATION_OVERLAY; + _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); + _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); + if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW.copy({.isDisabled = pan == vec2()}), self)) + pan = vec2(); + if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_FIT.copy({.isDisabled = self->editor->spritesheetID == ID_NONE}), self)) { + vec4 rect = {0, 0, self->anm2->spritesheets[self->editor->spritesheetID].texture.size.x, + self->anm2->spritesheets[self->editor->spritesheetID].texture.size.y}; - animationIDs.emplace_back(ID_NONE); - animationOverlayItem.items.emplace_back(IMGUI_NONE); + if ((rect.z > 0 && rect.w > 0)) { + float scaleX = self->editor->canvas.size.x / rect.z; + float scaleY = self->editor->canvas.size.y / rect.w; + float fitScale = std::min(scaleX, scaleY); - for (auto& [id, animation] : self->anm2->animations) - { - animationIDs.emplace_back(id); - animationOverlayItem.items.emplace_back(animation.name); - } + zoom = UNIT_TO_PERCENT(fitScale); + pan = {}; + } + } + ImGui::Text("%s", mousePositionString.c_str()); + _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD - s32 animationIndex = 0; + ImGui::SameLine(); - if (self->preview->animationOverlayID != ID_NONE) - animationIndex = std::find(animationIDs.begin(), animationIDs.end(), self->preview->animationOverlayID) - animationIDs.begin(); - - if (_imgui_combo(animationOverlayItem, self, &animationIndex)) - self->preview->animationOverlayID = animationIDs[animationIndex]; + _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); + _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->editorBackgroundColor); + _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->editorIsBorder); + _imgui_end_child(); // IMGUI_CANVAS_VISUAL_CHILD - _imgui_drag_float(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, self, self->settings->previewOverlayTransparency); - _imgui_end_child(); //IMGUI_CANVAS_VISUAL_CHILD + ImVec2 editorCursorScreenPos = ImGui::GetCursorScreenPos(); + size = ivec2(vec2(ImGui::GetContentRegionAvail())); + editor_draw(self->editor); + ImGui::Image(self->editor->canvas.framebuffer, vec2(size)); - ImGui::SameLine(); + if (ImGui::IsItemHovered()) { + self->pendingCursor = TOOL_CURSORS[tool]; + imgui_keyboard_nav_disable(); + } else { + _imgui_end(); // IMGUI_SPRITESHEET_EDITOR + imgui_keyboard_nav_enable(); + return; + } - _imgui_begin_child(IMGUI_CANVAS_HELPER_CHILD, self); - _imgui_checkbox(IMGUI_CANVAS_AXES, self, self->settings->previewIsAxes); - ImGui::SameLine(); - _imgui_color_edit4(IMGUI_CANVAS_AXES_COLOR, self, self->settings->previewAxesColor); - ImGui::SameLine(); - _imgui_checkbox(IMGUI_CANVAS_ALT_ICONS, self, self->settings->previewIsAltIcons); - _imgui_checkbox(IMGUI_CANVAS_ROOT_TRANSFORM, self, self->settings->previewIsRootTransform); - ImGui::SameLine(); - _imgui_checkbox(IMGUI_CANVAS_TRIGGERS, self, self->settings->previewIsTriggers); - _imgui_checkbox(IMGUI_CANVAS_PIVOTS, self, self->settings->previewIsPivots); - ImGui::SameLine(); - _imgui_checkbox(IMGUI_CANVAS_ICONS, self, self->settings->previewIsIcons); - ImGui::SameLine(); - _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->previewIsBorder); - _imgui_end_child(); // IMGUI_CANVAS_HELPER_CHILD + _imgui_end(); // IMGUI_SPRITESHEET_EDITOR - ImVec2 previewCursorScreenPos = ImGui::GetCursorScreenPos(); - - if (!self->preview->isRender) size = ivec2(vec2(ImGui::GetContentRegionAvail())); - - preview_draw(self->preview); - ImGui::Image(self->preview->canvas.framebuffer, vec2(size)); - - if (self->settings->previewIsTriggers) - { - Anm2Frame trigger; - anm2_frame_from_time(self->anm2, &trigger, {self->reference->animationID, ANM2_TRIGGERS}, self->preview->time); + mousePos = vec2(ImGui::GetMousePos() - editorCursorScreenPos - pan) / PERCENT_TO_UNIT(zoom); - if (trigger.eventID != ID_NONE) - { - f32 textScale = ImGui::GetCurrentWindow()->FontWindowScale; - ImVec2 textPos = previewCursorScreenPos + ImGui::GetStyle().ItemSpacing; - ImGui::SetWindowFontScale(IMGUI_TRIGGERS_FONT_SCALE); - ImGui::GetWindowDrawList()->AddText(textPos, IMGUI_TRIGGERS_EVENT_COLOR, self->anm2->events[trigger.eventID].name.c_str()); - ImGui::SetWindowFontScale(textScale); - } - } + const bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); + const bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); + const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); + const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); + const bool isShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); + const bool isCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); + const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + const bool isMouseRightClick = ImGui::IsMouseClicked(ImGuiMouseButton_Right); + const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + const bool isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right); + const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); + const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); + const float mouseWheel = ImGui::GetIO().MouseWheel; + const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - if (ImGui::IsItemHovered()) - { - self->pendingCursor = TOOL_CURSORS[tool]; - imgui_keyboard_nav_disable(); - } - else - { - _imgui_end(); // IMGUI_ANIMATION_EDITOR - imgui_keyboard_nav_enable(); - return; - } + Anm2Frame* frame = nullptr; + if (self->reference->itemType == ANM2_LAYER) + frame = anm2_frame_from_reference(self->anm2, *self->reference); - _imgui_end(); // IMGUI_ANIMATION_PREVIEW + Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->editor->spritesheetID); + Texture* texture = spritesheet ? &spritesheet->texture : nullptr; - mousePos = vec2(ImGui::GetMousePos() - previewCursorScreenPos - pan - (vec2(size) * 0.5f)) / PERCENT_TO_UNIT(zoom); - - const bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); - const bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); - const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); - const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); - const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); - const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); - const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); - const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); - const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); - const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - const f32 mouseWheel = ImGui::GetIO().MouseWheel; - - Anm2Frame* frame = nullptr; - - if (self->reference->itemType != ANM2_TRIGGERS) - frame = anm2_frame_from_reference(self->anm2, self->reference); + vec2 position = mousePos; + float step = isShift ? TOOL_STEP_MOD : TOOL_STEP; - f32 step = isMod ? TOOL_STEP_MOD : TOOL_STEP; - - if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) - pan += vec2(mouseDelta.x, mouseDelta.y); + if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + pan += vec2(mouseDelta.x, mouseDelta.y); - switch (tool) - { - case TOOL_MOVE: - if (!frame) break; - - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_snapshot(self, IMGUI_ACTION_MOVE); - - if (isMouseDown) - frame->position = vec2(mousePos); - else - { - if (isLeft) frame->position.x -= step; - if (isRight) frame->position.x += step; - if (isUp) frame->position.y -= step; - if (isDown) frame->position.y += step; - } - break; - case TOOL_ROTATE: - if (!frame) break; + auto spritesheet_editor_move = [&](bool isPrimary = true) { + bool isActivated = isPrimary ? isMouseClick || isLeft || isRight || isUp || isDown : isMouseRightClick; + bool isUseMouseDown = isPrimary ? isMouseDown : isMouseRightDown; - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_snapshot(self, IMGUI_ACTION_ROTATE); - - if (isMouseDown) - frame->rotation += mouseDelta.x; - else - { - if (isLeft || isUp) frame->rotation -= step; - if (isRight || isDown) frame->rotation += step; - } - break; - case TOOL_SCALE: - if (!frame) break; + if (isActivated) + imgui_snapshot(self, IMGUI_ACTION_MOVE); - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_snapshot(self, IMGUI_ACTION_SCALE); - - if (isMouseDown) - frame->scale += vec2(mouseDelta.x, mouseDelta.y); - else - { - if (isLeft) frame->scale.x -= step; - if (isRight) frame->scale.x += step; - if (isUp) frame->scale.y -= step; - if (isDown) frame->scale.y += step; - } - break; - default: - break; - } + if (isUseMouseDown) + frame->pivot = position - frame->crop; + else if (isPrimary) { + if (isLeft) + frame->pivot.x -= step; + if (isRight) + frame->pivot.x += step; + if (isUp) + frame->pivot.y -= step; + if (isDown) + frame->pivot.y += step; + } + }; - if (mouseWheel != 0 || isZoomIn || isZoomOut) - { - f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); - } + auto spritesheet_editor_crop = [&](bool isPrimary = true) { + bool isActivated = isPrimary ? isMouseClick || isLeft || isRight || isUp || isDown : isMouseRightClick; + bool isUseMouseClick = isPrimary ? isMouseClick : isMouseRightClick; + bool isUseMouseDown = isPrimary ? isMouseDown : isMouseRightDown; + + vec2 cropPosition = self->settings->editorIsGridSnap ? vec2(roundf(position.x / gridSize.x) * gridSize.x + gridOffset.x - (gridSize.x * 0.5f), + roundf(position.y / gridSize.y) * gridSize.y + gridOffset.y - (gridSize.y * 0.5f)) + : position; + + if (isActivated) + imgui_snapshot(self, IMGUI_ACTION_MOVE); + + if (isUseMouseClick) { + frame->crop = cropPosition; + frame->size = ivec2(); + } else if (isUseMouseDown) + frame->size = position - frame->crop; + else if (isPrimary) { + if (isCtrl) { + if (isLeft) + frame->crop.x -= step; + if (isRight) + frame->crop.x += step; + if (isUp) + frame->crop.y -= step; + if (isDown) + frame->crop.y += step; + } else { + if (isLeft) + frame->size.x -= step; + if (isRight) + frame->size.x += step; + if (isUp) + frame->size.y -= step; + if (isDown) + frame->size.y += step; + } + + frame->size.x = std::max({}, frame->size.x); + frame->size.y = std::max({}, frame->size.y); + } + }; + + auto spritesheet_editor_draw = [&](vec4 color) { + if (isMouseClick || isMouseRightClick) + imgui_snapshot(self, color != vec4() ? IMGUI_ACTION_DRAW : IMGUI_ACTION_ERASE); + + if (isMouseDown || isMouseRightDown) + texture_pixel_set(texture, position, color); + }; + + switch (tool) { + case TOOL_MOVE: + if (!texture || !frame) + break; + spritesheet_editor_move(true); + spritesheet_editor_crop(false); + break; + case TOOL_CROP: + if (!texture || !frame) + break; + spritesheet_editor_crop(true); + spritesheet_editor_move(false); + break; + case TOOL_DRAW: + case TOOL_ERASE: + if (!texture) + break; + spritesheet_editor_draw(tool == TOOL_DRAW && !isMouseRightDown ? self->settings->toolColor : vec4()); + break; + case TOOL_COLOR_PICKER: + if (isMouseDown) { + SDL_GetMouseState(&mousePos.x, &mousePos.y); + + ImGuiIO& io = ImGui::GetIO(); + ivec2 fbPosition = {(int)(position.x * io.DisplayFramebufferScale.x), (int)(position.y * io.DisplayFramebufferScale.y)}; + ivec2 size{}; + SDL_GetWindowSizeInPixels(self, &size.x, &size.y); + + if (fbPosition.x < 0 || fbPosition.y < 0 || fbPosition.x >= size.x || fbPosition.y >= size.y) + return false; + + uint8_t rgba[4]; + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(fbPosition.x, size.y - 1 - fbPosition.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + + color = vec4(UINT8_TO_FLOAT(rgba[0]), UINT8_TO_FLOAT(rgba[1]), UINT8_TO_FLOAT(rgba[2]), UINT8_TO_FLOAT(rgba[3])); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); + ImGui::BeginTooltip(); + _imgui_color_button(IMGUI_COLOR_PICKER_BUTTON, self, toolColor); + ImGui::EndTooltip(); + ImGui::PopStyleVar(); + } + break; + default: + break; + } + + if (mouseWheel != 0 || isZoomIn || isZoomOut) { + float delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + } } -static void _imgui_spritesheet_editor(Imgui* self) -{ - static vec2 mousePos = {0, 0}; - static s32& tool = self->settings->tool; - static vec4& toolColor = self->settings->toolColor; - static ivec2& gridSize = self->settings->editorGridSize; - static ivec2& gridOffset = self->settings->editorGridOffset; - static vec2& pan = self->settings->editorPan; - static f32& zoom = self->settings->editorZoom; - static ivec2& size = self->editor->canvas.size; - - std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); +static void _imgui_frame_properties(Imgui* self) { + static Anm2Type& type = self->reference->itemType; - IMGUI_BEGIN_OR_RETURN(IMGUI_SPRITESHEET_EDITOR, self); - - _imgui_begin_child(IMGUI_CANVAS_GRID_CHILD, self); - _imgui_checkbox(IMGUI_CANVAS_GRID, self, self->settings->editorIsGrid); - ImGui::SameLine(); - _imgui_checkbox(IMGUI_CANVAS_GRID_SNAP, self, self->settings->editorIsGridSnap); - ImGui::SameLine(); - _imgui_color_edit4(IMGUI_CANVAS_GRID_COLOR, self, self->settings->editorGridColor); - _imgui_input_int2(IMGUI_CANVAS_GRID_SIZE, self, gridSize); - _imgui_input_int2(IMGUI_CANVAS_GRID_OFFSET, self, gridOffset); - _imgui_end_child(); - - ImGui::SameLine(); - - _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); - _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); - if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); - if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_FIT.copy({self->editor->spritesheetID == ID_NONE}), self)) - { - vec4 rect = {0, 0, self->anm2->spritesheets[self->editor->spritesheetID].texture.size.x, - self->anm2->spritesheets[self->editor->spritesheetID].texture.size.y}; + IMGUI_BEGIN_OR_RETURN(IMGUI_FRAME_PROPERTIES, self); - if ((rect.z > 0 && rect.w > 0)) - { - f32 scaleX = self->editor->canvas.size.x / rect.z; - f32 scaleY = self->editor->canvas.size.y / rect.w; - f32 fitScale = std::min(scaleX, scaleY); + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *self->reference); - zoom = UNIT_TO_PERCENT(fitScale); - pan = {}; - } - } - ImGui::Text(mousePositionString.c_str()); - _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD - - ImGui::SameLine(); - - _imgui_begin_child(IMGUI_CANVAS_VISUAL_CHILD, self); - _imgui_color_edit4(IMGUI_CANVAS_BACKGROUND_COLOR, self, self->settings->editorBackgroundColor); - _imgui_checkbox(IMGUI_CANVAS_BORDER, self, self->settings->editorIsBorder); - _imgui_end_child(); // IMGUI_CANVAS_VISUAL_CHILD - - ImVec2 editorCursorScreenPos = ImGui::GetCursorScreenPos(); - size = ivec2(vec2(ImGui::GetContentRegionAvail())); - editor_draw(self->editor); - ImGui::Image(self->editor->canvas.framebuffer, vec2(size)); - - if (ImGui::IsItemHovered()) - { - self->pendingCursor = TOOL_CURSORS[tool]; - imgui_keyboard_nav_disable(); - } - else - { - _imgui_end(); // IMGUI_SPRITESHEET_EDITOR - imgui_keyboard_nav_enable(); - return; - } + bool isLayerFrame = frame && type == ANM2_LAYER; - _imgui_end(); // IMGUI_SPRITESHEET_EDITOR + if (!frame || type != ANM2_TRIGGER) { + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({.isDisabled = !isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->crop); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({.isDisabled = !isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->size); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->position); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({.isDisabled = !isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->pivot); + _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->scale); + _imgui_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->rotation); + _imgui_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->delay); + _imgui_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->tintRGBA); + _imgui_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->offsetRGB); + _imgui_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->isVisible); + _imgui_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({.isDisabled = !frame}), self, !frame ? dummy_value() : frame->isInterpolated); + _imgui_checkbox(IMGUI_FRAME_PROPERTIES_ROUND.copy({.isDisabled = !frame}), self, self->settings->propertiesIsRound); + if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_X.copy({.isDisabled = !frame}), self)) + frame->scale.x = -frame->scale.x; + if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_Y.copy({.isDisabled = !frame}), self)) + frame->scale.y = -frame->scale.y; - mousePos = vec2(ImGui::GetMousePos() - editorCursorScreenPos - pan) / PERCENT_TO_UNIT(zoom); + if (self->settings->propertiesIsRound && frame) { + frame->position = glm::trunc(frame->position); + frame->pivot = glm::trunc(frame->pivot); + frame->crop = glm::trunc(frame->crop); + frame->scale = glm::trunc(frame->scale); + frame->rotation = glm::trunc(frame->rotation); + } + } else { + std::vector eventIDs; + ImguiItem eventItem = IMGUI_FRAME_PROPERTIES_EVENT.copy({.isDisabled = !frame}); - const bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); - const bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); - const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); - const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); - const bool isShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - const bool isCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); - const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); - const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); - const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); - const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); - const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); - const f32 mouseWheel = ImGui::GetIO().MouseWheel; - const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - - Anm2Frame* frame = nullptr; - if (self->reference->itemType == ANM2_LAYER) - frame = anm2_frame_from_reference(self->anm2, self->reference); + eventIDs.emplace_back(ID_NONE); + eventItem.items.emplace_back(IMGUI_NONE); - Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->editor->spritesheetID); - Texture* texture = spritesheet ? &spritesheet->texture : nullptr; + for (auto& [id, event] : self->anm2->events) { + eventIDs.emplace_back(id); + eventItem.items.emplace_back(event.name); + } - vec2 position = mousePos; - f32 step = isShift ? TOOL_STEP_MOD : TOOL_STEP; - - if ((tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) - pan += vec2(mouseDelta.x, mouseDelta.y); - - switch (tool) - { - case TOOL_MOVE: - if (!texture || !frame) break; + int eventIndex = std::find(eventIDs.begin(), eventIDs.end(), frame->eventID) - eventIDs.begin(); - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_snapshot(self, IMGUI_ACTION_MOVE); + if (_imgui_combo(eventItem, self, &eventIndex)) + frame->eventID = eventIDs[eventIndex]; - if (isMouseDown) - { - if (self->settings->editorIsGridSnap) - { - position.x = roundf((position.x - gridSize.x) / gridSize.x) * gridSize.x + gridOffset.x - (gridSize.x * 0.5f); - position.y = roundf((position.y - gridSize.y) / gridSize.y) * gridSize.y + gridOffset.y - (gridSize.y * 0.5f); - } + _imgui_input_int(IMGUI_FRAME_PROPERTIES_AT_FRAME.copy({.isDisabled = !frame}), self, frame->atFrame); + } - frame->pivot = position - frame->crop; - } - else - { - if (isLeft) frame->pivot.x -= step; - if (isRight) frame->pivot.x += step; - if (isUp) frame->pivot.y -= step; - if (isDown) frame->pivot.y += step; - } - break; - case TOOL_CROP: - if (!texture || !frame) break; + _imgui_end(); // IMGUI_FRAME_PROPERTIES +} +*/ - if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_snapshot(self, IMGUI_ACTION_MOVE); +static void _imgui_log(Imgui* self) { + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 borderColor = style.Colors[ImGuiCol_Border]; + ImVec4 textColor = style.Colors[ImGuiCol_Text]; - if (self->settings->editorIsGridSnap) - { - position.x = roundf(position.x / gridSize.x) * gridSize.x + gridOffset.x - (gridSize.x * 0.5f); - position.y = roundf(position.y / gridSize.y) * gridSize.y + gridOffset.y - (gridSize.y * 0.5f); - } - - if (isMouseClick) - { - frame->crop = position; - frame->size = ivec2(0,0); - } - else if (isMouseDown) - frame->size = position - frame->crop; - else - { - if (isCtrl) - { - if (isLeft) frame->crop.x -= step; - if (isRight) frame->crop.x += step; - if (isUp) frame->crop.y -= step; - if (isDown) frame->crop.y += step; - } - else - { - if (isLeft) frame->size.x -= step; - if (isRight) frame->size.x += step; - if (isUp) frame->size.y -= step; - if (isDown) frame->size.y += step; - } + ImVec2 position = {io.DisplaySize.x - IMGUI_LOG_PADDING, io.DisplaySize.y - IMGUI_LOG_PADDING}; - frame->size.x = std::max({}, frame->size.x); - frame->size.y = std::max({}, frame->size.y); - } - break; - case TOOL_DRAW: - case TOOL_ERASE: - { - if (!texture) break; - - vec4 color = tool == TOOL_ERASE ? COLOR_TRANSPARENT : toolColor; + for (int i = (int)self->log.size() - 1; i >= 0; --i) { + ImguiLogItem& item = self->log[i]; + float lifetime = item.timeRemaining / IMGUI_LOG_DURATION; + borderColor.w = lifetime; + textColor.w = lifetime; - if (isMouseClick) - imgui_snapshot(self, tool == TOOL_DRAW ? IMGUI_ACTION_DRAW : IMGUI_ACTION_ERASE); + item.timeRemaining -= io.DeltaTime; - if (isMouseDown) - texture_pixel_set(texture, position, color); - break; - } - case TOOL_COLOR_PICKER: - if (isMouseDown) - { - SDL_GetMouseState(&mousePos.x, &mousePos.y); - _imgui_window_color_from_position_get(self->window, mousePos, toolColor); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2()); - ImGui::BeginTooltip(); - _imgui_color_button(IMGUI_COLOR_PICKER_BUTTON, self, toolColor); - ImGui::EndTooltip(); - ImGui::PopStyleVar(); - } - break; - default: - break; - } - - if (mouseWheel != 0 || isZoomIn || isZoomOut) - { - f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); - } + if (item.timeRemaining <= 0.0f) { + self->log.erase(self->log.begin() + i); + continue; + } + + ImGui::SetNextWindowPos(position, ImGuiCond_Always, {1.0f, 1.0f}); + ImGui::PushStyleColor(ImGuiCol_Border, borderColor); + ImGui::PushStyleColor(ImGuiCol_Text, textColor); + ImGui::SetNextWindowBgAlpha(lifetime); + + _imgui_begin(IMGUI_LOG_WINDOW.copy({.label = std::format(IMGUI_LOG_FORMAT, i)}), self); + ImGui::TextUnformatted(item.text.c_str()); + ImVec2 windowSize = ImGui::GetWindowSize(); + _imgui_end(); // IMGUI_LOG_WINDOW + + ImGui::PopStyleColor(2); + + position.y -= windowSize.y + IMGUI_LOG_PADDING; + } } -static void _imgui_frame_properties(Imgui* self) -{ - static Anm2Type& type = self->reference->itemType; - - IMGUI_BEGIN_OR_RETURN(IMGUI_FRAME_PROPERTIES, self); +static void _imgui_dock(Imgui* self) { + ImguiItem window = IMGUI_WINDOW_MAIN; + ImGuiViewport* viewport = ImGui::GetMainViewport(); - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - - bool isLayerFrame = frame && type == ANM2_LAYER; - - if (!frame || type != ANM2_TRIGGERS) - { - _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_CROP.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->crop); - _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SIZE.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->size); - _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_POSITION.copy({!frame}), self, !frame ? dummy_value() : frame->position); - _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_PIVOT.copy({!isLayerFrame}), self, !isLayerFrame ? dummy_value() : frame->pivot); - _imgui_drag_float2(IMGUI_FRAME_PROPERTIES_SCALE.copy({!frame}), self, !frame ? dummy_value() : frame->scale); - _imgui_drag_float(IMGUI_FRAME_PROPERTIES_ROTATION.copy({!frame}), self, !frame ? dummy_value() : frame->rotation); - _imgui_input_int(IMGUI_FRAME_PROPERTIES_DELAY.copy({!frame}), self, !frame ? dummy_value() : frame->delay); - _imgui_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({!frame}), self, !frame ? dummy_value() : frame->tintRGBA); - _imgui_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({!frame}), self, !frame ? dummy_value() : frame->offsetRGB); - _imgui_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({!frame}), self, !frame ? dummy_value() : frame->isVisible); - _imgui_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({!frame}), self, !frame ? dummy_value() : frame->isInterpolated); - _imgui_checkbox(IMGUI_FRAME_PROPERTIES_ROUND.copy({!frame}), self, self->settings->propertiesIsRound); - if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_X.copy({!frame}), self)) frame->scale.x = -frame->scale.x; - if (_imgui_button(IMGUI_FRAME_PROPERTIES_FLIP_Y.copy({!frame}), self)) frame->scale.y = -frame->scale.y; - - if (self->settings->propertiesIsRound && frame) - { - frame->position = glm::trunc(frame->position); - frame->pivot = glm::trunc(frame->pivot); - frame->crop = glm::trunc(frame->crop); - frame->scale = glm::trunc(frame->scale); - frame->rotation = glm::trunc(frame->rotation); - frame->tintRGBA = glm::trunc(frame->tintRGBA); - frame->offsetRGB = glm::trunc(frame->offsetRGB); - } - } - else - { - std::vector eventIDs; - ImguiItem eventItem = IMGUI_FRAME_PROPERTIES_EVENT.copy({!frame}); - - eventIDs.emplace_back(ID_NONE); - eventItem.items.emplace_back(IMGUI_NONE); - - for (auto & [id, event] : self->anm2->events) - { - eventIDs.emplace_back(id); - eventItem.items.emplace_back(event.name); - } - - s32 eventIndex = std::find(eventIDs.begin(), eventIDs.end(), frame->eventID) - eventIDs.begin(); - - if (_imgui_combo(eventItem, self, &eventIndex)) - frame->eventID = eventIDs[eventIndex]; + ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR.size->y)); + ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR.size->y)); + ImGui::SetNextWindowViewport(viewport->ID); - _imgui_input_int(IMGUI_FRAME_PROPERTIES_AT_FRAME.copy({!frame}), self, frame->atFrame); - } + _imgui_begin(window, self); + _imgui_dockspace(IMGUI_DOCKSPACE_MAIN, self); - _imgui_end(); // IMGUI_FRAME_PROPERTIES + //_imgui_tools(self); + //_imgui_animations(self); + //_imgui_events(self); + _imgui_spritesheets(self); + //_imgui_animation_preview(self); + //_imgui_spritesheet_editor(self); + //_imgui_layers(self); + //_imgui_nulls(self); + //_imgui_timeline(self); + //_imgui_onionskin(self); + //_imgui_frame_properties(self); + + _imgui_end(); // IMGUI_WINDOW_MAIN } -static void _imgui_log(Imgui* self) -{ - ImGuiIO& io = ImGui::GetIO(); - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4 borderColor = style.Colors[ImGuiCol_Border]; - ImVec4 textColor = style.Colors[ImGuiCol_Text]; +void imgui_init(Imgui* self, Dialog* dialog, Resources* resources, Anm2* anm2, Anm2Reference* reference, Editor* editor, Preview* preview, + GeneratePreview* generatePreview, Settings* settings, Snapshots* snapshots, Clipboard* clipboard, SDL_Window* window, + SDL_GLContext* glContext) { + self->dialog = dialog; + self->resources = resources; + self->anm2 = anm2; + self->reference = reference; + self->editor = editor; + self->preview = preview; + self->generatePreview = generatePreview; + self->settings = settings; + self->snapshots = snapshots; + self->clipboard = clipboard; + self->window = window; + self->glContext = glContext; - ImVec2 position = {io.DisplaySize.x - IMGUI_LOG_PADDING, io.DisplaySize.y - IMGUI_LOG_PADDING}; - - for (s32 i = (s32)self->log.size() - 1; i >= 0; --i) - { - ImguiLogItem& item = self->log[i]; - f32 lifetime = item.timeRemaining / IMGUI_LOG_DURATION; - borderColor.w = lifetime; - textColor.w = lifetime; + self->saveAnm2 = *anm2; - item.timeRemaining -= io.DeltaTime; + ImGui::CreateContext(); + ImGui::StyleColorsDark(); - if (item.timeRemaining <= 0.0f) - { - self->log.erase(self->log.begin() + i); - continue; - } - - ImGui::SetNextWindowPos(position, ImGuiCond_Always, {1.0f, 1.0f}); - ImGui::PushStyleColor(ImGuiCol_Border, borderColor); - ImGui::PushStyleColor(ImGuiCol_Text, textColor); - ImGui::SetNextWindowBgAlpha(lifetime); + ImGui_ImplSDL3_InitForOpenGL(self->window, *self->glContext); + ImGui_ImplOpenGL3_Init(IMGUI_OPENGL_VERSION); - _imgui_begin(IMGUI_LOG_WINDOW.copy({.label = std::format(IMGUI_LOG_FORMAT, i)}), self); - ImGui::TextUnformatted(item.text.c_str()); - ImVec2 windowSize = ImGui::GetWindowSize(); - _imgui_end(); // IMGUI_LOG_WINDOW + ImGuiIO& io = ImGui::GetIO(); + io.IniFilename = nullptr; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.ConfigWindowsMoveFromTitleBarOnly = true; - ImGui::PopStyleColor(2); + ImGui::GetStyle().FontScaleMain = self->settings->displayScale; + io.Fonts->AddFontFromFileTTF(FONT_PATH, FONT_SIZE); - position.y -= windowSize.y + IMGUI_LOG_PADDING; - } + self->style = ImGui::GetStyle(); + + imgui_keyboard_nav_enable(); + + ImGui::LoadIniSettingsFromDisk(settings_path_get().c_str()); + + for (int i = 0; i < HOTKEY_COUNT; i++) { + if (!SETTINGS_HOTKEY_MEMBERS[i]) + continue; + imgui_hotkey_chord_registry()[i] = imgui_chord_from_string_get(*&(self->settings->*SETTINGS_HOTKEY_MEMBERS[i])); + } } -static void _imgui_dock(Imgui* self) -{ - ImguiItem window = IMGUI_WINDOW_MAIN; - ImGuiViewport* viewport = ImGui::GetMainViewport(); +void imgui_update(Imgui* self) { + ImGui_ImplSDL3_NewFrame(); + ImGui_ImplOpenGL3_NewFrame(); + ImGui::NewFrame(); - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR.size.y)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR.size.y)); - ImGui::SetNextWindowViewport(viewport->ID); - - _imgui_begin(window, self); - _imgui_dockspace(IMGUI_DOCKSPACE_MAIN, self); + _imgui_taskbar(self); + _imgui_dock(self); + _imgui_log(self); - _imgui_tools(self); - _imgui_animations(self); - _imgui_events(self); - _imgui_spritesheets(self); - _imgui_animation_preview(self); - _imgui_spritesheet_editor(self); - _imgui_layers(self); - _imgui_nulls(self); - _imgui_timeline(self); - _imgui_onionskin(self); - _imgui_frame_properties(self); + if (self->isContextualActionsEnabled) { + for (const auto& item : imgui_item_registry()) { + if (item->is_chord() && _imgui_chord_pressed(item->chord_get()) && !item->focusWindow.has_value()) { + if (item->snapshotAction.has_value()) + imgui_snapshot(self, *item->snapshotAction); + if (item->function) + item->function(self); + } + } + } - _imgui_end(); // IMGUI_WINDOW_MAIN + imgui_contextual_actions_enable(self); + + if (self->pendingCursor != self->cursor) { + SDL_SetCursor(SDL_CreateSystemCursor(self->pendingCursor)); + self->cursor = self->pendingCursor; + } + + self->pendingCursor = CURSOR_DEFAULT; + + SDL_Event event; + + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL3_ProcessEvent(&event); + + switch (event.type) { + case SDL_EVENT_DROP_FILE: { + const char* droppedFile = event.drop.data; + + if (path_is_extension(droppedFile, ANM2_EXTENSION)) + _imgui_anm2_open(self, droppedFile); + else if (path_is_extension(droppedFile, ANM2_SPRITESHEET_EXTENSION)) { + if (self->anm2->path.empty()) { + _imgui_no_anm2_path_check(self); + break; + } + _imgui_spritesheet_add(self, droppedFile); + } else + imgui_log_push(self, IMGUI_LOG_DRAG_DROP_ERROR); + + break; + } + case SDL_EVENT_QUIT: + if (self->isTryQuit) + self->isQuit = true; + else + imgui_quit(self); + break; + default: + break; + } + } + + _imgui_confirm_popup(IMGUI_NO_ANM2_PATH_CONFIRMATION, self, nullptr, true); } -void imgui_init -( - Imgui* self, - Dialog* dialog, - Resources* resources, - Anm2* anm2, - Anm2Reference* reference, - Editor* editor, - Preview* preview, - GeneratePreview* generatePreview, - Settings* settings, - Snapshots* snapshots, - Clipboard* clipboard, - SDL_Window* window, - SDL_GLContext* glContext -) -{ - IMGUI_CHECKVERSION(); - - self->dialog = dialog; - self->resources = resources; - self->anm2 = anm2; - self->reference = reference; - self->editor = editor; - self->preview = preview; - self->generatePreview = generatePreview; - self->settings = settings; - self->snapshots = snapshots; - self->clipboard = clipboard; - self->window = window; - self->glContext = glContext; - - self->saveAnm2 = *anm2; - - ImGui::CreateContext(); - ImGui::StyleColorsDark(); - - ImGui_ImplSDL3_InitForOpenGL(self->window, *self->glContext); - ImGui_ImplOpenGL3_Init(IMGUI_OPENGL_VERSION); - - ImGuiIO& io = ImGui::GetIO(); - io.IniFilename = nullptr; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - io.ConfigWindowsMoveFromTitleBarOnly = true; - - imgui_keyboard_nav_enable(); - - ImGui::LoadIniSettingsFromDisk(settings_path_get().c_str()); - - for (s32 i = 0; i < HOTKEY_COUNT; i++) - { - if (!SETTINGS_HOTKEY_MEMBERS[i]) continue; - imgui_hotkey_chord_registry()[i] = imgui_chord_from_string_get(*&(self->settings->*SETTINGS_HOTKEY_MEMBERS[i])); - } - +void imgui_draw(void) { + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } -void imgui_update(Imgui* self) -{ - ImGui_ImplSDL3_NewFrame(); - ImGui_ImplOpenGL3_NewFrame(); - ImGui::NewFrame(); +void imgui_free(void) { + if (!ImGui::GetCurrentContext()) + return; - _imgui_taskbar(self); - _imgui_dock(self); - _imgui_log(self); - - if (self->isContextualActionsEnabled) - { - for (const auto& item : imgui_item_registry()) - { - if (item->is_chord() && _imgui_chord_pressed(item->chord_get()) && !item->is_focus_window()) - { - if (item->is_undoable()) imgui_snapshot(self, item->snapshotAction); - if (item->is_function()) item->function(self); - } - } - } - - imgui_contextual_actions_enable(self); - - if (self->pendingCursor != self->cursor) - { - SDL_SetCursor(SDL_CreateSystemCursor(self->pendingCursor)); - self->cursor = self->pendingCursor; - } - - self->pendingCursor = CURSOR_DEFAULT; - - SDL_Event event; - - while(SDL_PollEvent(&event)) - { - ImGui_ImplSDL3_ProcessEvent(&event); - - switch (event.type) - { - case SDL_EVENT_DROP_FILE: - { - const char* droppedFile = event.drop.data; - - if (path_is_extension(droppedFile, ANM2_EXTENSION)) _imgui_anm2_open(self, droppedFile); - else if (path_is_extension(droppedFile, ANM2_SPRITESHEET_EXTENSION)) _imgui_spritesheet_add(self, droppedFile); - else imgui_log_push(self, IMGUI_LOG_DRAG_DROP_ERROR); - - break; - } - case SDL_EVENT_QUIT: - if (self->isTryQuit) self->isQuit = true; - else imgui_quit(self); - break; - default: - break; - } - } -} - -void imgui_draw(void) -{ - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); -} - -void imgui_free(void) -{ - if (!ImGui::GetCurrentContext()) return; - - ImGui_ImplSDL3_Shutdown(); - ImGui_ImplOpenGL3_Shutdown(); - ImGui::SaveIniSettingsToDisk(settings_path_get().c_str()); - ImGui::DestroyContext(); + ImGui_ImplSDL3_Shutdown(); + ImGui_ImplOpenGL3_Shutdown(); + ImGui::SaveIniSettingsToDisk(settings_path_get().c_str()); + ImGui::DestroyContext(); } \ No newline at end of file diff --git a/src/imgui.h b/src/imgui.h index 1ccc1f5..bf561a5 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -1,61 +1,93 @@ #pragma once +#include "canvas.h" #include "clipboard.h" #include "dialog.h" #include "editor.h" #include "ffmpeg.h" -#include "preview.h" #include "generate_preview.h" +#include "preview.h" #include "resources.h" #include "settings.h" #include "snapshots.h" #include "tool.h" #include "window.h" +#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS +#define IMGUI_DEBUG_PARANOID #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_ENABLE_DOCKING -#define IM_VEC2_CLASS_EXTRA \ - inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \ - inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \ - inline ImVec2 operator+(const ImVec2& rhs) const { return ImVec2(x + rhs.x, y + rhs.y); } \ - inline ImVec2 operator-(const ImVec2& rhs) const { return ImVec2(x - rhs.x, y - rhs.y); } \ - inline ImVec2 operator*(const ImVec2& rhs) const { return ImVec2(x * rhs.x, y * rhs.y); } \ - inline ImVec2 operator*(float s) const { return ImVec2(x * s, y * s); } \ - friend inline ImVec2 operator*(float s, const ImVec2& v) { return ImVec2(v.x * s, v.y * s); } \ - inline ImVec2& operator+=(const ImVec2& rhs) { x += rhs.x; y += rhs.y; return *this; } \ - inline ImVec2& operator-=(const ImVec2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } \ - inline ImVec2& operator*=(float s) { x *= s; y *= s; return *this; } \ - inline ImVec2(const vec2& v) : x(v.x), y(v.y) {} \ - inline operator vec2() const { return vec2(x, y); } +#define IM_VEC2_CLASS_EXTRA \ + inline bool operator==(const ImVec2& rhs) const { return x == rhs.x && y == rhs.y; } \ + inline bool operator!=(const ImVec2& rhs) const { return !(*this == rhs); } \ + inline ImVec2 operator+(const ImVec2& rhs) const { return ImVec2(x + rhs.x, y + rhs.y); } \ + inline ImVec2 operator-(const ImVec2& rhs) const { return ImVec2(x - rhs.x, y - rhs.y); } \ + inline ImVec2 operator*(const ImVec2& rhs) const { return ImVec2(x * rhs.x, y * rhs.y); } \ + inline ImVec2 operator*(float s) const { return ImVec2(x * s, y * s); } \ + friend inline ImVec2 operator*(float s, const ImVec2& v) { return ImVec2(v.x * s, v.y * s); } \ + inline ImVec2& operator+=(const ImVec2& rhs) { \ + x += rhs.x; \ + y += rhs.y; \ + return *this; \ + } \ + inline ImVec2& operator-=(const ImVec2& rhs) { \ + x -= rhs.x; \ + y -= rhs.y; \ + return *this; \ + } \ + inline ImVec2& operator*=(float s) { \ + x *= s; \ + y *= s; \ + return *this; \ + } \ + inline ImVec2(const vec2& v) : x(v.x), y(v.y) {} \ + inline operator vec2() const { return vec2(x, y); } -#define IM_VEC4_CLASS_EXTRA \ - inline bool operator==(const ImVec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } \ - inline bool operator!=(const ImVec4& rhs) const { return !(*this == rhs); } \ - inline ImVec4 operator+(const ImVec4& rhs) const { return ImVec4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w); } \ - inline ImVec4 operator-(const ImVec4& rhs) const { return ImVec4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w); } \ - inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } \ - inline ImVec4 operator*(float s) const { return ImVec4(x * s, y * s, z * s, w * s); } \ - friend inline ImVec4 operator*(float s, const ImVec4& v) { return ImVec4(v.x * s, v.y * s, v.z * s, v.w * s); } \ - inline ImVec4& operator+=(const ImVec4& rhs) { x += rhs.x; y += rhs.y; z += rhs.z; w += rhs.w; return *this; } \ - inline ImVec4& operator-=(const ImVec4& rhs) { x -= rhs.x; y -= rhs.y; z -= rhs.z; w -= rhs.w; return *this; } \ - inline ImVec4& operator*=(float s) { x *= s; y *= s; z *= s; w *= s; return *this; } \ - inline ImVec4(const vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {} \ - inline operator vec4() const { return vec4(x, y, z, w); } - +#define IM_VEC4_CLASS_EXTRA \ + inline bool operator==(const ImVec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } \ + inline bool operator!=(const ImVec4& rhs) const { return !(*this == rhs); } \ + inline ImVec4 operator+(const ImVec4& rhs) const { return ImVec4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w); } \ + inline ImVec4 operator-(const ImVec4& rhs) const { return ImVec4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w); } \ + inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } \ + inline ImVec4 operator*(float s) const { return ImVec4(x * s, y * s, z * s, w * s); } \ + friend inline ImVec4 operator*(float s, const ImVec4& v) { return ImVec4(v.x * s, v.y * s, v.z * s, v.w * s); } \ + inline ImVec4& operator+=(const ImVec4& rhs) { \ + x += rhs.x; \ + y += rhs.y; \ + z += rhs.z; \ + w += rhs.w; \ + return *this; \ + } \ + inline ImVec4& operator-=(const ImVec4& rhs) { \ + x -= rhs.x; \ + y -= rhs.y; \ + z -= rhs.z; \ + w -= rhs.w; \ + return *this; \ + } \ + inline ImVec4& operator*=(float s) { \ + x *= s; \ + y *= s; \ + z *= s; \ + w *= s; \ + return *this; \ + } \ + inline ImVec4(const vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {} \ + inline operator vec4() const { return vec4(x, y, z, w); } + +#include +#include #include #include -#include -#include #define IMGUI_CHORD_NONE (ImGuiMod_None) -#define IMGUI_EVENTS_FOOTER_HEIGHT 40 #define IMGUI_FRAME_BORDER 2.0f #define IMGUI_LOG_DURATION 3.0f #define IMGUI_LOG_PADDING 10.0f +#define IMGUI_TEXT_HEIGHT_PADDING 4.0f #define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX) #define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, 128) #define IMGUI_PLAYHEAD_LINE_WIDTH 2.0f -#define IMGUI_SPRITESHEETS_FOOTER_HEIGHT 65 #define IMGUI_TIMELINE_FRAME_MULTIPLE 5 #define IMGUI_TIMELINE_MERGE #define IMGUI_TOOL_COLOR_PICKER_DURATION 0.25f @@ -77,6 +109,7 @@ #define IMGUI_ACTION_CROP "Crop" #define IMGUI_ACTION_ADD_SPRITESHEET "Add Spritesheet" #define IMGUI_ACTION_REPLACE_SPRITESHEET "Replace Spritesheet" +#define IMGUI_ACTION_SPRITESHEET_SWAP "Spritesheet Swap" #define IMGUI_ACTION_OPEN_FILE "Open File" #define IMGUI_SET_ITEM_PROPERTIES_POPUP "Item Properties" @@ -84,22 +117,27 @@ #define IMGUI_POPUP_MODAL_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize #define IMGUI_LOG_NO_ANM2_PATH "Please save the .anm2 to a path first!" -#define IMGUI_LOG_FILE_OPEN_FORMAT "Opened anm2: {}" -#define IMGUI_LOG_FILE_SAVE_FORMAT "Saved anm2 to: {}" +#define IMGUI_LOG_FILE_OPEN_FORMAT "Opened anm2: {}" +#define IMGUI_LOG_FILE_SAVE_FORMAT "Saved anm2 to: {}" #define IMGUI_LOG_SPRITESHEET_RELOAD "Reloaded selected spritesheets" -#define IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT "Saved rendered frames to: {}" -#define IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT "Saved rendered animation to: {}" +#define IMGUI_LOG_RENDER_ANIMATION_FRAMES_SAVE_FORMAT "Saved rendered frames to: {}" +#define IMGUI_LOG_RENDER_ANIMATION_SAVE_FORMAT "Saved rendered animation to: {}" #define IMGUI_LOG_RENDER_ANIMATION_NO_ANIMATION_ERROR "No animation selected; rendering cancelled." #define IMGUI_LOG_RENDER_ANIMATION_NO_FRAMES_ERROR "No frames to render; rendering cancelled." #define IMGUI_LOG_RENDER_ANIMATION_DIRECTORY_ERROR "Invalid directory! Make sure it exists and you have write permissions." #define IMGUI_LOG_RENDER_ANIMATION_PATH_ERROR "Invalid path! Make sure it's valid and you have write permissions." -#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR "Invalid FFmpeg path! Make sure you have it installed and the path is correct." -#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR "FFmpeg could not render animation! Check paths or your FFmpeg installation." -#define IMGUI_LOG_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" +#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_PATH_ERROR \ + "Invalid FFmpeg path! Make sure you have it installed and the path is " \ + "correct." +#define IMGUI_LOG_RENDER_ANIMATION_FFMPEG_ERROR \ + "FFmpeg could not render animation! Check paths or your FFmpeg " \ + "installation." +#define IMGUI_LOG_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" #define IMGUI_LOG_DRAG_DROP_ERROR "Invalid file for dragging/dropping!" #define IMGUI_LOG_ANIMATION_PASTE_ERROR "Failed to parse clipboard text as an animation." #define IMGUI_LOG_FRAME_PASTE_ERROR "Failed to parse clipboard text as a frame." #define IMGUI_LOG_RELOAD_SPRITESHEET "Reloaded spritesheet(s)." +#define IMGUI_LOG_ADD_SPRITESHEET_ERROR "Failed to add spritesheet: {}. Make sure it's a valid PNG file." #define IMGUI_NONE "None" #define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" @@ -141,7 +179,6 @@ const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR = {0.113, 0.184 const ImVec4 IMGUI_ACTIVE_COLOR = {1.0, 1.0, 1.0, 1.0}; const ImVec4 IMGUI_INACTIVE_COLOR = {1.0, 1.0, 1.0, 0.25}; -const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {50.0, 50.0}; const ImVec2 IMGUI_TOOLTIP_OFFSET = {16, 8}; const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; @@ -152,6 +189,7 @@ const ImGuiKey IMGUI_INPUT_UP = ImGuiKey_UpArrow; const ImGuiKey IMGUI_INPUT_DOWN = ImGuiKey_DownArrow; const ImGuiKey IMGUI_INPUT_SHIFT = ImGuiMod_Shift; const ImGuiKey IMGUI_INPUT_CTRL = ImGuiMod_Ctrl; +const ImGuiKey IMGUI_INPUT_ALT = ImGuiMod_Alt; const ImGuiKey IMGUI_INPUT_ZOOM_IN = ImGuiKey_1; const ImGuiKey IMGUI_INPUT_ZOOM_OUT = ImGuiKey_2; const ImGuiKey IMGUI_INPUT_ENTER = ImGuiKey_Enter; @@ -159,2427 +197,1432 @@ const ImGuiKey IMGUI_INPUT_RENAME = ImGuiKey_F2; const ImGuiKey IMGUI_INPUT_DEFAULT = ImGuiKey_Home; const ImGuiMouseButton IMGUI_MOUSE_DEFAULT = ImGuiMouseButton_Middle; -enum ImguiPopupType -{ - IMGUI_POPUP_NONE, - IMGUI_POPUP_BY_ITEM, - IMGUI_POPUP_CENTER_WINDOW +enum ImguiPopupType { IMGUI_POPUP_NONE, IMGUI_POPUP_BY_ITEM, IMGUI_POPUP_CENTER_WINDOW }; +enum ImguiPopupState { IMGUI_POPUP_STATE_CLOSED, IMGUI_POPUP_STATE_OPEN, IMGUI_POPUP_STATE_CONFIRM, IMGUI_POPUP_STATE_CANCEL }; + +struct ImguiColorSet { + std::optional normal = {}; + std::optional active = {}; + std::optional hovered = {}; + std::optional border = {}; }; -enum ImguiPopupState -{ - IMGUI_POPUP_STATE_CLOSED, - IMGUI_POPUP_STATE_OPEN, - IMGUI_POPUP_STATE_CONFIRM, - IMGUI_POPUP_STATE_CANCEL +struct ImguiLogItem { + std::string text; + float timeRemaining; }; -struct ImguiColorSet -{ - ImVec4 normal{}; - ImVec4 active{}; - ImVec4 hovered{}; - ImVec4 border{}; - - bool is_normal() const { return normal != ImVec4(); } - bool is_active() const { return active != ImVec4(); } - bool is_hovered() const { return hovered != ImVec4(); } - bool is_border() const { return border != ImVec4(); } +struct Imgui { + Dialog* dialog = nullptr; + Resources* resources = nullptr; + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Editor* editor = nullptr; + Preview* preview = nullptr; + GeneratePreview* generatePreview = nullptr; + Settings* settings = nullptr; + Snapshots* snapshots = nullptr; + Clipboard* clipboard = nullptr; + SDL_Window* window = nullptr; + SDL_GLContext* glContext = nullptr; + std::string pendingPopup{}; + ImguiPopupType pendingPopupType = IMGUI_POPUP_NONE; + ImVec2 pendingPopupPosition{}; + std::vector log; + ImGuiStyle style; + Anm2 saveAnm2; + SDL_SystemCursor cursor; + SDL_SystemCursor pendingCursor; + bool isCursorSet = false; + bool isContextualActionsEnabled = true; + bool isQuit = false; + bool isTryQuit = false; }; -struct ImguiLogItem -{ - std::string text; - f32 timeRemaining; -}; - -struct Imgui -{ - Dialog* dialog = nullptr; - Resources* resources = nullptr; - Anm2* anm2 = nullptr; - Anm2Reference* reference = nullptr; - Editor* editor = nullptr; - Preview* preview = nullptr; - GeneratePreview* generatePreview = nullptr; - Settings* settings = nullptr; - Snapshots* snapshots = nullptr; - Clipboard* clipboard = nullptr; - SDL_Window* window = nullptr; - SDL_GLContext* glContext = nullptr; - std::string pendingPopup{}; - ImguiPopupType pendingPopupType = IMGUI_POPUP_NONE; - ImVec2 pendingPopupPosition{}; - std::vector log; - Anm2 saveAnm2; - SDL_SystemCursor cursor; - SDL_SystemCursor pendingCursor; - bool isCursorSet = false; - bool isContextualActionsEnabled = true; - bool isQuit = false; - bool isTryQuit = false; -}; - -static inline void imgui_snapshot(Imgui* self, const std::string& action = SNAPSHOT_ACTION) -{ - self->snapshots->action = action; - Snapshot snapshot = snapshot_get(self->snapshots); - snapshots_undo_push(self->snapshots, &snapshot); +static inline void imgui_snapshot(Imgui* self, const std::string& action = SNAPSHOT_ACTION) { + self->snapshots->action = action; + Snapshot snapshot = snapshot_get(self->snapshots); + snapshots_undo_push(self->snapshots, &snapshot); } -static void imgui_log_push(Imgui* self, const std::string& text) -{ - self->log.push_back({text, IMGUI_LOG_DURATION}); - log_imgui(text); +static void imgui_log_push(Imgui* self, const std::string& text) { + self->log.push_back({text, IMGUI_LOG_DURATION}); + log_imgui(text); } -static inline void imgui_anm2_new(Imgui* self) -{ - anm2_reference_clear(self->reference); - anm2_free(self->anm2); - anm2_new(self->anm2); +static inline void imgui_anm2_new(Imgui* self) { + *self->reference = Anm2Reference(); + anm2_free(self->anm2); + anm2_new(self->anm2); } -static inline void imgui_file_open(Imgui* self) -{ - dialog_anm2_open(self->dialog); +static inline void imgui_file_open(Imgui* self) { dialog_anm2_open(self->dialog); } + +static inline void imgui_file_save(Imgui* self) { + if (self->anm2->path.empty()) + dialog_anm2_save(self->dialog); + else { + anm2_serialize(self->anm2, self->anm2->path); + imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->anm2->path)); + } + + self->saveAnm2 = *self->anm2; } -static inline void imgui_file_save(Imgui* self) -{ - if (self->anm2->path.empty()) - dialog_anm2_save(self->dialog); - else - { - anm2_serialize(self->anm2, self->anm2->path); - imgui_log_push(self, std::format(IMGUI_LOG_FILE_SAVE_FORMAT, self->anm2->path)); +static inline void imgui_file_new(Imgui* self) { + std::string path = self->anm2->path; + imgui_anm2_new(self); + self->anm2->path = path; + imgui_file_save(self); +} + +static inline void imgui_file_save_as(Imgui* self) { dialog_anm2_save(self->dialog); } + +static inline void imgui_quit(Imgui* self) { + if (self->saveAnm2 != *self->anm2) + self->isTryQuit = true; + else + self->isQuit = true; +} + +static inline void imgui_explore(Imgui* self) { + std::filesystem::path filePath = self->anm2->path; + std::filesystem::path parentPath = filePath.parent_path(); + dialog_explorer_open(parentPath.string()); +} + +static inline void imgui_tool_pan_set(Imgui* self) { self->settings->tool = TOOL_PAN; } +static inline void imgui_tool_move_set(Imgui* self) { self->settings->tool = TOOL_MOVE; } +static inline void imgui_tool_rotate_set(Imgui* self) { self->settings->tool = TOOL_ROTATE; } +static inline void imgui_tool_scale_set(Imgui* self) { self->settings->tool = TOOL_SCALE; } +static inline void imgui_tool_crop_set(Imgui* self) { self->settings->tool = TOOL_CROP; } +static inline void imgui_tool_draw_set(Imgui* self) { self->settings->tool = TOOL_DRAW; } +static inline void imgui_tool_erase_set(Imgui* self) { self->settings->tool = TOOL_ERASE; } +static inline void imgui_tool_color_picker_set(Imgui* self) { self->settings->tool = TOOL_COLOR_PICKER; } + +static inline void imgui_undo(Imgui* self) { + if (self->snapshots->undoStack.top == 0) + return; + + snapshots_undo(self->snapshots); + imgui_log_push(self, std::format(IMGUI_LOG_UNDO_FORMAT, self->snapshots->action)); +} + +static inline void imgui_redo(Imgui* self) { + if (self->snapshots->redoStack.top == 0) + return; + + std::string action = self->snapshots->action; + snapshots_redo(self->snapshots); + imgui_log_push(self, std::format(IMGUI_LOG_REDO_FORMAT, action)); +} + +static inline void imgui_cut(Imgui* self) { clipboard_cut(self->clipboard); } + +static inline void imgui_copy(Imgui* self) { clipboard_copy(self->clipboard); } + +static inline void imgui_paste(Imgui* self) { + if (!clipboard_paste(self->clipboard)) { + switch (self->clipboard->type) { + case CLIPBOARD_FRAME: + imgui_log_push(self, IMGUI_LOG_FRAME_PASTE_ERROR); + break; + case CLIPBOARD_ANIMATION: + imgui_log_push(self, IMGUI_LOG_ANIMATION_PASTE_ERROR); + break; + default: + break; } - - self->saveAnm2 = *self->anm2; + } } -static inline void imgui_file_new(Imgui* self) -{ - std::string path = self->anm2->path; - imgui_anm2_new(self); - self->anm2->path = path; - imgui_file_save(self); +static inline void imgui_onionskin_toggle(Imgui* self) { self->settings->onionskinIsEnabled = !self->settings->onionskinIsEnabled; } + +static std::unordered_map IMGUI_KEY_MAP = {{"A", ImGuiKey_A}, + {"B", ImGuiKey_B}, + {"C", ImGuiKey_C}, + {"D", ImGuiKey_D}, + {"E", ImGuiKey_E}, + {"F", ImGuiKey_F}, + {"G", ImGuiKey_G}, + {"H", ImGuiKey_H}, + {"I", ImGuiKey_I}, + {"J", ImGuiKey_J}, + {"K", ImGuiKey_K}, + {"L", ImGuiKey_L}, + {"M", ImGuiKey_M}, + {"N", ImGuiKey_N}, + {"O", ImGuiKey_O}, + {"P", ImGuiKey_P}, + {"Q", ImGuiKey_Q}, + {"R", ImGuiKey_R}, + {"S", ImGuiKey_S}, + {"T", ImGuiKey_T}, + {"U", ImGuiKey_U}, + {"V", ImGuiKey_V}, + {"W", ImGuiKey_W}, + {"X", ImGuiKey_X}, + {"Y", ImGuiKey_Y}, + {"Z", ImGuiKey_Z}, + + {"0", ImGuiKey_0}, + {"1", ImGuiKey_1}, + {"2", ImGuiKey_2}, + {"3", ImGuiKey_3}, + {"4", ImGuiKey_4}, + {"5", ImGuiKey_5}, + {"6", ImGuiKey_6}, + {"7", ImGuiKey_7}, + {"8", ImGuiKey_8}, + {"9", ImGuiKey_9}, + + {"Num0", ImGuiKey_Keypad0}, + {"Num1", ImGuiKey_Keypad1}, + {"Num2", ImGuiKey_Keypad2}, + {"Num3", ImGuiKey_Keypad3}, + {"Num4", ImGuiKey_Keypad4}, + {"Num5", ImGuiKey_Keypad5}, + {"Num6", ImGuiKey_Keypad6}, + {"Num7", ImGuiKey_Keypad7}, + {"Num8", ImGuiKey_Keypad8}, + {"Num9", ImGuiKey_Keypad9}, + {"NumAdd", ImGuiKey_KeypadAdd}, + {"NumSubtract", ImGuiKey_KeypadSubtract}, + {"NumMultiply", ImGuiKey_KeypadMultiply}, + {"NumDivide", ImGuiKey_KeypadDivide}, + {"NumEnter", ImGuiKey_KeypadEnter}, + {"NumDecimal", ImGuiKey_KeypadDecimal}, + + {"F1", ImGuiKey_F1}, + {"F2", ImGuiKey_F2}, + {"F3", ImGuiKey_F3}, + {"F4", ImGuiKey_F4}, + {"F5", ImGuiKey_F5}, + {"F6", ImGuiKey_F6}, + {"F7", ImGuiKey_F7}, + {"F8", ImGuiKey_F8}, + {"F9", ImGuiKey_F9}, + {"F10", ImGuiKey_F10}, + {"F11", ImGuiKey_F11}, + {"F12", ImGuiKey_F12}, + + {"Up", ImGuiKey_UpArrow}, + {"Down", ImGuiKey_DownArrow}, + {"Left", ImGuiKey_LeftArrow}, + {"Right", ImGuiKey_RightArrow}, + + {"Space", ImGuiKey_Space}, + {"Enter", ImGuiKey_Enter}, + {"Escape", ImGuiKey_Escape}, + {"Tab", ImGuiKey_Tab}, + {"Backspace", ImGuiKey_Backspace}, + {"Delete", ImGuiKey_Delete}, + {"Insert", ImGuiKey_Insert}, + {"Home", ImGuiKey_Home}, + {"End", ImGuiKey_End}, + {"PageUp", ImGuiKey_PageUp}, + {"PageDown", ImGuiKey_PageDown}, + + {"Minus", ImGuiKey_Minus}, + {"Equal", ImGuiKey_Equal}, + {"LeftBracket", ImGuiKey_LeftBracket}, + {"RightBracket", ImGuiKey_RightBracket}, + {"Semicolon", ImGuiKey_Semicolon}, + {"Apostrophe", ImGuiKey_Apostrophe}, + {"Comma", ImGuiKey_Comma}, + {"Period", ImGuiKey_Period}, + {"Slash", ImGuiKey_Slash}, + {"Backslash", ImGuiKey_Backslash}, + {"GraveAccent", ImGuiKey_GraveAccent}, + + {"MouseLeft", ImGuiKey_MouseLeft}, + {"MouseRight", ImGuiKey_MouseRight}, + {"MouseMiddle", ImGuiKey_MouseMiddle}, + {"MouseX1", ImGuiKey_MouseX1}, + {"MouseX2", ImGuiKey_MouseX2}}; + +static std::unordered_map IMGUI_MOD_MAP = { + {"Ctrl", ImGuiMod_Ctrl}, + {"Shift", ImGuiMod_Shift}, + {"Alt", ImGuiMod_Alt}, + {"Super", ImGuiMod_Super}, +}; + +static inline ImGuiKey imgui_key_from_char_get(char c) { + if (c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + if (c >= 'A' && c <= 'Z') + return (ImGuiKey)(ImGuiKey_A + (c - 'A')); + return ImGuiKey_None; } -static inline void imgui_file_save_as(Imgui* self) -{ - dialog_anm2_save(self->dialog); -} +static inline std::string imgui_string_from_chord_get(ImGuiKeyChord chord) { + std::string result; -static inline void imgui_quit(Imgui* self) -{ - if (self->saveAnm2 != *self->anm2) - self->isTryQuit = true; + if (chord & ImGuiMod_Ctrl) + result += "Ctrl+"; + if (chord & ImGuiMod_Shift) + result += "Shift+"; + if (chord & ImGuiMod_Alt) + result += "Alt+"; + if (chord & ImGuiMod_Super) + result += "Super+"; + + ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); + + if (key != ImGuiKey_None) { + const char* name = ImGui::GetKeyName(key); + if (name && *name) + result += name; else - self->isQuit = true; + result += "Unknown"; + } + + if (!result.empty() && result.back() == '+') + result.pop_back(); + + return result; } -static inline void imgui_explore(Imgui* self) -{ - std::filesystem::path filePath = self->anm2->path; - std::filesystem::path parentPath = filePath.parent_path(); - dialog_explorer_open(parentPath.string()); -} +static inline ImGuiKeyChord imgui_chord_from_string_get(const std::string& str) { + ImGuiKeyChord chord = 0; + ImGuiKey baseKey = ImGuiKey_None; -static inline void imgui_tool_pan_set(Imgui* self) -{ - self->settings->tool = TOOL_PAN; -} + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, '+')) { + // trim + token.erase(0, token.find_first_not_of(" \t\r\n")); + token.erase(token.find_last_not_of(" \t\r\n") + 1); -static inline void imgui_tool_move_set(Imgui* self) -{ - self->settings->tool = TOOL_MOVE; -} + if (token.empty()) + continue; -static inline void imgui_tool_rotate_set(Imgui* self) -{ - self->settings->tool = TOOL_ROTATE; -} - -static inline void imgui_tool_scale_set(Imgui* self) -{ - self->settings->tool = TOOL_SCALE; -} - -static inline void imgui_tool_crop_set(Imgui* self) -{ - self->settings->tool = TOOL_CROP; -} - -static inline void imgui_tool_draw_set(Imgui* self) -{ - self->settings->tool = TOOL_DRAW; -} - -static inline void imgui_tool_erase_set(Imgui* self) -{ - self->settings->tool = TOOL_ERASE; -} - -static inline void imgui_tool_color_picker_set(Imgui* self) -{ - self->settings->tool = TOOL_COLOR_PICKER; -} - -static inline void imgui_undo(Imgui* self) -{ - if (self->snapshots->undoStack.top == 0) return; - - snapshots_undo(self->snapshots); - imgui_log_push(self, std::format(IMGUI_LOG_UNDO_FORMAT, self->snapshots->action)); -} - -static inline void imgui_redo(Imgui* self) -{ - if (self->snapshots->redoStack.top == 0) return; - - std::string action = self->snapshots->action; - snapshots_redo(self->snapshots); - imgui_log_push(self, std::format(IMGUI_LOG_REDO_FORMAT, action)); -} - -static inline void imgui_cut(Imgui* self) -{ - clipboard_cut(self->clipboard); -} - -static inline void imgui_copy(Imgui* self) -{ - clipboard_copy(self->clipboard); -} - -static inline void imgui_paste(Imgui* self) -{ - if (!clipboard_paste(self->clipboard)) - { - switch (self->clipboard->type) - { - case CLIPBOARD_FRAME: imgui_log_push(self, IMGUI_LOG_FRAME_PASTE_ERROR); break; - case CLIPBOARD_ANIMATION: imgui_log_push(self, IMGUI_LOG_ANIMATION_PASTE_ERROR); break; - default: break; - } + if (auto it = IMGUI_MOD_MAP.find(token); it != IMGUI_MOD_MAP.end()) { + chord |= it->second; + } else if (baseKey == ImGuiKey_None) { + if (auto it2 = IMGUI_KEY_MAP.find(token); it2 != IMGUI_KEY_MAP.end()) + baseKey = it2->second; } -} + } -static inline void imgui_onionskin_toggle(Imgui* self) -{ - self->settings->onionskinIsEnabled = !self->settings->onionskinIsEnabled; -} + if (baseKey != ImGuiKey_None) + chord |= baseKey; -static const std::unordered_map IMGUI_KEY_MAP = -{ - { "A", ImGuiKey_A }, - { "B", ImGuiKey_B }, - { "C", ImGuiKey_C }, - { "D", ImGuiKey_D }, - { "E", ImGuiKey_E }, - { "F", ImGuiKey_F }, - { "G", ImGuiKey_G }, - { "H", ImGuiKey_H }, - { "I", ImGuiKey_I }, - { "J", ImGuiKey_J }, - { "K", ImGuiKey_K }, - { "L", ImGuiKey_L }, - { "M", ImGuiKey_M }, - { "N", ImGuiKey_N }, - { "O", ImGuiKey_O }, - { "P", ImGuiKey_P }, - { "Q", ImGuiKey_Q }, - { "R", ImGuiKey_R }, - { "S", ImGuiKey_S }, - { "T", ImGuiKey_T }, - { "U", ImGuiKey_U }, - { "V", ImGuiKey_V }, - { "W", ImGuiKey_W }, - { "X", ImGuiKey_X }, - { "Y", ImGuiKey_Y }, - { "Z", ImGuiKey_Z }, - - { "0", ImGuiKey_0 }, - { "1", ImGuiKey_1 }, - { "2", ImGuiKey_2 }, - { "3", ImGuiKey_3 }, - { "4", ImGuiKey_4 }, - { "5", ImGuiKey_5 }, - { "6", ImGuiKey_6 }, - { "7", ImGuiKey_7 }, - { "8", ImGuiKey_8 }, - { "9", ImGuiKey_9 }, - - { "Num0", ImGuiKey_Keypad0 }, - { "Num1", ImGuiKey_Keypad1 }, - { "Num2", ImGuiKey_Keypad2 }, - { "Num3", ImGuiKey_Keypad3 }, - { "Num4", ImGuiKey_Keypad4 }, - { "Num5", ImGuiKey_Keypad5 }, - { "Num6", ImGuiKey_Keypad6 }, - { "Num7", ImGuiKey_Keypad7 }, - { "Num8", ImGuiKey_Keypad8 }, - { "Num9", ImGuiKey_Keypad9 }, - { "NumAdd", ImGuiKey_KeypadAdd }, - { "NumSubtract", ImGuiKey_KeypadSubtract }, - { "NumMultiply", ImGuiKey_KeypadMultiply }, - { "NumDivide", ImGuiKey_KeypadDivide }, - { "NumEnter", ImGuiKey_KeypadEnter }, - { "NumDecimal", ImGuiKey_KeypadDecimal }, - - { "F1", ImGuiKey_F1 }, - { "F2", ImGuiKey_F2 }, - { "F3", ImGuiKey_F3 }, - { "F4", ImGuiKey_F4 }, - { "F5", ImGuiKey_F5 }, - { "F6", ImGuiKey_F6 }, - { "F7", ImGuiKey_F7 }, - { "F8", ImGuiKey_F8 }, - { "F9", ImGuiKey_F9 }, - { "F10", ImGuiKey_F10 }, - { "F11", ImGuiKey_F11 }, - { "F12", ImGuiKey_F12 }, - - { "Up", ImGuiKey_UpArrow }, - { "Down", ImGuiKey_DownArrow }, - { "Left", ImGuiKey_LeftArrow }, - { "Right", ImGuiKey_RightArrow }, - - { "Space", ImGuiKey_Space }, - { "Enter", ImGuiKey_Enter }, - { "Escape", ImGuiKey_Escape }, - { "Tab", ImGuiKey_Tab }, - { "Backspace", ImGuiKey_Backspace }, - { "Delete", ImGuiKey_Delete }, - { "Insert", ImGuiKey_Insert }, - { "Home", ImGuiKey_Home }, - { "End", ImGuiKey_End }, - { "PageUp", ImGuiKey_PageUp }, - { "PageDown", ImGuiKey_PageDown }, - - { "Minus", ImGuiKey_Minus }, - { "Equal", ImGuiKey_Equal }, - { "LeftBracket", ImGuiKey_LeftBracket }, - { "RightBracket", ImGuiKey_RightBracket }, - { "Semicolon", ImGuiKey_Semicolon }, - { "Apostrophe", ImGuiKey_Apostrophe }, - { "Comma", ImGuiKey_Comma }, - { "Period", ImGuiKey_Period }, - { "Slash", ImGuiKey_Slash }, - { "Backslash", ImGuiKey_Backslash }, - { "GraveAccent", ImGuiKey_GraveAccent }, - - { "MouseLeft", ImGuiKey_MouseLeft }, - { "MouseRight", ImGuiKey_MouseRight }, - { "MouseMiddle", ImGuiKey_MouseMiddle }, - { "MouseX1", ImGuiKey_MouseX1 }, - { "MouseX2", ImGuiKey_MouseX2 } -}; - -static std::unordered_map IMGUI_MOD_MAP = -{ - { "Ctrl", ImGuiMod_Ctrl }, - { "Shift", ImGuiMod_Shift }, - { "Alt", ImGuiMod_Alt }, - { "Super", ImGuiMod_Super }, -}; - -static inline ImGuiKey imgui_key_from_char_get(char c) -{ - if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; - if (c >= 'A' && c <= 'Z') return (ImGuiKey)(ImGuiKey_A + (c - 'A')); - return ImGuiKey_None; -} - -static inline std::string imgui_string_from_chord_get(ImGuiKeyChord chord) -{ - std::string result; - - if (chord & ImGuiMod_Ctrl) result += "Ctrl+"; - if (chord & ImGuiMod_Shift) result += "Shift+"; - if (chord & ImGuiMod_Alt) result += "Alt+"; - if (chord & ImGuiMod_Super) result += "Super+"; - - ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); - - if (key != ImGuiKey_None) - { - const char* name = ImGui::GetKeyName(key); - if (name && *name) - result += name; - else - result += "Unknown"; - } - - if (!result.empty() && result.back() == '+') - result.pop_back(); - - return result; -} - -static inline ImGuiKeyChord imgui_chord_from_string_get(const std::string& str) -{ - ImGuiKeyChord chord = 0; - ImGuiKey baseKey = ImGuiKey_None; - - std::stringstream ss(str); - std::string token; - while (std::getline(ss, token, '+')) - { - // trim - token.erase(0, token.find_first_not_of(" \t\r\n")); - token.erase(token.find_last_not_of(" \t\r\n") + 1); - - if (token.empty()) - continue; - - if (auto it = IMGUI_MOD_MAP.find(token); it != IMGUI_MOD_MAP.end()) { - chord |= it->second; - } - else if (baseKey == ImGuiKey_None) { - if (auto it2 = IMGUI_KEY_MAP.find(token); it2 != IMGUI_KEY_MAP.end()) - baseKey = it2->second; - } - } - - if (baseKey != ImGuiKey_None) - chord |= baseKey; - - return chord; + return chord; } static inline void imgui_keyboard_nav_enable(void) { ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; } static inline void imgui_keyboard_nav_disable(void) { ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; } static inline void imgui_contextual_actions_enable(Imgui* self) { self->isContextualActionsEnabled = true; } -static inline void imgui_contextual_actions_disable(Imgui* self){ self->isContextualActionsEnabled = false; } +static inline void imgui_contextual_actions_disable(Imgui* self) { self->isContextualActionsEnabled = false; } static inline bool imgui_is_popup_open(const std::string& label) { return ImGui::IsPopupOpen(label.c_str()); } - -static inline bool imgui_is_any_popup_open(void) -{ - return ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId); -} - +static inline bool imgui_is_any_popup_open(void) { return ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId); } static inline void imgui_open_popup(const std::string& label) { ImGui::OpenPopup(label.c_str()); } -static inline void imgui_pending_popup_process(Imgui* self) -{ - if (self->pendingPopup.empty()) return; +static inline void imgui_pending_popup_process(Imgui* self) { + if (self->pendingPopup.empty()) + return; - switch (self->pendingPopupType) - { - case IMGUI_POPUP_CENTER_WINDOW: - ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); - break; - case IMGUI_POPUP_BY_ITEM: - default: - ImGui::SetNextWindowPos(self->pendingPopupPosition); - break; - } + switch (self->pendingPopupType) { + case IMGUI_POPUP_CENTER_WINDOW: + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + break; + case IMGUI_POPUP_BY_ITEM: + default: + ImGui::SetNextWindowPos(self->pendingPopupPosition); + break; + } - imgui_open_popup(self->pendingPopup.c_str()); + if (!imgui_is_any_popup_open()) + imgui_open_popup(self->pendingPopup.c_str()); - self->pendingPopup.clear(); - self->pendingPopupType = IMGUI_POPUP_NONE; - self->pendingPopupPosition = ImVec2(); + self->pendingPopup.clear(); + self->pendingPopupType = IMGUI_POPUP_NONE; + self->pendingPopupPosition = ImVec2(); } -static inline bool imgui_begin_popup(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) -{ - imgui_pending_popup_process(imgui); - if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); - bool isActivated = ImGui::BeginPopup(label.c_str(), IMGUI_POPUP_FLAGS); - return isActivated; +static inline bool imgui_begin_popup(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) { + imgui_pending_popup_process(imgui); + if (size != ImVec2()) + ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); + bool isActivated = ImGui::BeginPopup(label.c_str(), IMGUI_POPUP_FLAGS); + return isActivated; } -static inline bool imgui_begin_popup_modal(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) -{ - imgui_pending_popup_process(imgui); - if (size != ImVec2()) ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); - bool isActivated = ImGui::BeginPopupModal(label.c_str(), nullptr, IMGUI_POPUP_MODAL_FLAGS); - if (isActivated) imgui_contextual_actions_disable(imgui); - return isActivated; +static inline bool imgui_begin_popup_modal(const std::string& label, Imgui* imgui, ImVec2 size = ImVec2()) { + imgui_pending_popup_process(imgui); + if (size != ImVec2()) + ImGui::SetNextWindowSizeConstraints(size, ImVec2(FLT_MAX, FLT_MAX)); + bool isActivated = ImGui::BeginPopupModal(label.c_str(), nullptr, IMGUI_POPUP_MODAL_FLAGS); + if (isActivated) + imgui_contextual_actions_disable(imgui); + return isActivated; } -static inline void imgui_close_current_popup(Imgui* imgui) -{ - imgui_contextual_actions_enable(imgui); - ImGui::CloseCurrentPopup(); +static inline void imgui_close_current_popup(Imgui* imgui) { + imgui_contextual_actions_enable(imgui); + ImGui::CloseCurrentPopup(); } -static inline void imgui_end_popup(Imgui* imgui) -{ - ImGui::EndPopup(); - imgui_pending_popup_process(imgui); +static inline void imgui_end_popup(Imgui* imgui) { + ImGui::EndPopup(); + imgui_pending_popup_process(imgui); } -enum ImguiItemType -{ - IMGUI_ITEM, - IMGUI_TEXT, - IMGUI_WINDOW, - IMGUI_DOCKSPACE, - IMGUI_CHILD, - IMGUI_TABLE, - IMGUI_CONFIRM_POPUP, - IMGUI_SELECTABLE, - IMGUI_BUTTON, - IMGUI_RADIO_BUTTON, - IMGUI_COLOR_BUTTON, - IMGUI_CHECKBOX, - IMGUI_INPUT_INT, - IMGUI_INPUT_TEXT, - IMGUI_INPUT_FLOAT, - IMGUI_SLIDER_FLOAT, - IMGUI_DRAG_FLOAT, - IMGUI_COLOR_EDIT, - IMGUI_COMBO, - IMGUI_ATLAS_BUTTON -}; - -struct ImguiItemOverride -{ - bool isDisabled{}; - bool isSelected{}; - std::string label{}; - ImVec2 size{}; - s32 id{}; - s32 max{}; - AtlasType atlas = ATLAS_NONE; - bool isMnemonicDisabled{}; - s32 value{}; - s32 rowCount{}; +enum ImguiItemType { + IMGUI_ITEM, + IMGUI_TEXT, + IMGUI_IMAGE, + IMGUI_WINDOW, + IMGUI_DOCKSPACE, + IMGUI_BEGIN_CHILD, + IMGUI_END_CHILD, + IMGUI_TABLE, + IMGUI_CONFIRM_POPUP, + IMGUI_SELECTABLE, + IMGUI_BUTTON, + IMGUI_RADIO_BUTTON, + IMGUI_COLOR_BUTTON, + IMGUI_CHECKBOX, + IMGUI_INPUT_INT, + IMGUI_INPUT_TEXT, + IMGUI_INPUT_FLOAT, + IMGUI_SLIDER_FLOAT, + IMGUI_DRAG_FLOAT, + IMGUI_COLOR_EDIT, + IMGUI_COMBO, + IMGUI_ATLAS_TEXT, + IMGUI_ATLAS_BUTTON }; struct ImguiItem; -static std::vector& imgui_item_registry(void) -{ - static std::vector registry; - return registry; +static std::vector& imgui_item_registry(void) { + static std::vector registry; + return registry; } -static ImGuiKeyChord* imgui_hotkey_chord_registry(void) -{ - static ImGuiKeyChord registry[HOTKEY_COUNT]; - return registry; +static ImGuiKeyChord* imgui_hotkey_chord_registry(void) { + static ImGuiKeyChord registry[HOTKEY_COUNT]; + return registry; } -typedef void(*ImguiFunction)(Imgui*); +typedef void (*ImguiFunction)(Imgui*); -// Item -struct ImguiItem -{ - std::string label{}; - std::string tooltip{}; - std::string& text = tooltip; - std::string snapshotAction{}; - std::string popup{}; - std::string dragDrop{}; - std::string focusWindow{}; - std::vector items{}; - AtlasType atlas = ATLAS_NONE; - ImGuiKeyChord chord = IMGUI_CHORD_NONE; - HotkeyType hotkey = HOTKEY_NONE; - ImGuiKey mnemonicKey = ImGuiKey_None; - s32 mnemonicIndex = INDEX_NONE; - ImVec2 size{}; - ImVec2 popupSize{}; - ImguiColorSet color{}; - ImguiFunction function = nullptr; - ImguiPopupType popupType = IMGUI_POPUP_CENTER_WINDOW; - bool isDisabled = false; - bool isMnemonicDisabled = false; - bool isEmptyFormat = false; - bool isSelected = false; - bool isUseItemActivated = false; - bool isSizeToText = false; - bool isHotkeyInLabel = false; - bool isSameLine = false; - bool isSeparator = false; - s32 id = 0; - s32 idOffset{}; - f32 speed = 0.25f; - s32 step = 1; - s32 stepFast = 10; - s32 min{}; - s32 max{}; - s32 value{}; - vec2 atlasOffset{}; - s32 border{}; - s32 flags{}; - s32 windowFlags{}; - s32 rowCount = 0; +#define IMGUI_ITEM_MEMBERS \ + X(label, std::string, false, {}) \ + X(tooltip, std::string, false, {}) \ + X(snapshotAction, std::string, true, {}) \ + X(popup, std::string, false, {}) \ + X(dragDrop, std::string, true, {}) \ + X(focusWindow, std::string, true, {}) \ + X(items, std::vector, false, {}) \ + X(atlas, AtlasType, false, ATLAS_NONE) \ + X(textureID, int, false, ID_NONE) \ + X(chord, ImGuiKeyChord, true, {}) \ + X(hotkey, HotkeyType, true, {}) \ + X(mnemonicKey, ImGuiKey, false, ImGuiKey_None) \ + X(mnemonicIndex, int, false, INDEX_NONE) \ + X(size, vec2, true, {}) \ + X(uvMin, vec2, false, vec2()) \ + X(uvMax, vec2, false, vec2(1.0f)) \ + X(popupSize, vec2, false, {}) \ + X(color, ImguiColorSet, false, {}) \ + X(function, ImguiFunction, false, {}) \ + X(popupType, ImguiPopupType, false, IMGUI_POPUP_CENTER_WINDOW) \ + X(isDisabled, bool, false, false) \ + X(isSelected, bool, false, false) \ + X(isMnemonicDisabled, bool, false, false) \ + X(isEmptyFormat, bool, false, false) \ + X(isUseItemActivated, bool, false, false) \ + X(isSizeToText, bool, false, false) \ + X(isSizeToRegion, bool, false, false) \ + X(isHotkeyInLabel, bool, false, false) \ + X(isSameLine, bool, false, false) \ + X(isSeparator, bool, false, false) \ + X(id, int, false, 0) \ + X(idOffset, int, false, {}) \ + X(speed, float, false, 0.25f) \ + X(step, float, false, 1.0f) \ + X(stepFast, float, false, 10.0f) \ + X(min, int, true, {}) \ + X(max, int, true, {}) \ + X(value, int, false, {}) \ + X(atlasOffset, vec2, false, {}) \ + X(cursorPosition, vec2, true, {}) \ + X(cursorOffset, vec2, false, {}) \ + X(itemSpacing, vec2, true, {}) \ + X(windowPadding, vec2, true, {}) \ + X(framePadding, vec2, true, {}) \ + X(border, int, true, {}) \ + X(flags, int, false, {}) \ + X(windowFlags, int, false, {}) \ + X(rowCount, int, true, {}) - bool is_border() const { return border != 0; } - bool is_row() const { return rowCount != 0; } - bool is_hotkey() const { return hotkey != HOTKEY_NONE; } - bool is_chord() const { return chord != IMGUI_CHORD_NONE || is_hotkey(); } - bool is_drag_drop() const { return !dragDrop.empty(); } - bool is_focus_window() const { return !focusWindow.empty(); } - bool is_popup() const { return !popup.empty(); } - bool is_function() const { return function; } - bool is_size() const { return size != ImVec2(); } - bool is_popup_size() const { return popupSize != ImVec2(); } - bool is_tooltip() const { return !tooltip.empty(); } - bool is_undoable() const { return !snapshotAction.empty(); } - bool is_mnemonic() const { return mnemonicKey != ImGuiKey_None; } - bool is_range() const { return min != 0 || max != 0; } - const char* drag_drop_get() const { return dragDrop.c_str(); } - const char* text_get() const { return text.c_str(); } - - void construct() - { - static s32 idNew = 0; - id = idNew++; - - imgui_item_registry().push_back(this); - - std::string labelNew{}; - - for (s32 i = 0; i < (s32)label.size(); i++) - { - if (label[i] == '&') - { - if (label[i + 1] == '&') - { - labelNew += '&'; - i++; - } - else if (label[i + 1] != '\0') - { - mnemonicKey = imgui_key_from_char_get(label[i + 1]); - mnemonicIndex = (s32)labelNew.size(); - labelNew += label[i + 1]; - i++; - } - } - else - labelNew += label[i]; - } - - label = labelNew; - } - - ImguiItem copy(const ImguiItemOverride& override) const - { - ImguiItem out = *this; - if (override.isDisabled) out.isDisabled = override.isDisabled; - if(override.isSelected) out.isSelected = override.isSelected; - if (is_popup() && imgui_is_popup_open(popup)) out.isSelected = true; - if (id != ID_NONE) out.id = id + idOffset + override.id; - if (!override.label.empty()) out.label = override.label; - if (override.size != ImVec2{}) out.size = override.size; - if (override.max != 0) out.max = override.max; - if (override.value != 0) out.value = override.value; - if (override.rowCount != 0) out.rowCount = override.rowCount; - if (override.atlas != ATLAS_NONE) out.atlas = override.atlas; - if (override.isMnemonicDisabled) out.isMnemonicDisabled = override.isMnemonicDisabled; - return out; - } - - ImGuiKeyChord chord_get() const - { - if (is_hotkey()) return imgui_hotkey_chord_registry()[hotkey]; - return chord; - } - - std::string label_get() const - { - std::string newLabel = label; - if (isHotkeyInLabel) - newLabel += std::format(IMGUI_LABEL_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get())); - return newLabel; - } - - std::string tooltip_get() const - { - std::string newTooltip = tooltip; - if (is_chord()) - newTooltip += std::format(IMGUI_TOOLTIP_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get())); - return newTooltip; - } +struct ImguiItemOverride { +#define X(name, type, isOptional, ...) std::optional name = {}; + IMGUI_ITEM_MEMBERS +#undef X }; -#define IMGUI_ITEM(NAME, ...) const inline ImguiItem NAME = []{ ImguiItem self; __VA_ARGS__; self.construct(); return self; }() +struct ImguiItem { +#define X(name, type, isOptional, ...) std::conditional_t, type> name = __VA_ARGS__; + IMGUI_ITEM_MEMBERS +#undef X -IMGUI_ITEM(IMGUI_WINDOW_MAIN, - self.label = "## Window", - self.flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus -); + bool is_chord() const { return chord.has_value() || hotkey.has_value(); } + bool is_mnemonic() const { return mnemonicKey != ImGuiKey_None; } + bool is_range() const { return min != 0 || max != 0; } + const char* drag_drop_get() const { return dragDrop->c_str(); } + const char* text_get() const { return tooltip.c_str(); } -IMGUI_ITEM(IMGUI_DOCKSPACE_MAIN, - self.label = "## Dockspace", - self.flags = ImGuiDockNodeFlags_PassthruCentralNode -); + void construct() { + static int idNew = 0; + id = idNew++; -IMGUI_ITEM(IMGUI_FOOTER_CHILD, - self.label = "## Footer Child", - self.size = {0, 36}, - self.flags = true -); + imgui_item_registry().push_back(this); -IMGUI_ITEM(IMGUI_TASKBAR, - self.label = "Taskbar", - self.size = {0, 32}, - self.flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoSavedSettings -); + std::string labelNew{}; -IMGUI_ITEM(IMGUI_FILE, - self.label = "&File", - self.tooltip = "Opens the file menu, for reading/writing anm2 files.", - self.popup = "## File Popup", - self.popupType = IMGUI_POPUP_BY_ITEM, - self.isSizeToText = true, - self.isSameLine = true -); + for (int i = 0; i < (int)label.size(); i++) { + if (label[i] == '&') { + if (label[i + 1] == '&') { + labelNew += '&'; + i++; + } else if (label[i + 1] != '\0') { + mnemonicKey = imgui_key_from_char_get(label[i + 1]); + mnemonicIndex = (int)labelNew.size(); + labelNew += label[i + 1]; + i++; + } + } else + labelNew += label[i]; + } -IMGUI_ITEM(IMGUI_NEW, - self.label = "&New ", - self.tooltip = "Load a blank .anm2 file to edit.", - self.function = imgui_file_new, - self.hotkey = HOTKEY_NEW, - self.isSizeToText = true, - self.isHotkeyInLabel = true -); + label = labelNew; + } -IMGUI_ITEM(IMGUI_OPEN, - self.label = "&Open ", - self.tooltip = "Open an existing .anm2 file to edit.", - self.function = imgui_file_open, - self.hotkey = HOTKEY_OPEN, - self.isSizeToText = true, - self.isHotkeyInLabel = true -); + ImguiItem copy(const ImguiItemOverride& override) const { + ImguiItem out = *this; -IMGUI_ITEM(IMGUI_SAVE, - self.label = "&Save ", - self.tooltip = "Saves the current .anm2 file to its path.\nIf no path exists, one can be chosen.", - self.function = imgui_file_save, - self.hotkey = HOTKEY_SAVE, - self.isSizeToText = true, - self.isHotkeyInLabel = true -); +#define X(name, type, value, isOptional) \ + if (override.name.has_value()) \ + out.name = *override.name; + IMGUI_ITEM_MEMBERS +#undef X -IMGUI_ITEM(IMGUI_SAVE_AS, - self.label = "S&ave As ", - self.tooltip = "Saves the current .anm2 file to a chosen path.", - self.function = imgui_file_save_as, - self.hotkey = HOTKEY_SAVE_AS, - self.isSizeToText = true, - self.isHotkeyInLabel = true -); + return out; + } -IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION, - self.label = "E&xplore Anm2 Location", - self.tooltip = "Open the system's file explorer in the anm2's path.", - self.function = imgui_explore, - self.isSizeToText = true, - self.isSeparator = true -); + ImGuiKeyChord chord_get() const { + if (hotkey.has_value()) + return imgui_hotkey_chord_registry()[*hotkey]; + if (chord.has_value()) + return *chord; + return ImGuiKey_None; + } -IMGUI_ITEM(IMGUI_EXIT, - self.label = "&Exit ", - self.tooltip = "Exits the program.", - self.function = imgui_quit, - self.hotkey = HOTKEY_EXIT, - self.isSizeToText = true, - self.isHotkeyInLabel = true -); + std::string label_get() const { + std::string newLabel = label; + if (isHotkeyInLabel) + newLabel += std::format(IMGUI_LABEL_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get())); + return newLabel; + } -IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, - self.label = "Exit Confirmation", - self.text = "Unsaved changes will be lost!\nAre you sure you want to exit?" -); + std::string tooltip_get() const { + std::string newTooltip = tooltip; + if (is_chord()) + newTooltip += std::format(IMGUI_TOOLTIP_HOTKEY_FORMAT, imgui_string_from_chord_get(chord_get())); + return newTooltip; + } +}; -IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION, - self.label = "Open Confirmation", - self.text = "Unsaved changes will be lost!\nAre you sure you open a new file?" -); +#define IMGUI_ITEM(NAME, ...) \ + const inline ImguiItem NAME = [] { \ + ImguiItem self; \ + __VA_ARGS__; \ + self.construct(); \ + return self; \ + }() -IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION, - self.label = "No Anm2 Path", - self.text = "You will need to load or make a new .anm2 file first!\n" -); +IMGUI_ITEM(IMGUI_WINDOW_MAIN, self.label = "## Window", + self.flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus); -IMGUI_ITEM(IMGUI_WIZARD, - self.label = "&Wizard", - self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", - self.popup = "## Wizard Popup", - self.popupType = IMGUI_POPUP_BY_ITEM, - self.isSizeToText = true, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_DOCKSPACE_MAIN, self.label = "## Dockspace", self.flags = ImGuiDockNodeFlags_PassthruCentralNode); + +IMGUI_ITEM(IMGUI_FOOTER_CHILD, self.label = "## Footer Child", self.size = {0, 42}, self.flags = true); + +IMGUI_ITEM(IMGUI_TASKBAR, self.label = "Taskbar", self.size = {0, 32}, + self.flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings); + +IMGUI_ITEM(IMGUI_FILE, self.label = "&File", self.tooltip = "Opens the file menu, for reading/writing anm2 files.", self.popup = "## File Popup", + self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); + +IMGUI_ITEM(IMGUI_NEW, self.label = "&New", self.tooltip = "Load a blank .anm2 file to edit.", self.function = imgui_file_new, self.hotkey = HOTKEY_NEW, + self.isSizeToText = true, self.isHotkeyInLabel = true); + +IMGUI_ITEM(IMGUI_OPEN, self.label = "&Open", self.tooltip = "Open an existing .anm2 file to edit.", self.function = imgui_file_open, self.hotkey = HOTKEY_OPEN, + self.isSizeToText = true, self.isHotkeyInLabel = true); + +IMGUI_ITEM(IMGUI_SAVE, self.label = "&Save", + self.tooltip = "Saves the current .anm2 file to its path.\nIf no " + "path exists, one can be chosen.", + self.function = imgui_file_save, self.hotkey = HOTKEY_SAVE, self.isSizeToText = true, self.isHotkeyInLabel = true); + +IMGUI_ITEM(IMGUI_SAVE_AS, self.label = "S&ave As", self.tooltip = "Saves the current .anm2 file to a chosen path.", self.function = imgui_file_save_as, + self.hotkey = HOTKEY_SAVE_AS, self.isSizeToText = true, self.isHotkeyInLabel = true); + +IMGUI_ITEM(IMGUI_EXPLORE_ANM2_LOCATION, self.label = "E&xplore Anm2 Location", self.tooltip = "Open the system's file explorer in the anm2's path.", + self.function = imgui_explore, self.isSizeToText = true, self.isSeparator = true); + +IMGUI_ITEM(IMGUI_EXIT, self.label = "&Exit", self.tooltip = "Exits the program.", self.function = imgui_quit, self.hotkey = HOTKEY_EXIT, + self.isSizeToText = true, self.isHotkeyInLabel = true); + +IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, self.label = "Exit Confirmation", self.tooltip = "Unsaved changes will be lost!\nAre you sure you want to exit?"); + +IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION, self.label = "Open Confirmation", self.tooltip = "Unsaved changes will be lost!\nAre you sure you open a new file?"); + +IMGUI_ITEM(IMGUI_NO_ANM2_PATH_CONFIRMATION, self.label = "No Anm2 Path", self.tooltip = "You will need to load or make a new .anm2 file first!\n"); + +IMGUI_ITEM(IMGUI_WIZARD, self.label = "&Wizard", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", + self.popup = "## Wizard Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); #define IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING 40 -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID, - self.label = "&Generate Animation from Grid", - self.tooltip = "Generate a new animation from grid values.", - self.popup = "Generate Animation from Grid", - self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = - { - (GENERATE_PREVIEW_SIZE.x * 2) + IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING, - GENERATE_PREVIEW_SIZE.y + (IMGUI_FOOTER_CHILD.size.y * 2) + (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING / 2) - } -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID, self.label = "&Generate Animation from Grid", self.tooltip = "Generate a new animation from grid values.", + self.popup = "Generate Animation from Grid", self.popupType = IMGUI_POPUP_CENTER_WINDOW, + self.popupSize = {(GENERATE_PREVIEW_SIZE.x * 2) + IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING, + GENERATE_PREVIEW_SIZE.y + (IMGUI_FOOTER_CHILD.size->y * 2) + (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING * 0.5f)}); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, - self.label = "## Generate Animation From Grid Options Child", - self.size = - { - IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2, - IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size.y - }, - self.flags = true -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_OPTIONS_CHILD, self.label = "## Generate Animation From Grid Options Child", + self.size = {IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x * 0.5f, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, + self.flags = true); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, - self.label = "Start Position", - self.tooltip = "Set the starting position on the layer's spritesheet for the generated animation." -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_START_POSITION, self.label = "Start Position", + self.tooltip = "Set the starting position on the layer's " + "spritesheet for the generated animation."); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, - self.label = "Size", - self.tooltip = "Set the size of each frame in the generated animation." -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SIZE, self.label = "Size", self.tooltip = "Set the size of each frame in the generated animation."); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, - self.label = "Pivot", - self.tooltip = "Set the pivot of each frame in the generated animation." -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, self.label = "Pivot", self.tooltip = "Set the pivot of each frame in the generated animation."); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, - self.label = "Rows", - self.tooltip = "Set how many rows will be used in the generated animation.", - self.min = 1, - self.max = 1000 -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self.label = "Rows", self.tooltip = "Set how many rows will be used in the generated animation.", + self.min = 1, self.max = 1000); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, - self.label = "Columns", - self.tooltip = "Set how many columns will be used in the generated animation.", - self.min = 1, - self.max = 1000 -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self.label = "Columns", self.tooltip = "Set how many columns will be used in the generated animation.", + self.min = 1, self.max = 1000); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT, - self.label = "Count", - self.tooltip = "Set how many frames will be made for the generated animation.", - self.value = ANM2_FRAME_NUM_MIN -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COUNT, self.label = "Count", self.tooltip = "Set how many frames will be made for the generated animation.", + self.value = ANM2_FRAME_NUM_MIN); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, - self.label = "Delay", - self.tooltip = "Set the delay of each frame in the generated animation.", - self.max = 1000 -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_DELAY, self.label = "Delay", self.tooltip = "Set the delay of each frame in the generated animation.", + self.max = 1000); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, - self.label = "## Generate Animation From Grid Preview Child", - self.size = - { - IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2, - IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size.y - }, - self.flags = true -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PREVIEW_CHILD, self.label = "## Generate Animation From Grid Preview Child", + self.size = {IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x * 0.5f, IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, + self.flags = true); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, - self.label = "## Generate Animation From Grid Slider Child", - self.size = - { - (IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x / 2) - (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING / 2), - IMGUI_FOOTER_CHILD.size.y - }, - self.flags = true -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER_CHILD, self.label = "## Generate Animation From Grid Slider Child", + self.size = {(IMGUI_GENERATE_ANIMATION_FROM_GRID.popupSize.x * 0.5f) - (IMGUI_GENERATE_ANIMATION_FROM_GRID_PADDING * 0.5f), + IMGUI_FOOTER_CHILD.size->y}, + self.flags = true); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER, - self.label = "## Generate Animation From Grid Slider", - self.tooltip = "Change the time of the generated animation preview.", - self.min = GENERATE_PREVIEW_TIME_MIN, - self.max = GENERATE_PREVIEW_TIME_MAX, - self.value = GENERATE_PREVIEW_TIME_MIN, - self.rowCount = 1, - self.isEmptyFormat = true, - self.flags = ImGuiSliderFlags_NoInput -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_SLIDER, self.label = "## Generate Animation From Grid Slider", + self.tooltip = "Change the time of the generated animation preview.", self.min = GENERATE_PREVIEW_TIME_MIN, self.max = GENERATE_PREVIEW_TIME_MAX, + self.value = GENERATE_PREVIEW_TIME_MIN, self.rowCount = 1, self.isEmptyFormat = true, self.flags = ImGuiSliderFlags_NoInput); -IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, - self.label = "Generate", - self.tooltip = "Generate an animation with the used settings.", - self.snapshotAction = "Generate Animation from Grid", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_GENERATE, self.label = "Generate", self.tooltip = "Generate an animation with the used settings.", + self.snapshotAction = "Generate Animation from Grid", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES, - self.label = "&Change All Frame Properties", - self.tooltip = "Change all frame properties in the selected animation item (or selected frame).", - self.popup = "Change All Frame Properties", - self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = {500, 405} -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES, self.label = "&Change All Frame Properties", + self.tooltip = "Change all frame properties in the selected " + "animation item (or selected frame).", + self.popup = "Change All Frame Properties", self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {500, 405}); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, - self.label = "## Change All Frame Properties Child", - self.size = - { - IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, - 275 - }, - self.flags = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, self.label = "## Change All Frame Properties Child", + self.size = {IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, 275}, self.flags = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, - self.label = "## Change All Frame Properties Settings Child", - self.size = - { - IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, - 55 - }, - self.flags = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS_CHILD, self.label = "## Change All Frame Properties Settings Child", + self.size = {IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, 55}, self.flags = true); IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SETTINGS, self.label = "Settings"); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, - self.label = "From Selected Frames", - self.tooltip = "The set frame properties will start from the selected frame.", - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_FROM_SELECTED_FRAME, self.label = "From Selected Frames", + self.tooltip = "The set frame properties will start from the selected frame.", self.isSameLine = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES, - self.label = "# of Frames", - self.tooltip = "Set the amount of frames that the set frame properties will apply to.", - self.size = {200, 0}, - self.value = ANM2_FRAME_NUM_MIN, - self.max = 1000 -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_NUMBER_FRAMES, self.label = "# of Frames", + self.tooltip = "Set the amount of frames that the set frame properties will apply to.", self.size = {200, 0}, self.value = ANM2_FRAME_NUM_MIN, + self.max = 1000); #define IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT 4 -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, - self.label = "Add", - self.tooltip = "The specified values will be added to all specified frames.", - self.snapshotAction = "Add Frame Properties", - self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, self.label = "Add", self.tooltip = "The specified values will be added to all specified frames.", + self.snapshotAction = "Add Frame Properties", self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, - self.label = "Subtract", - self.tooltip = "The specified values will be subtracted from all selected frames.", - self.snapshotAction = "Subtract Frame Properties", - self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, self.label = "Subtract", + self.tooltip = "The specified values will be subtracted from all selected frames.", self.snapshotAction = "Subtract Frame Properties", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, - self.label = "Set", - self.tooltip = "The specified values will be set to the specified value in selected frames.", - self.snapshotAction = "Set Frame Properties", - self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SET, self.label = "Set", + self.tooltip = "The specified values will be set to the specified " + "value in selected frames.", + self.snapshotAction = "Set Frame Properties", self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, - self.label = "Cancel", - self.tooltip = "Cancel changing all frame properties.", - self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT -); +IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CANCEL, self.label = "Cancel", self.tooltip = "Cancel changing all frame properties.", + self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT); -IMGUI_ITEM(IMGUI_SCALE_ANM2, - self.label = "S&cale Anm2", - self.tooltip = "Scale up all size and position-related frame properties in the anm2.", - self.popup = "Scale Anm2", - self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = {260, 75}, - self.isSizeToText = true, - self.isSeparator = true -); +IMGUI_ITEM(IMGUI_SCALE_ANM2, self.label = "S&cale Anm2", self.tooltip = "Scale up all size and position-related frame properties in the anm2.", + self.popup = "Scale Anm2", self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {260, 75}, self.isSizeToText = true, + self.isSeparator = true); -IMGUI_ITEM(IMGUI_SCALE_ANM2_OPTIONS_CHILD, - self.label = "## Scale Anm2 Options Child", - self.size = {IMGUI_SCALE_ANM2.popupSize.x, IMGUI_SCALE_ANM2.popupSize.y - IMGUI_FOOTER_CHILD.size.y}, - self.flags = true -); +IMGUI_ITEM(IMGUI_SCALE_ANM2_OPTIONS_CHILD, self.label = "## Scale Anm2 Options Child", + self.size = {IMGUI_SCALE_ANM2.popupSize.x, IMGUI_SCALE_ANM2.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, self.flags = true); -IMGUI_ITEM(IMGUI_SCALE_ANM2_VALUE, - self.label = "Value", - self.tooltip = "The size and position-related frame properties in the anm2 will be scaled by this value.", - self.value = 1, - self.step = 0.25, - self.stepFast = 1 -); +IMGUI_ITEM(IMGUI_SCALE_ANM2_VALUE, self.label = "Value", + self.tooltip = "The size and position-related frame properties in " + "the anm2 will be scaled by this value.", + self.value = 1, self.step = 0.25, self.stepFast = 1); -IMGUI_ITEM(IMGUI_SCALE_ANM2_SCALE, - self.label = "Scale", - self.tooltip = "Scale the anm2 with the value specified.", - self.snapshotAction = "Scale Anm2", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SCALE_ANM2_SCALE, self.label = "Scale", self.tooltip = "Scale the anm2 with the value specified.", self.snapshotAction = "Scale Anm2", + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION, - self.label = "&Render Animation", - self.tooltip = "Renders the current animation preview; output options can be customized.", - self.popup = "Render Animation", - self.popupSize = {500, 170}, - self.popupType = IMGUI_POPUP_CENTER_WINDOW -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION, self.label = "&Render Animation", + self.tooltip = "Renders the current animation preview; output " + "options can be customized.", + self.popup = "Render Animation", self.popupSize = {500, 170}, self.popupType = IMGUI_POPUP_CENTER_WINDOW); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, - self.label = "## Render Animation Child", - self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_RENDER_ANIMATION.popupSize.y - IMGUI_FOOTER_CHILD.size.y}, - self.flags = true -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CHILD, self.label = "## Render Animation Child", + self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_RENDER_ANIMATION.popupSize.y - IMGUI_FOOTER_CHILD.size->y}, self.flags = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, - self.label = "## Render Animation Footer Child", - self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_FOOTER_CHILD.size.y}, - self.flags = true -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FOOTER_CHILD, self.label = "## Render Animation Footer Child", + self.size = {IMGUI_RENDER_ANIMATION.popupSize.x, IMGUI_FOOTER_CHILD.size->y}, self.flags = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, - self.label = "## Location Browse", - self.tooltip = "Open file explorer to pick rendered animation location.", - self.atlas = ATLAS_FOLDER, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION_BROWSE, self.label = "## Location Browse", self.tooltip = "Open file explorer to pick rendered animation location.", + self.atlas = ATLAS_FOLDER, self.isSameLine = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION, - self.label = "Location", - self.tooltip = "Select the location of the rendered animation.\nFor PNG images, this should be a directory, otherwise, a filepath.", - self.max = 1024 -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_LOCATION, self.label = "Location", + self.tooltip = "Select the location of the rendered animation.\nFor PNG " + "images, this should be a directory, otherwise, a filepath.", + self.max = 1024); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, - self.label = "## FFMpeg Browse", - self.tooltip = "Open file explorer to pick the path of FFmpeg", - self.atlas = ATLAS_FOLDER, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_BROWSE, self.label = "## FFMpeg Browse", self.tooltip = "Open file explorer to pick the path of FFmpeg", + self.atlas = ATLAS_FOLDER, self.isSameLine = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, - self.label = "FFmpeg Path", - self.tooltip = "Sets the path FFmpeg currently resides in.\nFFmpeg is required for rendering animations.\nDownload it from https://ffmpeg.org/, your package manager, or wherever else.", - self.max = 1024 -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FFMPEG_PATH, self.label = "FFmpeg Path", + self.tooltip = "Sets the path FFmpeg currently resides in.\nFFmpeg is required " + "for rendering animations.\nDownload it from " + "https://ffmpeg.org/, your package manager, or wherever else.", + self.max = 1024); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, - self.label = "Output", - self.tooltip = "Select the rendered animation output.\nIt can either be one animated image or a sequence of frames.", - self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, - self.value = RENDER_PNG -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, self.label = "Output", + self.tooltip = "Select the rendered animation output.\nIt can either be " + "one animated image or a sequence of frames.", + self.items = {std::begin(RENDER_TYPE_STRINGS), std::end(RENDER_TYPE_STRINGS)}, self.value = RENDER_PNG); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, - self.label = "Format", - self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., its filename.\nThe format will only take one argument; that being the frame's index.\nFor example, a format like \"{}.png\" will export a frame of index 0 as \"0.png\".", - self.max = UCHAR_MAX -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, self.label = "Format", + self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., " + "its filename.\nThe format will only take one argument; that " + "being the frame's index.\nFor example, a format like " + "\"{}.png\" will export a frame of index 0 as \"0.png\".", + self.max = UCHAR_MAX); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_IS_USE_ANIMATION_BOUNDS, - self.label = "Use Animation Bounds", - self.tooltip = "Instead of using the animation preview's bounds, the rendered animation will use the animation's bounds.\nNOTE: If you're looking to make a transparent animation, set the preview background to be transparent\nand toggle off other drawn things.", - self.value = SETTINGS_RENDER_IS_USE_ANIMATION_BOUNDS_DEFAULT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_IS_USE_ANIMATION_BOUNDS, self.label = "Use Animation Bounds", + self.tooltip = "Instead of using the animation preview's bounds, the rendered " + "animation will use the animation's bounds.\nNOTE: If you're looking " + "to make a transparent animation, set the preview background to be " + "transparent\nand toggle off other drawn things.", + self.value = SETTINGS_RENDER_IS_USE_ANIMATION_BOUNDS_DEFAULT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_SCALE, - self.label = "Scale", - self.tooltip = "Change the scale the animation will be rendered at.", - self.value = SETTINGS_RENDER_SCALE_DEFAULT, - self.size = {125, 0} -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_SCALE, self.label = "Scale", self.tooltip = "Change the scale the animation will be rendered at.", + self.value = SETTINGS_RENDER_SCALE_DEFAULT, self.size = {125, 0}); -IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM, - self.label = "Render", - self.tooltip = "Render the animation, with the used settings.", - self.popup = "Rendering Animation...", - self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = {300, 60}, - self.isSameLine = true, - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT -); +IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM, self.label = "Render", self.tooltip = "Render the animation, with the used settings.", + self.popup = "Rendering Animation...", self.popupType = IMGUI_POPUP_CENTER_WINDOW, self.popupSize = {300, 60}, self.isSameLine = true, + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT); -IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CHILD, - self.label = "##Rendering Child", - self.size = {400.0f, 65.0f}, - self.flags = true -); +IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CHILD, self.label = "##Rendering Child", self.size = {400.0f, 65.0f}, self.flags = true); -IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_INFO, self.label = "Recording frames. Once done, the program may halt\nas FFmpeg renders the animation. Please be patient!"); -IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL, - self.label = "Cancel", - self.tooltip = "Cancel rendering the animation.", - self.rowCount = 1 -); +IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_INFO, self.label = "Recording frames. Once done, the program may halt\nas " + "FFmpeg renders the animation. Please be patient!"); +IMGUI_ITEM(IMGUI_RENDERING_ANIMATION_CANCEL, self.label = "Cancel", self.tooltip = "Cancel rendering the animation.", self.rowCount = 1); -IMGUI_ITEM(IMGUI_PLAYBACK, - self.label = "&Playback", - self.tooltip = "Opens the playback menu, for configuring playback settings.", - self.popup = "## Playback Popup", - self.popupType = IMGUI_POPUP_BY_ITEM, - self.isSizeToText = true, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_PLAYBACK, self.label = "&Playback", self.tooltip = "Opens the playback menu, for configuring playback settings.", + self.popup = "## Playback Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ALWAYS_LOOP, - self.label = "&Always Loop", - self.tooltip = "Sets the animation playback to always loop, regardless of the animation's loop setting.", - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_ALWAYS_LOOP, self.label = "&Always Loop", + self.tooltip = "Sets the animation playback to always loop, " + "regardless of the animation's loop setting.", + self.isSizeToText = true); -IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD, - self.label = "&Clamp Playhead", - self.tooltip = "The playhead (draggable icon on timeline) won't be able to exceed the animation length.", - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_CLAMP_PLAYHEAD, self.label = "&Clamp Playhead", + self.tooltip = "The playhead (draggable icon on timeline) won't be " + "able to exceed the animation length.", + self.isSizeToText = true); -IMGUI_ITEM(IMGUI_SETTINGS, - self.label = "&Settings", - self.tooltip = "Opens the setting menu, for configuring general program settings.", - self.popup = "## Settings Popup", - self.popupType = IMGUI_POPUP_BY_ITEM, - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_SETTINGS, self.label = "&Settings", self.tooltip = "Opens the setting menu, for configuring general program settings.", + self.popup = "## Settings Popup", self.popupType = IMGUI_POPUP_BY_ITEM, self.isSizeToText = true); -IMGUI_ITEM(IMGUI_VSYNC, - self.label = "&Vsync", - self.tooltip = "Toggle vertical sync; synchronizes program framerate with your monitor's refresh rate.", - self.isSizeToText = true, - self.isSeparator = true -); +IMGUI_ITEM(IMGUI_VSYNC, self.label = "&Vsync", + self.tooltip = "Toggle vertical sync; synchronizes program " + "framerate with your monitor's refresh rate.", + self.isSizeToText = true, self.isSeparator = true); -IMGUI_ITEM(IMGUI_HOTKEYS, - self.label = "&Hotkeys", - self.tooltip = "Change the program's hotkeys.", - self.popup = "Hotkeys", - self.popupSize = {500, 405}, - self.isSizeToText = true, - self.isSeparator = true -); +IMGUI_ITEM(IMGUI_HOTKEYS, self.label = "&Hotkeys", self.tooltip = "Change the program's hotkeys.", self.popup = "Hotkeys", self.popupSize = {500, 405}, + self.isSizeToText = true, self.isSeparator = true); -IMGUI_ITEM(IMGUI_HOTKEYS_CHILD, - self.label = "## Hotkeys Child", - self.size = {IMGUI_HOTKEYS.popupSize.x, IMGUI_HOTKEYS.popupSize.y - 35}, - self.flags = true -); +IMGUI_ITEM(IMGUI_HOTKEYS_CHILD, self.label = "## Hotkeys Child", self.size = {IMGUI_HOTKEYS.popupSize.x, IMGUI_HOTKEYS.popupSize.y - 35}, self.flags = true); #define IMGUI_HOTKEYS_FUNCTION "Function" #define IMGUI_HOTKEYS_HOTKEY "Hotkey" -IMGUI_ITEM(IMGUI_HOTKEYS_TABLE, - self.label = "## Hotkeys Table", - self.value = 2, - self.flags = ImGuiTableFlags_Borders -); +IMGUI_ITEM(IMGUI_HOTKEYS_TABLE, self.label = "## Hotkeys Table", self.value = 2, self.flags = ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY); -IMGUI_ITEM(IMGUI_HOTKEYS_OPTIONS_CHILD, - self.label = "## Merge Options Child", - self.size = {IMGUI_HOTKEYS.popupSize.x, 35}, - self.flags = true -); +IMGUI_ITEM(IMGUI_HOTKEYS_OPTIONS_CHILD, self.label = "## Merge Options Child", self.size = {IMGUI_HOTKEYS.popupSize.x, 35}, self.flags = true); -IMGUI_ITEM(IMGUI_HOTKEYS_CONFIRM, - self.label = "Confirm", - self.tooltip = "Use these hotkeys.", - self.rowCount = 1 -); +IMGUI_ITEM(IMGUI_HOTKEYS_CONFIRM, self.label = "Confirm", self.tooltip = "Use these hotkeys.", self.rowCount = 1); -IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, - self.label = "&Reset to Default Settings", - self.tooltip = "Reset the program's settings to their default state.", - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, self.label = "&Reset to Default Settings", self.tooltip = "Reset the program's settings to their default state.", + self.isSizeToText = true); -IMGUI_ITEM(IMGUI_LAYERS, - self.label = "Layers", - self.flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_LAYERS, self.label = "Layers", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_LAYERS_CHILD, self.label = "## Layers Child", self.flags = true); -IMGUI_ITEM(IMGUI_LAYER, - self.label = "## Layer Item", - self.dragDrop = "## Layer Drag Drop", - self.atlas = ATLAS_LAYER, - self.idOffset = 3000 -); +IMGUI_ITEM(IMGUI_LAYER, self.label = "## Layer Item", self.dragDrop = "## Layer Drag Drop", self.atlas = ATLAS_LAYER, self.isSizeToRegion = true, + self.idOffset = 3000); -IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID, - self.label = "## Spritesheet ID", - self.tooltip = "Change the spritesheet ID this layer uses.", - self.atlas = ATLAS_SPRITESHEET, - self.size = {50, 0} -); +IMGUI_ITEM(IMGUI_LAYER_SPRITESHEET_ID, self.label = "## Spritesheet ID", self.tooltip = "Change the spritesheet ID this layer uses.", + self.atlas = ATLAS_SPRITESHEET, self.size = {50, 0}); #define IMGUI_LAYERS_OPTIONS_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_LAYER_ADD, - self.label = "Add", - self.tooltip = "Adds a new layer.", - self.snapshotAction = "Add Layer", - self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_LAYER_ADD, self.label = "Add", self.tooltip = "Adds a new layer.", self.snapshotAction = "Add Layer", + self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_LAYER_REMOVE, - self.label = "Remove", - self.tooltip = "Removes the selected layer.\nThis will remove all layer animations that use this layer from all animations.", - self.snapshotAction = "Remove Layer", - self.chord = ImGuiKey_Delete, - self.focusWindow = IMGUI_LAYERS.label, - self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT -); +IMGUI_ITEM(IMGUI_LAYER_REMOVE, self.label = "Remove", + self.tooltip = "Removes the selected layer.\nThis will remove all layer " + "animations that use this layer from all animations.", + self.snapshotAction = "Remove Layer", self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_LAYERS.label, + self.rowCount = IMGUI_LAYERS_OPTIONS_ROW_COUNT); -IMGUI_ITEM(IMGUI_NULLS, - self.label = "Nulls", - self.flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_NULLS, self.label = "Nulls", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_NULLS_CHILD, self.label = "## Nulls Child", self.flags = true); -IMGUI_ITEM(IMGUI_NULL, - self.label = "## Null Item", - self.dragDrop = "## Null Drag Drop", - self.atlas = ATLAS_NULL, - self.idOffset = 4000 -); +IMGUI_ITEM(IMGUI_NULL, self.label = "## Null Item", self.dragDrop = "## Null Drag Drop", self.atlas = ATLAS_NULL, self.idOffset = 4000); #define IMGUI_NULLS_OPTIONS_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_NULL_ADD, - self.label = "Add", - self.tooltip = "Adds a null layer.", - self.snapshotAction = "Add Null", - self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_NULL_ADD, self.label = "Add", self.tooltip = "Adds a null layer.", self.snapshotAction = "Add Null", + self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_NULL_REMOVE, - self.label = "Remove", - self.tooltip = "Removes the selected null.\nThis will remove all null animations that use this null from all animations.", - self.snapshotAction = "Remove Null", - self.chord = ImGuiKey_Delete, - self.focusWindow = IMGUI_NULLS.label, - self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT -); +IMGUI_ITEM(IMGUI_NULL_REMOVE, self.label = "Remove", + self.tooltip = "Removes the selected null.\nThis will remove all null " + "animations that use this null from all animations.", + self.snapshotAction = "Remove Null", self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_NULLS.label, + self.rowCount = IMGUI_NULLS_OPTIONS_ROW_COUNT); -IMGUI_ITEM(IMGUI_ANIMATIONS, - self.label = "Animations", - self.flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_ANIMATIONS, self.label = "Animations", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_ANIMATIONS_CHILD, self.label = "## Animations Child", self.flags = true); -IMGUI_ITEM(IMGUI_ANIMATION, - self.label = "## Animation Item", - self.dragDrop = "## Animation Drag Drop", - self.atlas = ATLAS_ANIMATION, - self.idOffset = 2000 -); +IMGUI_ITEM(IMGUI_ANIMATION, self.label = "## Animation Item", self.dragDrop = "## Animation Drag Drop", self.atlas = ATLAS_ANIMATION, + self.isSizeToRegion = true, self.idOffset = 2000); #define IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT 5 -IMGUI_ITEM(IMGUI_ANIMATION_ADD, - self.label = "Add", - self.tooltip = "Adds a new animation.", - self.snapshotAction = "Add Animation", - self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_ADD, self.label = "Add", self.tooltip = "Adds a new animation.", self.snapshotAction = "Add Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ANIMATION_DUPLICATE, - self.label = "Duplicate", - self.tooltip = "Duplicates the selected animation, placing it after.", - self.snapshotAction = "Duplicate Animation", - self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_DUPLICATE, self.label = "Duplicate", self.tooltip = "Duplicates the selected animation, placing it after.", + self.snapshotAction = "Duplicate Animation", self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ANIMATION_MERGE, - self.label = "Merge", - self.tooltip = "Open the animation merge popup, to merge animations together.", - self.popup = "Merge Animations", - self.popupSize = {300, 400}, - self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_MERGE, self.label = "Merge", self.tooltip = "Open the animation merge popup, to merge animations together.", + self.popup = "Merge Animations", self.popupSize = {300, 400}, self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_MERGE_ANIMATIONS_CHILD, - self.label = "## Merge Animations", - self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 250}, - self.flags = true -); +IMGUI_ITEM(IMGUI_MERGE_ANIMATIONS_CHILD, self.label = "## Merge Animations", self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 250}, self.flags = true); -IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT_CHILD, - self.label = "## Merge On Conflict Child", - self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 75}, - self.flags = true -); +IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT_CHILD, self.label = "## Merge On Conflict Child", self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 75}, self.flags = true); IMGUI_ITEM(IMGUI_MERGE_ON_CONFLICT, self.label = "On Conflict"); -IMGUI_ITEM(IMGUI_MERGE_APPEND_FRAMES, - self.label = "Append Frames ", - self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames appended.", - self.value = ANM2_MERGE_APPEND, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_MERGE_APPEND_FRAMES, self.label = "Append Frames ", + self.tooltip = "On frame conflict, the merged animation will have " + "the selected animations' frames appended.", + self.value = ANM2_MERGE_APPEND, self.isSameLine = true); -IMGUI_ITEM(IMGUI_MERGE_REPLACE_FRAMES, - self.label = "Replace Frames", - self.tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", - self.value = ANM2_MERGE_REPLACE -); +IMGUI_ITEM(IMGUI_MERGE_REPLACE_FRAMES, self.label = "Replace Frames", + self.tooltip = "On frame conflict, the merged animation will have " + "the latest selected animations' frames.", + self.value = ANM2_MERGE_REPLACE); -IMGUI_ITEM(IMGUI_MERGE_PREPEND_FRAMES, - self.label = "Prepend Frames", - self.tooltip = "On frame conflict, the merged animation will have the selected animations' frames prepended.", - self.value = ANM2_MERGE_PREPEND, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_MERGE_PREPEND_FRAMES, self.label = "Prepend Frames", + self.tooltip = "On frame conflict, the merged animation will have " + "the selected animations' frames prepended.", + self.value = ANM2_MERGE_PREPEND, self.isSameLine = true); -IMGUI_ITEM(IMGUI_MERGE_IGNORE, - self.label = "Ignore ", - self.tooltip = "On frame conflict, the merged animation will ignore the other selected animations' frames.", - self.value = ANM2_MERGE_IGNORE -); +IMGUI_ITEM(IMGUI_MERGE_IGNORE, self.label = "Ignore ", + self.tooltip = "On frame conflict, the merged animation will ignore " + "the other selected animations' frames.", + self.value = ANM2_MERGE_IGNORE); -IMGUI_ITEM(IMGUI_MERGE_OPTIONS_CHILD, - self.label = "## Merge Options Child", - self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 35}, - self.flags = true -); +IMGUI_ITEM(IMGUI_MERGE_OPTIONS_CHILD, self.label = "## Merge Options Child", self.size = {IMGUI_ANIMATION_MERGE.popupSize.x, 35}, self.flags = true); -IMGUI_ITEM(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, - self.label = "Delete Animations After Merging", - self.tooltip = "After merging, the selected animations (besides the original) will be deleted." -); +IMGUI_ITEM(IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, self.label = "Delete Animations After Merging", + self.tooltip = "After merging, the selected animations (besides the " + "original) will be deleted."); -IMGUI_ITEM(IMGUI_MERGE_CONFIRM, - self.label = "Merge", - self.tooltip = "Merge the selected animations with the options set.", - self.snapshotAction = "Merge Animations", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_MERGE_CONFIRM, self.label = "Merge", self.tooltip = "Merge the selected animations with the options set.", + self.snapshotAction = "Merge Animations", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ANIMATION_REMOVE, - self.label = "Remove", - self.tooltip = "Remove the selected animation.", - self.snapshotAction = "Remove Animation", - self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, - self.chord = ImGuiKey_Delete, - self.focusWindow = IMGUI_ANIMATIONS.label, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_REMOVE, self.label = "Remove", self.tooltip = "Remove the selected animation.", self.snapshotAction = "Remove Animation", + self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_ANIMATIONS.label, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ANIMATION_DEFAULT, - self.label = "Default", - self.tooltip = "Set the selected animation as the default one.", - self.snapshotAction = "Default Animation", - self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_DEFAULT, self.label = "Default", self.tooltip = "Set the selected animation as the default one.", + self.snapshotAction = "Default Animation", self.rowCount = IMGUI_ANIMATIONS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_EVENTS, - self.label = "Events", - self.flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_EVENTS, self.label = "Events", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); IMGUI_ITEM(IMGUI_EVENTS_CHILD, self.label = "## Events Child", self.flags = true); -IMGUI_ITEM(IMGUI_EVENT, - self.label = "## Event", - self.atlas = ATLAS_EVENT, - self.idOffset = 1000 -); +IMGUI_ITEM(IMGUI_EVENT, self.label = "## Event", self.atlas = ATLAS_EVENT, self.idOffset = 1000); #define IMGUI_EVENTS_OPTIONS_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_EVENTS_ADD, - self.label = "Add", - self.tooltip = "Adds a new event.", - self.snapshotAction = "Add Event", - self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_EVENTS_ADD, self.label = "Add", self.tooltip = "Adds a new event.", self.snapshotAction = "Add Event", + self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_EVENTS_REMOVE_UNUSED, - self.label = "Remove Unused", - self.tooltip = "Removes all unused events (i.e., not being used in any triggers in any animation).", - self.snapshotAction = "Remove Unused Events", - self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT -); +IMGUI_ITEM(IMGUI_EVENTS_REMOVE_UNUSED, self.label = "Remove Unused", + self.tooltip = "Removes all unused events (i.e., not being used in " + "any triggers in any animation).", + self.snapshotAction = "Remove Unused Events", self.rowCount = IMGUI_EVENTS_OPTIONS_ROW_COUNT); -IMGUI_ITEM(IMGUI_SPRITESHEETS, - self.label = "Spritesheets", - self.flags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +// Spritesheets +IMGUI_ITEM(IMGUI_SPRITESHEET_PREVIEW, self.label = "## Spritesheet Preview", self.size = {65, 65}); +IMGUI_ITEM(IMGUI_SPRITESHEETS, self.label = "Spritesheets", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); +IMGUI_ITEM(IMGUI_SPRITESHEETS_CHILD, self.label = "## Spritesheets Child", self.flags = true, self.itemSpacing = vec2(), self.windowPadding = vec2()); +IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD, self.label = "## Spritesheet Child", self.flags = true, self.itemSpacing = vec2(), self.windowPadding = vec2()); +IMGUI_ITEM(IMGUI_SPRITESHEET, self.label = "## Spritesheet", self.dragDrop = "## Spritesheet Drag Drop"); +IMGUI_ITEM(IMGUI_SPRITESHEET_TEXT, self.label = "## Spritesheet Text", self.atlas = ATLAS_SPRITESHEET, self.itemSpacing = vec2(8, 0)); -IMGUI_ITEM(IMGUI_SPRITESHEETS_CHILD, self.label = "## Spritesheets Child", self.flags = true); - -IMGUI_ITEM(IMGUI_SPRITESHEET_CHILD, - self.label = "## Spritesheet Child", - self.rowCount = 1, - self.size = {0, IMGUI_SPRITESHEET_PREVIEW_SIZE.y + 40}, - self.flags = true -); - -IMGUI_ITEM(IMGUI_SPRITESHEET_SELECTED, self.label = "## Spritesheet Selected", self.isSameLine = true); - -IMGUI_ITEM(IMGUI_SPRITESHEET, - self.label = "## Spritesheet", - self.dragDrop = "## Spritesheet Drag Drop", - self.atlas = ATLAS_SPRITESHEET, - self.isSizeToText = true -); - -IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, - self.label = "## Spritesheets Footer Child", - self.size = {0, 60}, - self.flags = true -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_FOOTER_CHILD, self.label = "## Spritesheets Footer Child", self.size = {0, IMGUI_FOOTER_CHILD.size->y * 1.66f}, + self.flags = true); #define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT 4 #define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT 3 -IMGUI_ITEM(IMGUI_SPRITESHEET_ADD, - self.label = "Add", - self.tooltip = "Select a .png image to add as a spritesheet.", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEET_ADD, self.label = "Add", self.tooltip = "Select a .png image to add as a spritesheet.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEETS_RELOAD, - self.label = "Reload", - self.tooltip = "Reload the selected spritesheet(s).", - self.snapshotAction = "Reload Spritesheet(s)", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_RELOAD, self.label = "Reload", self.tooltip = "Reload the selected spritesheet(s).", + self.snapshotAction = "Reload Spritesheet(s)", self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEETS_REPLACE, - self.label = "Replace", - self.tooltip = "Replace the highlighted spritesheet with another.", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_REPLACE, self.label = "Replace", self.tooltip = "Replace the highlighted spritesheet with another.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEETS_REMOVE_UNUSED, - self.label = "Remove Unused", - self.tooltip = "Remove all unused spritesheets in the anm2 (i.e., no layer in any animation uses the spritesheet).", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_REMOVE_UNUSED, self.label = "Remove Unused", + self.tooltip = "Remove all unused spritesheets in the anm2 (i.e., " + "the spritesheet isn't used in any layer animations).", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_COUNT); -IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_ALL, - self.label = "Select All", - self.tooltip = "Select all spritesheets.", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_ALL, self.label = "Select All", self.tooltip = "Select all spritesheets.", self.chord = ImGuiMod_Ctrl + ImGuiKey_A, + self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_NONE, - self.label = "Select None", - self.tooltip = "Unselect all spritesheets.", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEETS_SELECT_NONE, self.label = "Select None", self.tooltip = "Unselect all spritesheets.", self.chord = ImGuiKey_Escape, + self.focusWindow = IMGUI_SPRITESHEETS.label, self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEET_SAVE, - self.label = "Save", - self.tooltip = "Save the selected spritesheets to their original locations.", - self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT -); +IMGUI_ITEM(IMGUI_SPRITESHEET_SAVE, self.label = "Save", self.tooltip = "Save the selected spritesheets to their original locations.", + self.rowCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_COUNT); const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {230, 85}; -IMGUI_ITEM(IMGUI_CANVAS_GRID_CHILD, - self.label = "## Canvas Grid Child", - self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_CANVAS_GRID_CHILD, self.label = "## Canvas Grid Child", self.size = IMGUI_CANVAS_CHILD_SIZE, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_CANVAS_GRID, - self.label = "Grid", - self.tooltip = "Toggles the visiblity of the canvas' grid." -); +IMGUI_ITEM(IMGUI_CANVAS_GRID, self.label = "Grid", self.tooltip = "Toggles the visiblity of the canvas' grid."); -IMGUI_ITEM(IMGUI_CANVAS_GRID_SNAP, - self.label = "Snap", - self.tooltip = "Using the crop tool will snap the points to the nearest grid point." -); +IMGUI_ITEM(IMGUI_CANVAS_GRID_SNAP, self.label = "Snap", self.tooltip = "Using the crop tool will snap the points to the nearest grid point."); -IMGUI_ITEM(IMGUI_CANVAS_GRID_COLOR, - self.label = "Color", - self.tooltip = "Change the color of the canvas' grid.", - self.flags = ImGuiColorEditFlags_NoInputs -); +IMGUI_ITEM(IMGUI_CANVAS_GRID_COLOR, self.label = "Color", self.tooltip = "Change the color of the canvas' grid.", self.flags = ImGuiColorEditFlags_NoInputs); -IMGUI_ITEM(IMGUI_CANVAS_GRID_SIZE, - self.label = "Size", - self.tooltip = "Change the size of the canvas' grid.", - self.min = CANVAS_GRID_MIN, - self.max = CANVAS_GRID_MAX, - self.value = CANVAS_GRID_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_GRID_SIZE, self.label = "Size", self.tooltip = "Change the size of the canvas' grid.", self.min = CANVAS_GRID_MIN, + self.max = CANVAS_GRID_MAX, self.value = CANVAS_GRID_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_GRID_OFFSET, - self.label = "Offset", - self.tooltip = "Change the offset of the canvas' grid, in pixels." -); +IMGUI_ITEM(IMGUI_CANVAS_GRID_OFFSET, self.label = "Offset", self.tooltip = "Change the offset of the canvas' grid, in pixels."); -IMGUI_ITEM(IMGUI_CANVAS_VIEW_CHILD, - self.label = "## View Child", - self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_CANVAS_VIEW_CHILD, self.label = "## View Child", self.size = IMGUI_CANVAS_CHILD_SIZE, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_CANVAS_ZOOM, - self.label = "Zoom", - self.tooltip = "Change the zoom of the canvas.", - self.min = CANVAS_ZOOM_MIN, - self.max = CANVAS_ZOOM_MAX, - self.speed = 1.0f, - self.value = CANVAS_ZOOM_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_ZOOM, self.label = "Zoom", self.tooltip = "Change the zoom of the canvas.", self.min = CANVAS_ZOOM_MIN, self.max = CANVAS_ZOOM_MAX, + self.speed = 1.0f, self.value = CANVAS_ZOOM_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD, - self.label = "## Animation Preview Visual Child", - self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD, self.label = "## Animation Preview Visual Child", self.size = IMGUI_CANVAS_CHILD_SIZE, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_CANVAS_BACKGROUND_COLOR, - self.label = "Background Color", - self.tooltip = "Change the background color of the canvas.", - self.flags = ImGuiColorEditFlags_NoInputs -); +IMGUI_ITEM(IMGUI_CANVAS_BACKGROUND_COLOR, self.label = "Background Color", self.tooltip = "Change the background color of the canvas.", + self.flags = ImGuiColorEditFlags_NoInputs); -IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY, - self.label = "Overlay", - self.tooltip = "Choose an animation to overlay over the previewed animation, for reference." -); +IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY, self.label = "Overlay", + self.tooltip = "Choose an animation to overlay over the previewed " + "animation, for reference."); -IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, - self.label = "Alpha", - self.tooltip = "Set the transparency of the animation overlay.", - self.value = SETTINGS_PREVIEW_OVERLAY_TRANSPARENCY_DEFAULT, - self.max = UCHAR_MAX -); +IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, self.label = "Alpha", self.tooltip = "Set the transparency of the animation overlay.", + self.value = SETTINGS_PREVIEW_OVERLAY_TRANSPARENCY_DEFAULT, self.max = UCHAR_MAX); -IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD, - self.label = "## Animation Preview Helper Child", - self.size = IMGUI_CANVAS_CHILD_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD, self.label = "## Animation Preview Helper Child", self.size = IMGUI_CANVAS_CHILD_SIZE, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_CANVAS_AXES, - self.label = "Axes", - self.tooltip = "Toggle the display of the X/Y axes." -); +IMGUI_ITEM(IMGUI_CANVAS_AXES, self.label = "Axes", self.tooltip = "Toggle the display of the X/Y axes."); -IMGUI_ITEM(IMGUI_CANVAS_AXES_COLOR, - self.label = "Color", - self.tooltip = "Change the color of the axes.", - self.flags = ImGuiColorEditFlags_NoInputs -); +IMGUI_ITEM(IMGUI_CANVAS_AXES_COLOR, self.label = "Color", self.tooltip = "Change the color of the axes.", self.flags = ImGuiColorEditFlags_NoInputs); -IMGUI_ITEM(IMGUI_CANVAS_ROOT_TRANSFORM, - self.label = "Root Transform", - self.tooltip = "Toggles the root frames's attributes transforming the other items in an animation.", - self.value = SETTINGS_PREVIEW_IS_ROOT_TRANSFORM_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_ROOT_TRANSFORM, self.label = "Root Transform", + self.tooltip = "Toggles the root frames's attributes transforming " + "the other items in an animation.", + self.value = SETTINGS_PREVIEW_IS_ROOT_TRANSFORM_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_TRIGGERS, - self.label = "Triggers", - self.tooltip = "Toggles activated triggers drawing their event name.", - self.value = SETTINGS_PREVIEW_IS_TRIGGERS_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_TRIGGERS, self.label = "Triggers", self.tooltip = "Toggles activated triggers drawing their event name.", + self.value = SETTINGS_PREVIEW_IS_TRIGGERS_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_PIVOTS, - self.label = "Pivots", - self.tooltip = "Toggles drawing each layer's pivot.", - self.value = SETTINGS_PREVIEW_IS_PIVOTS_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_PIVOTS, self.label = "Pivots", self.tooltip = "Toggles drawing each layer's pivot.", self.value = SETTINGS_PREVIEW_IS_PIVOTS_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_ICONS, - self.label = "Icons", - self.tooltip = "Toggles drawing the the colored root/null icons.", - self.value = SETTINGS_PREVIEW_IS_ICONS_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_ICONS, self.label = "Icons", self.tooltip = "Toggles drawing the the colored root/null icons.", + self.value = SETTINGS_PREVIEW_IS_ICONS_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_ALT_ICONS, - self.label = "Alt Icons", - self.tooltip = "Toggles the use of alternate icons for the targets (the colored root/null icons).", - self.value = SETTINGS_PREVIEW_IS_ALT_ICONS_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_ALT_ICONS, self.label = "Alt Icons", + self.tooltip = "Toggles the use of alternate icons for the targets " + "(the colored root/null icons).", + self.value = SETTINGS_PREVIEW_IS_ALT_ICONS_DEFAULT); -IMGUI_ITEM(IMGUI_CANVAS_BORDER, - self.label = "Border", - self.tooltip = "Toggles the appearance of a border around the items.", - self.value = SETTINGS_PREVIEW_IS_BORDER_DEFAULT -); +IMGUI_ITEM(IMGUI_CANVAS_BORDER, self.label = "Border", self.tooltip = "Toggles the appearance of a border around the items.", + self.value = SETTINGS_PREVIEW_IS_BORDER_DEFAULT); -IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW, - self.label = "Animation Preview", - self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW, self.label = "Animation Preview", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); #define IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW, - self.label = "Center View", - self.tooltip = "Centers the current view on the animation preview.", - self.hotkey = HOTKEY_CENTER_VIEW, - self.focusWindow = IMGUI_ANIMATION_PREVIEW.label, - self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW, self.label = "Center View", self.tooltip = "Centers the current view on the animation preview.", + self.hotkey = HOTKEY_CENTER_VIEW, self.focusWindow = IMGUI_ANIMATION_PREVIEW.label, self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT, + self.isSameLine = true); -IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_FIT, - self.label = "Fit", - self.tooltip = "Adjust the view/pan based on the size of the animation, to fit the canvas' size.", - self.hotkey = HOTKEY_FIT, - self.focusWindow = IMGUI_ANIMATION_PREVIEW.label, - self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT -); +IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_FIT, self.label = "Fit", + self.tooltip = "Adjust the view/pan based on the size of the " + "animation, to fit the canvas' size.", + self.hotkey = HOTKEY_FIT, self.focusWindow = IMGUI_ANIMATION_PREVIEW.label, self.rowCount = IMGUI_ANIMATION_PREVIEW_VIEW_ROW_COUNT); -IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR, - self.label = "Spritesheet Editor", - self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR, self.label = "Spritesheet Editor", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); #define IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW, - self.label = "Center View", - self.tooltip = "Centers the current view on the spritesheet editor.", - self.hotkey = HOTKEY_CENTER_VIEW, - self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, - self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW, self.label = "Center View", self.tooltip = "Centers the current view on the spritesheet editor.", + self.hotkey = HOTKEY_CENTER_VIEW, self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT, + self.isSameLine = true); -IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_FIT, - self.label = "Fit", - self.tooltip = "Adjust the view/pan based on the size of the spritesheet, to fit the canvas' size.", - self.hotkey = HOTKEY_FIT, - self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, - self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT -); +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_FIT, self.label = "Fit", + self.tooltip = "Adjust the view/pan based on the size of the " + "spritesheet, to fit the canvas' size.", + self.hotkey = HOTKEY_FIT, self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, self.rowCount = IMGUI_SPRITESHEET_EDITOR_VIEW_ROW_COUNT); IMGUI_ITEM(IMGUI_FRAME_PROPERTIES, self.label = "Frame Properties"); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION, - self.label = "Position", - self.tooltip = "Change the position of the selected frame.", - self.snapshotAction = "Frame Position", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION, self.label = "Position", self.tooltip = "Change the position of the selected frame.", + self.snapshotAction = "Frame Position", self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_CROP, - self.label = "Crop", - self.tooltip = "Change the crop position of the selected frame.", - self.snapshotAction = "Frame Crop", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_CROP, self.label = "Crop", self.tooltip = "Change the crop position of the selected frame.", + self.snapshotAction = "Frame Crop", self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SIZE, - self.label = "Size", - self.tooltip = "Change the size of the crop of the selected frame.", - self.snapshotAction = "Frame Size", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SIZE, self.label = "Size", self.tooltip = "Change the size of the crop of the selected frame.", + self.snapshotAction = "Frame Size", self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_PIVOT, - self.label = "Pivot", - self.tooltip = "Change the pivot of the selected frame.", - self.snapshotAction = "Frame Pivot", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_PIVOT, self.label = "Pivot", self.tooltip = "Change the pivot of the selected frame.", self.snapshotAction = "Frame Pivot", + self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE, - self.label = "Scale", - self.tooltip = "Change the scale of the selected frame.", - self.snapshotAction = "Frame Scale", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE, self.label = "Scale", self.tooltip = "Change the scale of the selected frame.", self.snapshotAction = "Frame Scale", + self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION, - self.label = "Rotation", - self.tooltip = "Change the rotation of the selected frame.", - self.snapshotAction = "Frame Rotation", - self.isUseItemActivated = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION, self.label = "Rotation", self.tooltip = "Change the rotation of the selected frame.", + self.snapshotAction = "Frame Rotation", self.isUseItemActivated = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_DELAY, - self.label = "Duration", - self.tooltip = "Change the duration of the selected frame.", - self.snapshotAction = "Frame Duration", - self.isUseItemActivated = true, - self.min = ANM2_FRAME_NUM_MIN, - self.max = ANM2_FRAME_NUM_MAX, - self.value = ANM2_FRAME_NUM_MIN -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_DELAY, self.label = "Duration", self.tooltip = "Change the duration of the selected frame.", + self.snapshotAction = "Frame Duration", self.isUseItemActivated = true, self.min = ANM2_FRAME_NUM_MIN, self.max = ANM2_FRAME_NUM_MAX, + self.value = ANM2_FRAME_NUM_MIN); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_TINT, - self.label = "Tint", - self.tooltip = "Change the tint of the selected frame.", - self.snapshotAction = "Frame Tint", - self.isUseItemActivated = true, - self.value = 1 -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_TINT, self.label = "Tint", self.tooltip = "Change the tint of the selected frame.", self.snapshotAction = "Frame Tint", + self.isUseItemActivated = true, self.value = 1); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, - self.label = "Color Offset", - self.tooltip = "Change the color offset of the selected frame.", - self.snapshotAction = "Frame Color Offset", - self.isUseItemActivated = true, - self.value = 0 -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, self.label = "Color Offset", self.tooltip = "Change the color offset of the selected frame.", + self.snapshotAction = "Frame Color Offset", self.isUseItemActivated = true, self.value = 0); #define IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_X, - self.label = "Flip X", - self.tooltip = "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly.)", - self.snapshotAction = "Frame Flip X", - self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_X, self.label = "Flip X", + self.tooltip = "Change the sign of the X scale, to cheat flipping the layer " + "horizontally.\n(Anm2 doesn't support flipping directly.)", + self.snapshotAction = "Frame Flip X", self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_Y, - self.label = "Flip Y", - self.tooltip = "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly.)", - self.snapshotAction = "Frame Flip Y", - self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_FLIP_Y, self.label = "Flip Y", + self.tooltip = "Change the sign of the Y scale, to cheat flipping the layer " + "vertically.\n(Anm2 doesn't support flipping directly.)", + self.snapshotAction = "Frame Flip Y", self.rowCount = IMGUI_FRAME_PROPERTIES_FLIP_ROW_COUNT); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_VISIBLE, - self.label = "Visible", - self.tooltip = "Toggles the visibility of the selected frame.", - self.snapshotAction = "Frame Visibility", - self.isSameLine = true, - self.value = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_VISIBLE, self.label = "Visible", self.tooltip = "Toggles the visibility of the selected frame.", + self.snapshotAction = "Frame Visibility", self.isSameLine = true, self.value = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_INTERPOLATED, - self.label = "Interpolation", - self.tooltip = "Toggles the interpolation of the selected frame.", - self.snapshotAction = "Frame Interpolation", - self.isSameLine = true, - self.value = true -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_INTERPOLATED, self.label = "Interpolation", self.tooltip = "Toggles the interpolation of the selected frame.", + self.snapshotAction = "Frame Interpolation", self.isSameLine = true, self.value = true); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROUND, - self.label = "Round", - self.tooltip = "Values will be rounded to the nearest integer.", - self.value = SETTINGS_PROPERTIES_IS_ROUND_DEFAULT -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROUND, self.label = "Round", self.tooltip = "Values will be rounded to the nearest integer.", + self.value = SETTINGS_PROPERTIES_IS_ROUND_DEFAULT); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_EVENT, - self.label = "Event", - self.tooltip = "Change the event the trigger uses.", - self.snapshotAction = "Trigger Event" -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_EVENT, self.label = "Event", self.tooltip = "Change the event the trigger uses.", self.snapshotAction = "Trigger Event"); -IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_AT_FRAME, - self.label = "At Frame", - self.tooltip = "Change the frame where the trigger occurs.", - self.snapshotAction = "Trigger At Frame" -); +IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_AT_FRAME, self.label = "At Frame", self.tooltip = "Change the frame where the trigger occurs.", + self.snapshotAction = "Trigger At Frame"); IMGUI_ITEM(IMGUI_TOOLS, self.label = "Tools"); -IMGUI_ITEM(IMGUI_TOOL_PAN, - self.label = "## Pan", - self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou can also use the middle mouse button to pan at any time.", - self.function = imgui_tool_pan_set, - self.hotkey = HOTKEY_PAN, - self.atlas = ATLAS_PAN -); +IMGUI_ITEM(IMGUI_TOOL_PAN, self.label = "## Pan", + self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou " + "can also use the middle mouse button to pan at any time.", + self.function = imgui_tool_pan_set, self.hotkey = HOTKEY_PAN, self.atlas = ATLAS_PAN); -IMGUI_ITEM(IMGUI_TOOL_MOVE, - self.label = "## Move", - self.tooltip = "Use the move tool.\nWhen in animation preview, will move the position of the frame.\nWhen in spritesheet editor, will move the pivot instead.\nUse mouse or directional keys to change the value.", - self.function = imgui_tool_move_set, - self.hotkey = HOTKEY_MOVE, - self.atlas = ATLAS_MOVE -); +IMGUI_ITEM(IMGUI_TOOL_MOVE, self.label = "## Move", + self.tooltip = "Use the move tool.\nAnimation Preview: Will move the position " + "of the frame." + "\nSpritesheet Editor: Will move the pivot, and holding right " + "click will use the Crop functionality instead." + "\nUse mouse or directional keys to change the value.", + self.function = imgui_tool_move_set, self.hotkey = HOTKEY_MOVE, self.atlas = ATLAS_MOVE); -IMGUI_ITEM(IMGUI_TOOL_ROTATE, - self.label = "## Rotate", - self.tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", - self.function = imgui_tool_rotate_set, - self.hotkey = HOTKEY_ROTATE, - self.atlas = ATLAS_ROTATE -); +IMGUI_ITEM(IMGUI_TOOL_ROTATE, self.label = "## Rotate", + self.tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is " + "dragged, or directional keys are pressed.\n(Animation Preview only.)", + self.function = imgui_tool_rotate_set, self.hotkey = HOTKEY_ROTATE, self.atlas = ATLAS_ROTATE); -IMGUI_ITEM(IMGUI_TOOL_SCALE, - self.label = "## Scale", - self.tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", - self.function = imgui_tool_scale_set, - self.hotkey = HOTKEY_SCALE, - self.atlas = ATLAS_SCALE -); +IMGUI_ITEM(IMGUI_TOOL_SCALE, self.label = "## Scale", + self.tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is " + "dragged, or directional keys are pressed.\n(Animation Preview only.)", + self.function = imgui_tool_scale_set, self.hotkey = HOTKEY_SCALE, self.atlas = ATLAS_SCALE); -IMGUI_ITEM(IMGUI_TOOL_CROP, - self.label = "## Crop", - self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\nAlternatively, you can use the arrow keys and Ctrl/Shift to move the size/position, respectively.\n(Spritesheet Editor only.)", - self.function = imgui_tool_crop_set, - self.hotkey = HOTKEY_CROP, - self.atlas = ATLAS_CROP -); +IMGUI_ITEM(IMGUI_TOOL_CROP, self.label = "## Crop", + self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how " + "the cursor is dragged." + "\nAlternatively, you can use the arrow keys and Ctrl/Shift to " + "move the size/position, respectively." + "\nHolding right click will use the Move tool's functionality." + "\n(Spritesheet Editor only.)", + self.function = imgui_tool_crop_set, self.hotkey = HOTKEY_CROP, self.atlas = ATLAS_CROP); -IMGUI_ITEM(IMGUI_TOOL_DRAW, - self.label = "## Draw", - self.tooltip = "Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)", - self.function = imgui_tool_draw_set, - self.hotkey = HOTKEY_DRAW, - self.atlas = ATLAS_DRAW -); +IMGUI_ITEM(IMGUI_TOOL_DRAW, self.label = "## Draw", + self.tooltip = "Draws pixels onto the selected spritesheet, with " + "the current color.\n(Spritesheet Editor only.)", + self.function = imgui_tool_draw_set, self.hotkey = HOTKEY_DRAW, self.atlas = ATLAS_DRAW); -IMGUI_ITEM(IMGUI_TOOL_ERASE, - self.label = "## Erase", - self.tooltip = "Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)", - self.function = imgui_tool_erase_set, - self.hotkey = HOTKEY_ERASE, - self.atlas = ATLAS_ERASE -); +IMGUI_ITEM(IMGUI_TOOL_ERASE, self.label = "## Erase", + self.tooltip = "Erases pixels from the selected " + "spritesheet.\n(Spritesheet Editor only.)", + self.function = imgui_tool_erase_set, self.hotkey = HOTKEY_ERASE, self.atlas = ATLAS_ERASE); -IMGUI_ITEM(IMGUI_TOOL_COLOR_PICKER, - self.label = "## Color Picker", - self.tooltip = "Selects a color from the canvas, to be used for drawing.\n(Spritesheet Editor only).", - self.function = imgui_tool_color_picker_set, - self.hotkey = HOTKEY_COLOR_PICKER, - self.atlas = ATLAS_COLOR_PICKER -); +IMGUI_ITEM(IMGUI_TOOL_COLOR_PICKER, self.label = "## Color Picker", + self.tooltip = "Selects a color from the canvas, to be used for " + "drawing.\n(Spritesheet Editor only).", + self.function = imgui_tool_color_picker_set, self.hotkey = HOTKEY_COLOR_PICKER, self.atlas = ATLAS_COLOR_PICKER); -IMGUI_ITEM(IMGUI_TOOL_UNDO, - self.label = "## Undo", - self.tooltip = "Undo the last action.", - self.function = imgui_undo, - self.hotkey = HOTKEY_UNDO, - self.atlas = ATLAS_UNDO -); +IMGUI_ITEM(IMGUI_TOOL_UNDO, self.label = "## Undo", self.tooltip = "Undo the last action.", self.function = imgui_undo, self.hotkey = HOTKEY_UNDO, + self.atlas = ATLAS_UNDO); -IMGUI_ITEM(IMGUI_TOOL_REDO, - self.label = "## Redo", - self.tooltip = "Redo the last action.", - self.function = imgui_redo, - self.hotkey = HOTKEY_REDO, - self.atlas = ATLAS_REDO -); +IMGUI_ITEM(IMGUI_TOOL_REDO, self.label = "## Redo", self.tooltip = "Redo the last action.", self.function = imgui_redo, self.hotkey = HOTKEY_REDO, + self.atlas = ATLAS_REDO); -IMGUI_ITEM(IMGUI_TOOL_COLOR, - self.label = "## Color", - self.tooltip = "Set the color, to be used by the draw tool.", - self.flags = ImGuiColorEditFlags_NoInputs -); +IMGUI_ITEM(IMGUI_TOOL_COLOR, self.label = "## Color", self.tooltip = "Set the color, to be used by the draw tool.", self.flags = ImGuiColorEditFlags_NoInputs); -const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = -{ - &IMGUI_TOOL_PAN, - &IMGUI_TOOL_MOVE, - &IMGUI_TOOL_ROTATE, - &IMGUI_TOOL_SCALE, - &IMGUI_TOOL_CROP, - &IMGUI_TOOL_DRAW, - &IMGUI_TOOL_ERASE, - &IMGUI_TOOL_COLOR_PICKER, - &IMGUI_TOOL_UNDO, - &IMGUI_TOOL_REDO, - &IMGUI_TOOL_COLOR -}; +const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = {&IMGUI_TOOL_PAN, &IMGUI_TOOL_MOVE, &IMGUI_TOOL_ROTATE, &IMGUI_TOOL_SCALE, + &IMGUI_TOOL_CROP, &IMGUI_TOOL_DRAW, &IMGUI_TOOL_ERASE, &IMGUI_TOOL_COLOR_PICKER, + &IMGUI_TOOL_UNDO, &IMGUI_TOOL_REDO, &IMGUI_TOOL_COLOR}; IMGUI_ITEM(IMGUI_COLOR_PICKER_BUTTON, self.label = "## Color Picker Button"); -IMGUI_ITEM(IMGUI_TIMELINE, - self.label = "Timeline", - self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE, self.label = "Timeline", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_CHILD, - self.label = "## Timeline Child", - self.flags = true -); +IMGUI_ITEM(IMGUI_TIMELINE_CHILD, self.label = "## Timeline Child", self.flags = true); -IMGUI_ITEM(IMGUI_TIMELINE_HEADER_CHILD, - self.label = "## Timeline Header Child", - self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_HEADER_CHILD, self.label = "## Timeline Header Child", self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_PLAYHEAD, - self.label = "## Playhead", - self.flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBackground | - ImGuiWindowFlags_NoInputs -); +IMGUI_ITEM(IMGUI_PLAYHEAD, self.label = "## Playhead", + self.flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoInputs); -IMGUI_ITEM(IMGUI_TIMELINE_ITEMS_CHILD, - self.label = "## Timeline Items", - self.size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEMS_CHILD, self.label = "## Timeline Items", self.size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_CHILD, - self.label = "## Timeline Item Child", - self.size = IMGUI_TIMELINE_ITEM_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_CHILD, self.label = "## Timeline Item Child", self.size = IMGUI_TIMELINE_ITEM_SIZE, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_CHILD, - self.label = "## Root Item Child", - self.color = {{0.045f, 0.08f, 0.11f, 1.0f}}, - self.size = IMGUI_TIMELINE_ITEM_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_CHILD, self.label = "## Root Item Child", self.color.normal = {0.045f, 0.08f, 0.11f, 1.0f}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, self.flags = true, self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_CHILD, - self.label = "## Layer Item Child", - self.color = {{0.0875f, 0.05f, 0.015f, 1.0f}}, - self.size = IMGUI_TIMELINE_ITEM_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_CHILD, self.label = "## Layer Item Child", self.color.normal = {0.0875f, 0.05f, 0.015f, 1.0f}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, self.flags = true, self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_CHILD, - self.label = "## Null Item Child", - self.color = {{0.055f, 0.10f, 0.055f, 1.0f}}, - self.size = IMGUI_TIMELINE_ITEM_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_CHILD, self.label = "## Null Item Child", self.color.normal = {0.055f, 0.10f, 0.055f, 1.0f}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, self.flags = true, self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD, - self.label = "## Triggers Item Child", - self.color = {{0.10f, 0.0375f, 0.07f, 1.0f}}, - self.size = IMGUI_TIMELINE_ITEM_SIZE, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD, self.label = "## Triggers Item Child", self.color.normal = {0.10f, 0.0375f, 0.07f, 1.0f}, + self.size = IMGUI_TIMELINE_ITEM_SIZE, self.flags = true, self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); -const inline ImguiItem* IMGUI_TIMELINE_ITEM_CHILDS[ANM2_COUNT] -{ - &IMGUI_TIMELINE_ITEM_CHILD, - &IMGUI_TIMELINE_ITEM_ROOT_CHILD, - &IMGUI_TIMELINE_ITEM_LAYER_CHILD, - &IMGUI_TIMELINE_ITEM_NULL_CHILD, - &IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD -}; +const inline ImguiItem* IMGUI_TIMELINE_ITEM_CHILDS[ANM2_COUNT]{&IMGUI_TIMELINE_ITEM_CHILD, &IMGUI_TIMELINE_ITEM_ROOT_CHILD, &IMGUI_TIMELINE_ITEM_LAYER_CHILD, + &IMGUI_TIMELINE_ITEM_NULL_CHILD, &IMGUI_TIMELINE_ITEM_TRIGGERS_CHILD}; #define IMGUI_POPUP_ITEM_PROPERTIES "Item Properties" #define IMGUI_POPUP_ITEM_PROPERTIES_TYPE IMGUI_POPUP_CENTER_WINDOW const ImVec2 IMGUI_POPUP_ITEM_PROPERTIES_SIZE = {300, 350}; -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, - self.label = "## Item Properties Type Child", - self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, - self.flags = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_TYPE_CHILD, self.label = "## Item Properties Type Child", self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, + self.flags = true); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, - self.label = "Layer", - self.tooltip = "The item will be a layer item.\nA layer item is a primary graphical item, using a spritesheet.", - self.isSizeToText = true, - self.value = ANM2_LAYER, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_LAYER, self.label = "Layer", + self.tooltip = "The item will be a layer item.\nA layer item is a " + "primary graphical item, using a spritesheet.", + self.isSizeToText = true, self.value = ANM2_LAYER, self.isSameLine = true); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, - self.label = "Null", - self.tooltip = "The item will be a null item.\nA null item is an invisible item, often accessed by a game engine.", - self.isSizeToText = true, - self.value = ANM2_NULL -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_NULL, self.label = "Null", + self.tooltip = "The item will be a null item.\nA null item is an " + "invisible item, often accessed by a game engine.", + self.isSizeToText = true, self.value = ANM2_NULL); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, - self.label = "## Item Properties Items", - self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 250}, - self.flags = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_ITEMS_CHILD, self.label = "## Item Properties Items", self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 250}, + self.flags = true); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, - self.label = "## Item Properties Options Child", - self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, - self.flags = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_OPTIONS_CHILD, self.label = "## Item Properties Options Child", self.size = {IMGUI_POPUP_ITEM_PROPERTIES_SIZE.x, 35}, + self.flags = true); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM, - self.label = "Confirm", - self.tooltip = "Set the timeline item's properties.", - self.snapshotAction = "Timeline Item Change", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_PROPERTIES_CONFIRM, self.label = "Confirm", self.tooltip = "Set the timeline item's properties.", + self.snapshotAction = "Timeline Item Change", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SELECTABLE, - self.label = "## Selectable", - self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SELECTABLE, self.label = "## Selectable", self.size = IMGUI_TIMELINE_ITEM_SIZE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE, - self.label = "Root", - self.tooltip = "The root item of an animation.\nChanging its properties will transform the rest of the animation.", - self.atlas = ATLAS_ROOT, - self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE, self.label = "Root", + self.tooltip = "The root item of an animation.\nChanging its properties " + "will transform the rest of the animation.", + self.size = IMGUI_TIMELINE_ITEM_SIZE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, - self.label = "## Layer Selectable", - self.tooltip = "A layer item.\nA graphical item within the animation.", - self.dragDrop = "## Layer Drag Drop", - self.popup = IMGUI_POPUP_ITEM_PROPERTIES, - self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, - self.atlas = ATLAS_LAYER, - self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, self.label = "## Layer Selectable", self.tooltip = "A layer item.\nA graphical item within the animation.", + self.dragDrop = "## Layer Drag Drop", self.color.active = {0.45f, 0.18f, 0.07f, 1.0f}, self.popup = IMGUI_POPUP_ITEM_PROPERTIES, + self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, self.size = IMGUI_TIMELINE_ITEM_SIZE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, - self.label = "## Null Selectable", - self.tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.", - self.dragDrop = "## Null Drag Drop", - self.popup = IMGUI_POPUP_ITEM_PROPERTIES, - self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, - self.atlas = ATLAS_NULL, - self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, self.label = "## Null Selectable", + self.tooltip = "A null item.\nAn invisible item within the " + "animation that is accessible via a game engine.", + self.dragDrop = "## Null Drag Drop", self.popup = IMGUI_POPUP_ITEM_PROPERTIES, self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, + self.size = IMGUI_TIMELINE_ITEM_SIZE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE, - self.label = "Triggers", - self.tooltip = "The animation's triggers.\nWill fire based on an event.", - self.atlas = ATLAS_TRIGGERS, - self.size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE, self.label = "Triggers", self.tooltip = "The animation's triggers.\nWill fire based on an event.", + self.size = IMGUI_TIMELINE_ITEM_SIZE); -const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT] -{ - &IMGUI_TIMELINE_ITEM_SELECTABLE, - &IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE, - &IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, - &IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, - &IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE -}; +const inline ImguiItem* IMGUI_TIMELINE_ITEM_SELECTABLES[ANM2_COUNT]{&IMGUI_TIMELINE_ITEM_SELECTABLE, &IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE, + &IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE, &IMGUI_TIMELINE_ITEM_NULL_SELECTABLE, + &IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE}; -IMGUI_ITEM(IMGUI_TIMELINE_SHOW_UNUSED, - self.label = "## Show Unused", - self.tooltip = "Layers/nulls without any frames will be hidden.", - self.snapshotAction = "Hide Unused", - self.atlas = ATLAS_SHOW_UNUSED -); +IMGUI_ITEM(IMGUI_TIMELINE_SHOW_UNUSED, self.label = "## Show Unused", self.tooltip = "Layers/nulls without any frames will be hidden.", + self.snapshotAction = "Hide Unused", self.atlas = ATLAS_SHOW_UNUSED); -IMGUI_ITEM(IMGUI_TIMELINE_HIDE_UNUSED, - self.label = "## Hide Unused", - self.tooltip = "Layers/nulls without any frames will be shown.", - self.snapshotAction = "Show Unused", - self.atlas = ATLAS_HIDE_UNUSED -); +IMGUI_ITEM(IMGUI_TIMELINE_HIDE_UNUSED, self.label = "## Hide Unused", self.tooltip = "Layers/nulls without any frames will be shown.", + self.snapshotAction = "Show Unused", self.atlas = ATLAS_HIDE_UNUSED); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_VISIBLE, - self.label = "## Visible", - self.tooltip = "The item is visible.\nPress to set to invisible.", - self.snapshotAction = "Item Invisible", - self.atlas = ATLAS_VISIBLE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_VISIBLE, self.label = "## Visible", self.tooltip = "The item is visible.\nPress to set to invisible.", + self.snapshotAction = "Item Invisible", self.atlas = ATLAS_VISIBLE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_INVISIBLE, - self.label = "## Invisible", - self.tooltip = "The item is invisible.\nPress to set to visible.", - self.snapshotAction = "Item Visible", - self.atlas = ATLAS_INVISIBLE -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_INVISIBLE, self.label = "## Invisible", self.tooltip = "The item is invisible.\nPress to set to visible.", + self.snapshotAction = "Item Visible", self.atlas = ATLAS_INVISIBLE); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SHOW_RECT, - self.label = "## Show Rect", - self.tooltip = "The rect is shown.\nPress to hide rect.", - self.snapshotAction = "Hide Rect", - self.atlas = ATLAS_SHOW_RECT -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_SHOW_RECT, self.label = "## Show Rect", self.tooltip = "The rect is shown.\nPress to hide rect.", + self.snapshotAction = "Hide Rect", self.atlas = ATLAS_SHOW_RECT); -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_HIDE_RECT, - self.label = "## Hide Rect", - self.tooltip = "The rect is hidden.\nPress to show rect.", - self.snapshotAction = "Show Rect", - self.atlas = ATLAS_HIDE_RECT -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_HIDE_RECT, self.label = "## Hide Rect", self.tooltip = "The rect is hidden.\nPress to show rect.", + self.snapshotAction = "Show Rect", self.atlas = ATLAS_HIDE_RECT); +IMGUI_ITEM(IMGUI_TIMELINE_FRAMES_CHILD, self.label = "## Timeline Frames Child", + self.windowFlags = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_HorizontalScrollbar); -IMGUI_ITEM(IMGUI_TIMELINE_FRAMES_CHILD, - self.label = "## Timeline Frames Child", - self.windowFlags = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_HorizontalScrollbar -); - -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FRAMES_CHILD, - self.label = "## Timeline Item Frames Child", - self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y} -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FRAMES_CHILD, self.label = "## Timeline Item Frames Child", self.size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}); IMGUI_ITEM(IMGUI_TIMELINE_FRAME, self.label = "## Frame"); #define IMGUI_TIMELINE_FRAME_BORDER 5 static const vec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.25f}; -IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME, - self.label = "## Root Frame", - self.snapshotAction = "Root Frame", - self.color = {{0.14f, 0.27f, 0.39f, 1.0f}, {0.28f, 0.54f, 0.78f, 1.0f}, {0.36f, 0.70f, 0.95f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, - self.size = IMGUI_TIMELINE_FRAME_SIZE, - self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, - self.border = IMGUI_FRAME_BORDER -); +IMGUI_ITEM(IMGUI_TIMELINE_ROOT_FRAME, self.label = "## Root Frame", self.snapshotAction = "Root Frame", + self.color = {vec4(0.14f, 0.27f, 0.39f, 1.0f), vec4(0.28f, 0.54f, 0.78f, 1.0f), vec4(0.36f, 0.70f, 0.95f, 1.0f), IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, self.border = IMGUI_FRAME_BORDER); -IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME, - self.label = "## Layer Frame", - self.dragDrop = "## Layer Frame Drag Drop", - self.snapshotAction = "Layer Frame", - self.color = {{0.45f, 0.18f, 0.07f, 1.0f}, {0.78f, 0.32f, 0.12f, 1.0f}, {0.95f, 0.40f, 0.15f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, - self.size = IMGUI_TIMELINE_FRAME_SIZE, - self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, - self.border = IMGUI_FRAME_BORDER -); +IMGUI_ITEM(IMGUI_TIMELINE_LAYER_FRAME, self.label = "## Layer Frame", self.dragDrop = "## Layer Frame Drag Drop", self.snapshotAction = "Layer Frame", + self.color = {vec4(0.45f, 0.18f, 0.07f, 1.0f), vec4(0.78f, 0.32f, 0.12f, 1.0f), vec4(0.95f, 0.40f, 0.15f, 1.0f), IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, self.border = IMGUI_FRAME_BORDER); -IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME, - self.label = "## Null Frame", - self.dragDrop = "## Null Frame Drag Drop", - self.snapshotAction = "Null Frame", - self.color = {{0.17f, 0.33f, 0.17f, 1.0f}, {0.34f, 0.68f, 0.34f, 1.0f}, {0.44f, 0.88f, 0.44f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, - self.size = IMGUI_TIMELINE_FRAME_SIZE, - self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, - self.border = IMGUI_FRAME_BORDER -); +IMGUI_ITEM(IMGUI_TIMELINE_NULL_FRAME, self.label = "## Null Frame", self.dragDrop = "## Null Frame Drag Drop", self.snapshotAction = "Null Frame", + self.color = {vec4(0.17f, 0.33f, 0.17f, 1.0f), vec4(0.34f, 0.68f, 0.34f, 1.0f), vec4(0.44f, 0.88f, 0.44f, 1.0f), IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, self.border = IMGUI_FRAME_BORDER); -IMGUI_ITEM(IMGUI_TIMELINE_TRIGGERS_FRAME, - self.label = "## Triggers Frame", - self.snapshotAction = "Trigger", - self.color = {{0.36f, 0.14f, 0.24f, 1.0f}, {0.72f, 0.28f, 0.48f, 1.0f}, {0.92f, 0.36f, 0.60f, 1.0f}, IMGUI_FRAME_BORDER_COLOR}, - self.size = IMGUI_TIMELINE_FRAME_SIZE, - self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, - self.border = IMGUI_FRAME_BORDER -); +IMGUI_ITEM(IMGUI_TIMELINE_TRIGGERS_FRAME, self.label = "## Triggers Frame", self.snapshotAction = "Trigger", + self.color = {vec4(0.36f, 0.14f, 0.24f, 1.0f), vec4(0.72f, 0.28f, 0.48f, 1.0f), vec4(0.92f, 0.36f, 0.60f, 1.0f), IMGUI_FRAME_BORDER_COLOR}, + self.size = IMGUI_TIMELINE_FRAME_SIZE, self.atlasOffset = IMGUI_TIMELINE_FRAME_ATLAS_OFFSET, self.border = IMGUI_FRAME_BORDER); -const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT] -{ - &IMGUI_TIMELINE_FRAME, - &IMGUI_TIMELINE_ROOT_FRAME, - &IMGUI_TIMELINE_LAYER_FRAME, - &IMGUI_TIMELINE_NULL_FRAME, - &IMGUI_TIMELINE_TRIGGERS_FRAME -}; +const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT]{&IMGUI_TIMELINE_FRAME, &IMGUI_TIMELINE_ROOT_FRAME, &IMGUI_TIMELINE_LAYER_FRAME, + &IMGUI_TIMELINE_NULL_FRAME, &IMGUI_TIMELINE_TRIGGERS_FRAME}; -IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, - self.label = "## Item Footer Child", - self.size = {IMGUI_TIMELINE_ITEM_CHILD.size.x, IMGUI_FOOTER_CHILD.size.y}, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); -IMGUI_ITEM(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, - self.label = "## Options Footer Child", - self.size = {0, IMGUI_FOOTER_CHILD.size.y}, - self.flags = true, - self.windowFlags = ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse -); +IMGUI_ITEM(IMGUI_TIMELINE_ITEM_FOOTER_CHILD, self.label = "## Item Footer Child", self.size = {IMGUI_TIMELINE_ITEM_CHILD.size->x, IMGUI_FOOTER_CHILD.size->y}, + self.flags = true, self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); +IMGUI_ITEM(IMGUI_TIMELINE_OPTIONS_FOOTER_CHILD, self.label = "## Options Footer Child", self.size = {0, IMGUI_FOOTER_CHILD.size->y}, self.flags = true, + self.windowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); #define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2 -IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM, - self.label = "Add", - self.tooltip = "Adds an item (layer or null) to the animation.\nMake sure to add a Layer/Null first in the Layers or Nulls windows.", - self.popup = "Add Item", - self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, - self.popupSize = {300, 350}, - self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_TIMELINE_ADD_ITEM, self.label = "Add", + self.tooltip = "Adds an item (layer or null) to the animation.\nMake sure " + "to add a Layer/Null first in the Layers or Nulls windows.", + self.popup = "Add Item", self.popupType = IMGUI_POPUP_ITEM_PROPERTIES_TYPE, self.popupSize = {300, 350}, + self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT, self.isSameLine = true); - - -IMGUI_ITEM(IMGUI_TIMELINE_REMOVE_ITEM, - self.label = "Remove", - self.tooltip = "Removes the selected item (layer or null) from the animation.", - self.snapshotAction = "Remove Item", - self.chord = ImGuiKey_Delete, - self.focusWindow = IMGUI_TIMELINE_ITEMS_CHILD.label, - self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT -); +IMGUI_ITEM(IMGUI_TIMELINE_REMOVE_ITEM, self.label = "Remove", self.tooltip = "Removes the selected item (layer or null) from the animation.", + self.snapshotAction = "Remove Item", self.chord = ImGuiKey_Delete, self.focusWindow = IMGUI_TIMELINE_ITEMS_CHILD.label, + self.rowCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT); #define IMGUI_TIMELINE_OPTIONS_ROW_COUNT 10 -IMGUI_ITEM(IMGUI_PLAY, - self.label = "|> Play", - self.tooltip = "Play the current animation, if paused.", - self.focusWindow = IMGUI_TIMELINE.label, - self.hotkey = HOTKEY_PLAY_PAUSE, - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_PLAY, self.label = "|> Play", self.tooltip = "Play the current animation, if paused.", self.focusWindow = IMGUI_TIMELINE.label, + self.hotkey = HOTKEY_PLAY_PAUSE, self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_PAUSE, - self.label = "|| Pause", - self.tooltip = "Pause the current animation, if playing.", - self.focusWindow = IMGUI_TIMELINE.label, - self.hotkey = HOTKEY_PLAY_PAUSE, - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_PAUSE, self.label = "|| Pause", self.tooltip = "Pause the current animation, if playing.", self.focusWindow = IMGUI_TIMELINE.label, + self.hotkey = HOTKEY_PLAY_PAUSE, self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ADD_FRAME, - self.label = "+ Insert Frame", - self.tooltip = "Inserts a frame in the selected animation item, based on the preview time.", - self.snapshotAction = "Insert Frame", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_INSERT_FRAME, self.label = "+ Insert Frame", + self.tooltip = "Inserts a frame in the selected animation item, " + "based on the preview time.", + self.snapshotAction = "Insert Frame", self.hotkey = HOTKEY_INSERT_FRAME, self.focusWindow = IMGUI_TIMELINE.label, + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_REMOVE_FRAME, - self.label = "- Delete Frame", - self.tooltip = "Removes the selected frame from the selected animation item.", - self.snapshotAction = "Delete Frame", - self.focusWindow = IMGUI_TIMELINE.label, - self.chord = ImGuiKey_Delete, - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_REMOVE_FRAME, self.label = "- Delete Frame", self.tooltip = "Removes the selected frame from the selected animation item.", + self.snapshotAction = "Delete Frame", self.focusWindow = IMGUI_TIMELINE.label, self.chord = ImGuiKey_Delete, + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_BAKE, - self.label = "Bake", - self.tooltip = "Opens the bake popup menu, if a frame is selected.\nBaking a frame takes the currently interpolated values at the time between it and the next frame and separates them based on the interval.", - self.popup = "Bake Frames", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.popupSize = {260, 145}, - self.isSameLine = true -); +static inline void imgui_shorten_frame(Imgui* self) { + if (self->reference->itemType == ANM2_TRIGGER) + return; + if (Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *self->reference)) + frame->delay = std::max(ANM2_FRAME_DELAY_MIN, frame->delay - 1); +} -IMGUI_ITEM(IMGUI_BAKE_CHILD, - self.label = "## Bake Child", - self.flags = true -); +IMGUI_ITEM(IMGUI_SHORTEN_FRAME, self.label = "## Shorten Frame", self.snapshotAction = "Shorten Frame", self.hotkey = HOTKEY_SHORTEN_FRAME, + self.function = imgui_shorten_frame); -IMGUI_ITEM(IMGUI_BAKE_INTERVAL, - self.label = "Interval", - self.tooltip = "Sets the delay of the baked frames the selected frame will be separated out into.", - self.min = ANM2_FRAME_DELAY_MIN, - self.value = ANM2_FRAME_DELAY_MIN -); +static inline void imgui_extend_frame(Imgui* self) { + if (self->reference->itemType == ANM2_TRIGGER) + return; + if (Anm2Frame* frame = anm2_frame_from_reference(self->anm2, *self->reference)) + frame->delay++; +} -IMGUI_ITEM(IMGUI_BAKE_ROUND_SCALE, - self.label = "Round Scale", - self.tooltip = "The scale of the baked frames will be rounded to the nearest integer.", - self.value = true -); +IMGUI_ITEM(IMGUI_EXTEND_FRAME, self.label = "## Extend Frame", self.snapshotAction = "Extend Frame", self.hotkey = HOTKEY_EXTEND_FRAME, + self.function = imgui_extend_frame); -IMGUI_ITEM(IMGUI_BAKE_ROUND_ROTATION, - self.label = "Round Rotation", - self.tooltip = "The rotation of the baked frames will be rounded to the nearest integer.", - self.value = true, - self.isSeparator = true -); +static inline void imgui_next_frame(Imgui* self) { + if (Anm2Item* item = anm2_item_from_reference(self->anm2, *self->reference)) { + self->reference->frameIndex = std::min((int)(item->frames.size() - 1), self->reference->frameIndex + 1); + self->preview->time = anm2_time_from_reference(self->anm2, *self->reference); + } +} -IMGUI_ITEM(IMGUI_BAKE_CONFIRM, - self.label = "Bake", - self.tooltip = "Bake the selected frame with the options selected.", - self.snapshotAction = "Bake Frames", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_NEXT_FRAME, self.label = "## Next Frame", self.hotkey = HOTKEY_NEXT_FRAME, self.function = imgui_next_frame); -IMGUI_ITEM(IMGUI_FIT_ANIMATION_LENGTH, - self.label = "Fit Animation Length", - self.tooltip = "Sets the animation's length to the latest frame.", - self.snapshotAction = "Fit Animation Length", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.isSameLine = true -); +static inline void imgui_previous_frame(Imgui* self) { + if (anm2_item_from_reference(self->anm2, *self->reference)) { + self->reference->frameIndex = std::max(0, self->reference->frameIndex - 1); + self->preview->time = anm2_time_from_reference(self->anm2, *self->reference); + } +} -IMGUI_ITEM(IMGUI_ANIMATION_LENGTH, - self.label = "Length", - self.tooltip = "Sets the animation length.\n(Will not change frames.)", - self.snapshotAction = "Set Animation Length", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.min = ANM2_FRAME_NUM_MIN, - self.max = ANM2_FRAME_NUM_MAX, - self.value = ANM2_FRAME_NUM_MIN, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_PREVIOUS_FRAME, self.label = "## Previous Frame", self.hotkey = HOTKEY_PREVIOUS_FRAME, self.function = imgui_previous_frame); -IMGUI_ITEM(IMGUI_FPS, - self.label = "FPS", - self.tooltip = "Sets the animation's frames per second (its speed).", - self.snapshotAction = "Set FPS", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.min = ANM2_FPS_MIN, - self.max = ANM2_FPS_MAX, - self.value = ANM2_FPS_DEFAULT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_BAKE, self.label = "Bake", + self.tooltip = "Opens the bake popup menu, if a frame is selected.\nBaking a frame " + "takes the currently interpolated values at the time between it and " + "the next frame and separates them based on the interval.", + self.popup = "Bake Frames", self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.popupSize = {260, 145}, self.isSameLine = true); -IMGUI_ITEM(IMGUI_LOOP, - self.label = "Loop", - self.tooltip = "Toggles the animation looping.", - self.snapshotAction = "Set Loop", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.value = true, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_BAKE_CHILD, self.label = "## Bake Child", self.flags = true); -IMGUI_ITEM(IMGUI_CREATED_BY, - self.label = "Author", - self.tooltip = "Sets the author of the animation.", - self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.max = UCHAR_MAX -); +IMGUI_ITEM(IMGUI_BAKE_INTERVAL, self.label = "Interval", + self.tooltip = "Sets the delay of the baked frames the selected " + "frame will be separated out into.", + self.min = ANM2_FRAME_DELAY_MIN, self.value = ANM2_FRAME_DELAY_MIN); + +IMGUI_ITEM(IMGUI_BAKE_ROUND_SCALE, self.label = "Round Scale", self.tooltip = "The scale of the baked frames will be rounded to the nearest integer.", + self.value = true); + +IMGUI_ITEM(IMGUI_BAKE_ROUND_ROTATION, self.label = "Round Rotation", + self.tooltip = "The rotation of the baked frames will be rounded to " + "the nearest integer.", + self.value = true, self.isSeparator = true); + +IMGUI_ITEM(IMGUI_BAKE_CONFIRM, self.label = "Bake", self.tooltip = "Bake the selected frame with the options selected.", self.snapshotAction = "Bake Frames", + self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT, self.isSameLine = true); + +IMGUI_ITEM(IMGUI_FIT_ANIMATION_LENGTH, self.label = "Fit Animation Length", self.tooltip = "Sets the animation's length to the latest frame.", + self.snapshotAction = "Fit Animation Length", self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true); + +IMGUI_ITEM(IMGUI_ANIMATION_LENGTH, self.label = "Length", self.tooltip = "Sets the animation length.\n(Will not change frames.)", + self.snapshotAction = "Set Animation Length", self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.min = ANM2_FRAME_NUM_MIN, + self.max = ANM2_FRAME_NUM_MAX, self.value = ANM2_FRAME_NUM_MIN, self.isSameLine = true); + +IMGUI_ITEM(IMGUI_FPS, self.label = "FPS", self.tooltip = "Sets the animation's frames per second (its speed).", self.snapshotAction = "Set FPS", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.min = ANM2_FPS_MIN, self.max = ANM2_FPS_MAX, self.value = ANM2_FPS_DEFAULT, + self.isSameLine = true); + +IMGUI_ITEM(IMGUI_LOOP, self.label = "Loop", self.tooltip = "Toggles the animation looping.", self.snapshotAction = "Set Loop", + self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.value = true, self.isSameLine = true); + +IMGUI_ITEM(IMGUI_CREATED_BY, self.label = "Author", self.tooltip = "Sets the author of the animation.", self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, + self.max = UCHAR_MAX); #define IMGUI_ONIONSKIN_ROW_COUNT 3 IMGUI_ITEM(IMGUI_ONIONSKIN, self.label = "Onionskin"); -IMGUI_ITEM(IMGUI_ONIONSKIN_ENABLED, - self.label = "Enabled", - self.tooltip = "Toggle onionskin (previews of frames before/after the current one.)", - self.function = imgui_onionskin_toggle, - self.hotkey = HOTKEY_ONIONSKIN, - self.isSeparator = true -); +IMGUI_ITEM(IMGUI_ONIONSKIN_ENABLED, self.label = "Enabled", self.tooltip = "Toggle onionskin (previews of frames before/after the current one.)", + self.function = imgui_onionskin_toggle, self.hotkey = HOTKEY_ONIONSKIN, self.isSeparator = true); IMGUI_ITEM(IMGUI_ONIONSKIN_BEFORE, self.label = "-- Before -- "); IMGUI_ITEM(IMGUI_ONIONSKIN_AFTER, self.label = "-- After -- "); -IMGUI_ITEM(IMGUI_ONIONSKIN_COUNT, - self.label = "Count", - self.tooltip = "Set the number of previewed frames appearing.", - self.min = 0, - self.max = 100, - self.value = SETTINGS_ONIONSKIN_BEFORE_COUNT_DEFAULT, - self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ONIONSKIN_COUNT, self.label = "Count", self.tooltip = "Set the number of previewed frames appearing.", self.min = 0, self.max = 100, + self.value = SETTINGS_ONIONSKIN_BEFORE_COUNT_DEFAULT, self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT, self.isSameLine = true); -IMGUI_ITEM(IMGUI_ONIONSKIN_COLOR_OFFSET, - self.label = "Color Offset", - self.tooltip = "Set the color offset of the previewed frames.", - self.flags = ImGuiColorEditFlags_NoInputs, - self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT -); +IMGUI_ITEM(IMGUI_ONIONSKIN_COLOR_OFFSET, self.label = "Color Offset", self.tooltip = "Set the color offset of the previewed frames.", + self.flags = ImGuiColorEditFlags_NoInputs, self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT); -IMGUI_ITEM(IMGUI_ONIONSKIN_DRAW_ORDER, - self.label = "Draw Order", - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ONIONSKIN_DRAW_ORDER, self.label = "Draw Order", self.isSameLine = true); -IMGUI_ITEM(IMGUI_ONIONSKIN_BELOW, - self.label = "Below", - self.tooltip = "The onionskin frames will draw below the base frame.", - self.value = ONIONSKIN_BELOW, - self.isSameLine = true -); +IMGUI_ITEM(IMGUI_ONIONSKIN_BELOW, self.label = "Below", self.tooltip = "The onionskin frames will draw below the base frame.", self.value = ONIONSKIN_BELOW, + self.isSameLine = true); -IMGUI_ITEM(IMGUI_ONIONSKIN_ABOVE, - self.label = "Above", - self.tooltip = "The onionskin frames will draw above the base frame.", - self.value = ONIONSKIN_ABOVE -); +IMGUI_ITEM(IMGUI_ONIONSKIN_ABOVE, self.label = "Above", self.tooltip = "The onionskin frames will draw above the base frame.", self.value = ONIONSKIN_ABOVE); IMGUI_ITEM(IMGUI_CONTEXT_MENU, self.label = "## Context Menu"); -IMGUI_ITEM(IMGUI_CUT, - self.label = "Cut", - self.tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", - self.snapshotAction = "Cut", - self.function = imgui_cut, - self.hotkey = HOTKEY_CUT, - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_CUT, self.label = "Cut", + self.tooltip = "Cuts the currently selected contextual element; " + "removing it and putting it to the clipboard.", + self.snapshotAction = "Cut", self.function = imgui_cut, self.hotkey = HOTKEY_CUT, self.isSizeToText = true); -IMGUI_ITEM(IMGUI_COPY, - self.label = "Copy", - self.tooltip = "Copies the currently selected contextual element to the clipboard.", - self.snapshotAction = "Copy", - self.function = imgui_copy, - self.hotkey = HOTKEY_COPY, - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_COPY, self.label = "Copy", self.tooltip = "Copies the currently selected contextual element to the clipboard.", self.snapshotAction = "Copy", + self.function = imgui_copy, self.hotkey = HOTKEY_COPY, self.isSizeToText = true); -IMGUI_ITEM(IMGUI_PASTE, - self.label = "Paste", - self.tooltip = "Pastes the currently selection contextual element from the clipboard.", - self.snapshotAction = "Paste", - self.function = imgui_paste, - self.hotkey = HOTKEY_PASTE, - self.isSizeToText = true -); +IMGUI_ITEM(IMGUI_PASTE, self.label = "Paste", self.tooltip = "Pastes the currently selection contextual element from the clipboard.", + self.snapshotAction = "Paste", self.function = imgui_paste, self.hotkey = HOTKEY_PASTE, self.isSizeToText = true); -IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, - self.label = "## Input Text", - self.tooltip = "Rename the selected item.", - self.snapshotAction = "Rename Item", - self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, - self.max = UCHAR_MAX -); +IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, self.label = "## Input Text", self.tooltip = "Rename the selected item.", self.snapshotAction = "Rename Item", + self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, self.max = UCHAR_MAX); -IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, - self.label = "## Input Int", - self.tooltip = "Change the selected item's value.", - self.snapshotAction = "Change Value", - self.step = 0 -); +IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, self.label = "## Input Int", self.tooltip = "Change the selected item's value.", self.snapshotAction = "Change Value", + self.step = 0); #define IMGUI_CONFIRM_POPUP_ROW_COUNT 2 -IMGUI_ITEM(IMGUI_POPUP_OK, - self.label = "OK", - self.tooltip = "Confirm the action.", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT -); +IMGUI_ITEM(IMGUI_POPUP_OK, self.label = "OK", self.tooltip = "Confirm the action.", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT); -IMGUI_ITEM(IMGUI_POPUP_CANCEL, - self.label = "Cancel", - self.tooltip = "Cancel the action.", - self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT -); +IMGUI_ITEM(IMGUI_POPUP_CANCEL, self.label = "Cancel", self.tooltip = "Cancel the action.", self.rowCount = IMGUI_CONFIRM_POPUP_ROW_COUNT); -IMGUI_ITEM(IMGUI_LOG_WINDOW, - self.label = "## Log Window", - self.flags = ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | - ImGuiWindowFlags_NoInputs -); +IMGUI_ITEM(IMGUI_LOG_WINDOW, self.label = "## Log Window", + self.flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoInputs); -void imgui_init -( - Imgui* self, - Dialog* dialog, - Resources* resources, - Anm2* anm2, - Anm2Reference* reference, - Editor* editor, - Preview* preview, - GeneratePreview* generatePreview, - Settings* settings, - Snapshots* snapshots, - Clipboard* clipboard, - SDL_Window* window, - SDL_GLContext* glContext -); +IMGUI_ITEM(IMGUI_ATLAS, self.label = "## Atlas Image", self.atlas = ATLAS_NONE); + +void imgui_init(Imgui* self, Dialog* dialog, Resources* resources, Anm2* anm2, Anm2Reference* reference, Editor* editor, Preview* preview, + GeneratePreview* generatePreview, Settings* settings, Snapshots* snapshots, Clipboard* clipboard, SDL_Window* window, SDL_GLContext* glContext); void imgui_update(Imgui* self); void imgui_draw(); diff --git a/src/main.cpp b/src/main.cpp index 42cffed..0970bdf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,61 +1,51 @@ #include "main.h" -static bool _anm2_rescale(const std::string& file, f32 scale) -{ - Anm2 anm2; +static bool _anm2_rescale(const std::string& file, float scale) { + Anm2 anm2; - if (!anm2_deserialize(&anm2, file, false)) return false; - anm2_scale(&anm2, scale); - return anm2_serialize(&anm2, file); + if (!anm2_deserialize(&anm2, file, false)) + return false; + anm2_scale(&anm2, scale); + return anm2_serialize(&anm2, file); } -s32 -main(s32 argc, char* argv[]) -{ - State state; +int main(int argc, char* argv[]) { + State state; - log_init(); + log_init(); - if (argc > 0 && argv[1]) - { - if (std::string(argv[1]) == ARGUMENT_RESCALE) - { - if (argv[2] && argv[3]) - { - if (_anm2_rescale(std::string(argv[2]), atof(argv[3]))) - { - log_info(std::format(ARGUMENT_RESCALE_ANM2_INFO, argv[2], argv[3])); - return EXIT_SUCCESS; - } - else - log_error(ARGUMENT_RESCALE_ANM2_ERROR); - } - else - log_error(ARGUMENT_RESCALE_ARGUMENT_ERROR); - - return EXIT_FAILURE; - } - else if (std::string(argv[1]) == ARGUMENT_TEST && argv[2]) - { - if (anm2_deserialize(&state.anm2, std::string(argv[2]), false)) return EXIT_SUCCESS; - return EXIT_FAILURE; - } - else if (std::string(argv[1]) == ARGUMENT_TEST_GL && argv[2]) - { - if (!sdl_init(&state, true)) return EXIT_FAILURE; - if (anm2_deserialize(&state.anm2, std::string(argv[2]))) return EXIT_SUCCESS; - return EXIT_FAILURE; - } - else - if (argv[1]) state.argument = argv[1]; - } + if (argc > 0 && argv[1]) { + if (std::string(argv[1]) == ARGUMENT_RESCALE) { + if (argv[2] && argv[3]) { + if (_anm2_rescale(std::string(argv[2]), atof(argv[3]))) { + log_info(std::format(ARGUMENT_RESCALE_ANM2_INFO, argv[2], argv[3])); + return EXIT_SUCCESS; + } else + log_error(ARGUMENT_RESCALE_ANM2_ERROR); + } else + log_error(ARGUMENT_RESCALE_ARGUMENT_ERROR); - init(&state); + return EXIT_FAILURE; + } else if (std::string(argv[1]) == ARGUMENT_TEST && argv[2]) { + if (anm2_deserialize(&state.anm2, std::string(argv[2]), false)) + return EXIT_SUCCESS; + return EXIT_FAILURE; + } else if (std::string(argv[1]) == ARGUMENT_TEST_GL && argv[2]) { + if (!sdl_init(&state, true)) + return EXIT_FAILURE; + if (anm2_deserialize(&state.anm2, std::string(argv[2]))) + return EXIT_SUCCESS; + return EXIT_FAILURE; + } else if (argv[1]) + state.argument = argv[1]; + } - while (state.isRunning) - loop(&state); + init(&state); - quit(&state); + while (state.isRunning) + loop(&state); - return EXIT_SUCCESS; + quit(&state); + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/preview.cpp b/src/preview.cpp index 6ef404e..ef13be7 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -1,282 +1,259 @@ #include "preview.h" -static void _preview_render_textures_free(Preview* self) -{ - for (auto& texture : self->renderFrames) - texture_free(&texture); +static void _preview_render_textures_free(Preview* self) { + for (auto& texture : self->renderFrames) + texture_free(&texture); - self->renderFrames.clear(); + self->renderFrames.clear(); } -void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) -{ - self->anm2 = anm2; - self->reference = reference; - self->resources = resources; - self->settings = settings; +void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { + self->anm2 = anm2; + self->reference = reference; + self->resources = resources; + self->settings = settings; - canvas_init(&self->canvas, vec2()); + canvas_init(&self->canvas, vec2()); } -void preview_tick(Preview* self) -{ - f32& time = self->time; - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); +void preview_tick(Preview* self) { + float& time = self->time; + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, *self->reference); - if (animation) - { - if (self->isPlaying) - { - if (self->isRender) - { - ivec2& size = self->canvas.size; - u32 framebufferPixelCount = size.x * size.y * TEXTURE_CHANNELS; - std::vector framebufferPixels(framebufferPixelCount); - Texture frameTexture; + if (animation) { + if (self->isPlaying) { + if (self->isRender) { + ivec2& size = self->canvas.size; + u32 framebufferPixelCount = size.x * size.y * TEXTURE_CHANNELS; + std::vector framebufferPixels(framebufferPixelCount); + Texture frameTexture; - glBindFramebuffer(GL_READ_FRAMEBUFFER, self->canvas.fbo); - glReadBuffer(GL_COLOR_ATTACHMENT0); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data()); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, self->canvas.fbo); + glReadBuffer(GL_COLOR_ATTACHMENT0); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data()); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - texture_from_rgba_init(&frameTexture, size, framebufferPixels.data()); - self->renderFrames.push_back(frameTexture); - } + texture_from_rgba_init(&frameTexture, size, framebufferPixels.data()); + self->renderFrames.push_back(frameTexture); + } - time += (f32)self->anm2->fps / TICK_DELAY; + time += (float)self->anm2->fps / TICK_DELAY; - if (time >= (f32)animation->frameNum - 1) - { - if (self->isRender) - { - self->isRender = false; - self->isRenderFinished = true; - time = 0.0f; - self->isPlaying = false; - } - else - { - if (self->settings->playbackIsLoop) - time = 0.0f; - else - { - time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); - self->isPlaying = false; - } - } - } + if (time >= (float)animation->frameNum - 1) { + if (self->isRender) { + self->isRender = false; + self->isRenderFinished = true; + time = 0.0f; + self->isPlaying = false; + } else { + if (self->settings->playbackIsLoop) + time = 0.0f; + else { + time = std::clamp(time, 0.0f, std::max(0.0f, (float)animation->frameNum - 1)); + self->isPlaying = false; + } } - - if (self->settings->playbackIsClampPlayhead) - time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); - else - time = std::max(time, 0.0f); + } } + + if (self->settings->playbackIsClampPlayhead) + time = std::clamp(time, 0.0f, std::max(0.0f, (float)animation->frameNum - 1)); + else + time = std::max(time, 0.0f); + } } -void preview_draw(Preview* self) -{ - ivec2& gridSize = self->settings->previewGridSize; - ivec2& gridOffset = self->settings->previewGridOffset; - vec4& gridColor = self->settings->previewGridColor; - GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; - GLuint& shaderAxis = self->resources->shaders[SHADER_AXIS]; - GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; - mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER); - - canvas_framebuffer_resize_check(&self->canvas); - - canvas_bind(&self->canvas); - canvas_viewport_set(&self->canvas); - canvas_clear(self->settings->previewBackgroundColor); - - if (self->settings->previewIsGrid) - canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor); +void preview_draw(Preview* self) { + ivec2& gridSize = self->settings->previewGridSize; + ivec2& gridOffset = self->settings->previewGridOffset; + vec4& gridColor = self->settings->previewGridColor; + GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; + GLuint& shaderAxis = self->resources->shaders[SHADER_AXIS]; + GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; + mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER); - if (self->settings->previewIsAxes) - canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor); + canvas_framebuffer_resize_check(&self->canvas); - auto animation_draw = [&](s32 animationID) - { - Anm2Animation* animation = map_find(self->anm2->animations, animationID); - if (!animation) return; + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->previewBackgroundColor); - auto root_draw = [&](Anm2Frame root, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) - { - mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, PERCENT_TO_UNIT(root.scale), root.rotation); - mat4 rootTransform = transform * model; - vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_ROOT_COLOR; - AtlasType atlas = self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; - f32 vertices[] = ATLAS_UV_VERTICES(atlas); - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, color); - }; + if (self->settings->previewIsGrid) + canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor); - auto layer_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) - { - Anm2Item& layerAnimation = animation->layerAnimations[id]; - if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) return; + if (self->settings->previewIsAxes) + canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor); - Anm2Frame frame; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time); - if (!frame.isVisible) return; + auto animation_draw = [&](int animationID) { + Anm2Animation* animation = map_find(self->anm2->animations, animationID); + if (!animation) + return; - mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 layerTransform = transform * (rootModel * model); - vec3 frameColorOffset = frame.offsetRGB + colorOffset; - vec4 frameTint = frame.tintRGBA; - frameTint.a = std::max(0.0f, frameTint.a - alphaOffset); - - Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[id].spritesheetID); - if (!spritesheet) return; - - Texture& texture = spritesheet->texture; - if (texture.isInvalid) return; - - vec2 inset = 0.5f / vec2(texture.size); - vec2 uvMin = frame.crop / vec2(texture.size) + inset; - vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset; - f32 vertices[] = UV_VERTICES(uvMin, uvMax); - canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frameTint, frameColorOffset); - - if (self->settings->previewIsBorder) - { - vec4 borderColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_BORDER_COLOR; - canvas_rect_draw(&self->canvas, shaderLine, layerTransform, borderColor); - } - - if (self->settings->previewIsPivots) - { - vec4 pivotColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_PIVOT_COLOR; - f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); - mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 pivotTransform = transform * (rootModel * pivotModel); - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, pivotColor); - } - }; - - auto null_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) - { - Anm2Item& nullAnimation = animation->nullAnimations[id]; - if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) return; - - Anm2Frame frame; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, time); - if (!frame.isVisible) return; - - Anm2Null null = self->anm2->nulls[id]; - - vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : - (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? - PREVIEW_NULL_SELECTED_COLOR : PREVIEW_NULL_COLOR; - - vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; - AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; - - mat4 model = quad_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 nullTransform = transform * (rootModel * model); - - f32 vertices[] = ATLAS_UV_VERTICES(atlas); - - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, nullTransform, vertices, color); - - if (null.isShowRect) - { - mat4 rectModel = quad_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 rectTransform = transform * (rootModel * rectModel); - canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); - } - }; - - auto base_draw = [&](f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) - { - Anm2Frame root; - anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time); - - mat4 rootModel = self->settings->previewIsRootTransform ? - quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f); - - if (self->settings->previewIsIcons && animation->rootAnimation.isVisible && root.isVisible) - root_draw(root, colorOffset, alphaOffset, isOnionskin); - - for (auto id : animation->layerOrder) - layer_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); - - if (self->settings->previewIsIcons) - for (auto& [id, _] : animation->nullAnimations) - null_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); - }; - - auto onionskin_draw = [&](s32 count, s32 direction, vec3 colorOffset) - { - for (s32 i = 1; i <= count; i++) - { - f32 time = self->time + (f32)(direction * i); - f32 alphaOffset = (1.0f / (count + 1)) * i; - base_draw(time, colorOffset, alphaOffset, true); - } - }; - - auto onionskins_draw = [&]() - { - if (!self->settings->onionskinIsEnabled) return; - onionskin_draw(self->settings->onionskinBeforeCount, -1, self->settings->onionskinBeforeColorOffset); - onionskin_draw(self->settings->onionskinAfterCount, 1, self->settings->onionskinAfterColorOffset); - }; - - if (self->settings->onionskinDrawOrder == ONIONSKIN_BELOW) onionskins_draw(); - base_draw(self->time); - if (self->settings->onionskinDrawOrder == ONIONSKIN_ABOVE) onionskins_draw(); + auto root_draw = [&](Anm2Frame root, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) { + mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, PERCENT_TO_UNIT(root.scale), root.rotation); + mat4 rootTransform = transform * model; + vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_ROOT_COLOR; + AtlasType atlas = self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; + float vertices[] = ATLAS_UV_VERTICES(atlas); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, color); }; - animation_draw(self->reference->animationID); - animation_draw(self->animationOverlayID); + auto layer_draw = [&](mat4 rootModel, int id, float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) { + Anm2Item& layerAnimation = animation->layerAnimations[id]; + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) + return; - canvas_unbind(); + Anm2Frame frame; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time); + if (!frame.isVisible) + return; + + mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 layerTransform = transform * (rootModel * model); + vec3 frameColorOffset = frame.offsetRGB + colorOffset; + vec4 frameTint = frame.tintRGBA; + frameTint.a = std::max(0.0f, frameTint.a - alphaOffset); + + Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[id].spritesheetID); + if (!spritesheet) + return; + + Texture& texture = spritesheet->texture; + if (texture.isInvalid) + return; + + vec2 inset = 0.5f / vec2(texture.size); + vec2 uvMin = frame.crop / vec2(texture.size) + inset; + vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size) - inset; + float vertices[] = UV_VERTICES(uvMin, uvMax); + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frameTint, frameColorOffset); + + if (self->settings->previewIsBorder) { + vec4 borderColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_BORDER_COLOR; + canvas_rect_draw(&self->canvas, shaderLine, layerTransform, borderColor); + } + + if (self->settings->previewIsPivots) { + vec4 pivotColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_PIVOT_COLOR; + float vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); + mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 pivotTransform = transform * (rootModel * pivotModel); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, pivotColor); + } + }; + + auto null_draw = [&](mat4 rootModel, int id, float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) { + Anm2Item& nullAnimation = animation->nullAnimations[id]; + if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) + return; + + Anm2Frame frame; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, time); + if (!frame.isVisible) + return; + + Anm2Null null = self->anm2->nulls[id]; + + vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) + : (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? PREVIEW_NULL_SELECTED_COLOR + : PREVIEW_NULL_COLOR; + + vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; + AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; + + mat4 model = quad_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 nullTransform = transform * (rootModel * model); + + float vertices[] = ATLAS_UV_VERTICES(atlas); + + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, nullTransform, vertices, color); + + if (null.isShowRect) { + mat4 rectModel = quad_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 rectTransform = transform * (rootModel * rectModel); + canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); + } + }; + + auto base_draw = [&](float time, vec3 colorOffset = {}, float alphaOffset = {}, bool isOnionskin = {}) { + Anm2Frame root; + anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time); + + mat4 rootModel = + self->settings->previewIsRootTransform ? quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f); + + if (self->settings->previewIsIcons && animation->rootAnimation.isVisible && root.isVisible) + root_draw(root, colorOffset, alphaOffset, isOnionskin); + + for (auto id : animation->layerOrder) + layer_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); + + if (self->settings->previewIsIcons) + for (auto& [id, _] : animation->nullAnimations) + null_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); + }; + + auto onionskin_draw = [&](int count, int direction, vec3 colorOffset) { + for (int i = 1; i <= count; i++) { + float time = self->time + (float)(direction * i); + float alphaOffset = (1.0f / (count + 1)) * i; + base_draw(time, colorOffset, alphaOffset, true); + } + }; + + auto onionskins_draw = [&]() { + if (!self->settings->onionskinIsEnabled) + return; + onionskin_draw(self->settings->onionskinBeforeCount, -1, self->settings->onionskinBeforeColorOffset); + onionskin_draw(self->settings->onionskinAfterCount, 1, self->settings->onionskinAfterColorOffset); + }; + + if (self->settings->onionskinDrawOrder == ONIONSKIN_BELOW) + onionskins_draw(); + base_draw(self->time); + if (self->settings->onionskinDrawOrder == ONIONSKIN_ABOVE) + onionskins_draw(); + }; + + animation_draw(self->reference->animationID); + animation_draw(self->animationOverlayID); + + canvas_unbind(); } -void preview_render_start(Preview* self) -{ - self->isRender = true; - self->isPlaying = true; - self->time = 0.0f; - _preview_render_textures_free(self); - - self->normalCanvasSize = self->canvas.size; - self->normalCanvasPan = self->settings->previewPan; - self->normalCanvasZoom = self->settings->previewZoom; +void preview_render_start(Preview* self) { + self->isRender = true; + self->isPlaying = true; + self->time = 0.0f; + _preview_render_textures_free(self); - if (self->settings->renderIsUseAnimationBounds) - { - vec4 rect = anm2_animation_rect_get(self->anm2, self->reference, self->settings->previewIsRootTransform); + self->normalCanvasSize = self->canvas.size; + self->normalCanvasPan = self->settings->previewPan; + self->normalCanvasZoom = self->settings->previewZoom; - self->canvas.size = ivec2 - ( - ceilf(rect.z * self->settings->renderScale), - ceilf(rect.w * self->settings->renderScale) - ); + if (self->settings->renderIsUseAnimationBounds) { + vec4 rect = anm2_animation_rect_get(self->anm2, *self->reference, self->settings->previewIsRootTransform); - vec2 rectCenter = vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f); - self->settings->previewPan = -rectCenter * self->settings->renderScale; - self->settings->previewZoom = UNIT_TO_PERCENT(self->settings->renderScale); - } + self->canvas.size = ivec2(ceilf(rect.z * self->settings->renderScale), ceilf(rect.w * self->settings->renderScale)); + + vec2 rectCenter = vec2(rect.x + rect.z * 0.5f, rect.y + rect.w * 0.5f); + self->settings->previewPan = -rectCenter * self->settings->renderScale; + self->settings->previewZoom = UNIT_TO_PERCENT(self->settings->renderScale); + } } -void preview_render_end(Preview* self) -{ - self->isRender = false; - self->isPlaying = false; - self->isRenderFinished = false; - _preview_render_textures_free(self); - - self->canvas.size = self->normalCanvasSize; - self->settings->previewPan = self->normalCanvasPan; - self->settings->previewZoom = self->normalCanvasZoom; +void preview_render_end(Preview* self) { + self->isRender = false; + self->isPlaying = false; + self->isRenderFinished = false; + _preview_render_textures_free(self); + + self->canvas.size = self->normalCanvasSize; + self->settings->previewPan = self->normalCanvasPan; + self->settings->previewZoom = self->normalCanvasZoom; } -void preview_free(Preview* self) -{ - canvas_free(&self->canvas); -} \ No newline at end of file +void preview_free(Preview* self) { canvas_free(&self->canvas); } \ No newline at end of file diff --git a/src/preview.h b/src/preview.h index 7fcfa26..de54696 100644 --- a/src/preview.h +++ b/src/preview.h @@ -1,9 +1,9 @@ #pragma once #include "anm2.h" +#include "canvas.h" #include "resources.h" #include "settings.h" -#include "canvas.h" const vec2 PREVIEW_SIZE = {2000, 2000}; const vec2 PREVIEW_CANVAS_SIZE = {2000, 2000}; @@ -26,23 +26,22 @@ const vec4 PREVIEW_NULL_COLOR = COLOR_BLUE; const vec4 PREVIEW_NULL_SELECTED_COLOR = COLOR_RED; const vec4 PREVIEW_PIVOT_COLOR = COLOR_RED; -struct Preview -{ - Anm2* anm2 = nullptr; - Anm2Reference* reference = nullptr; - Resources* resources = nullptr; - Settings* settings = nullptr; - s32 animationOverlayID = ID_NONE; - Canvas canvas; - vec2 normalCanvasSize{}; - vec2 normalCanvasPan{}; - f32 normalCanvasZoom{}; - bool isPlaying = false; - bool isRender = false; - bool isRenderFinished = false; - bool isRenderCancelled = false; - std::vector renderFrames; - f32 time{}; +struct Preview { + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; + int animationOverlayID = ID_NONE; + Canvas canvas; + vec2 normalCanvasSize{}; + vec2 normalCanvasPan{}; + float normalCanvasZoom{}; + bool isPlaying = false; + bool isRender = false; + bool isRenderFinished = false; + bool isRenderCancelled = false; + std::vector renderFrames; + float time{}; }; void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); diff --git a/src/resources.cpp b/src/resources.cpp index 09f0ea5..c338e88 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -1,17 +1,15 @@ #include "resources.h" -void resources_init(Resources* self) -{ - texture_from_encoded_data_init(&self->atlas, TEXTURE_ATLAS_SIZE, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH); - - for (s32 i = 0; i < SHADER_COUNT; i++) - shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); +void resources_init(Resources* self) { + texture_from_path_init(&self->atlas, ATLAS_PATH); + + for (int i = 0; i < SHADER_COUNT; i++) + shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); } -void resources_free(Resources* self) -{ - for (auto& shader : self->shaders) - shader_free(&shader); +void resources_free(Resources* self) { + for (auto& shader : self->shaders) + shader_free(&shader); - texture_free(&self->atlas); + texture_free(&self->atlas); } \ No newline at end of file diff --git a/src/resources.h b/src/resources.h index 912ceca..af0dbcb 100644 --- a/src/resources.h +++ b/src/resources.h @@ -1,15 +1,14 @@ #pragma once -#include "PACKED.h" -#include "texture.h" +#include "RESOURCE.h" #include "shader.h" +#include "texture.h" #define RESOURCES_TEXTURES_FREE_INFO "Freed texture resources" -struct Resources -{ - GLuint shaders[SHADER_COUNT]; - Texture atlas; +struct Resources { + GLuint shaders[SHADER_COUNT]; + Texture atlas; }; void resources_init(Resources* self); diff --git a/src/settings.cpp b/src/settings.cpp index 81347d9..d1b31e2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,276 +1,305 @@ #include "settings.h" -static void _settings_setting_load(Settings* self, const std::string& line) -{ - for (s32 i = 0; i < SETTINGS_COUNT; i++) - { - const auto& entry = SETTINGS_ENTRIES[i]; - const std::string& key = entry.key; - void* target = (u8*)self + entry.offset; +static void _settings_setting_load(Settings* self, const std::string& line) { + for (int i = 0; i < SETTINGS_COUNT; i++) { + const auto& entry = SETTINGS_ENTRIES[i]; + const std::string& key = entry.key; + void* target = (u8*)self + entry.offset; - auto match_key = [&](const std::string& full) -> const char* - { - if (!line.starts_with(full)) - return nullptr; + auto match_key = [&](const std::string& full) -> const char* { + if (!line.starts_with(full)) + return nullptr; - size_t p = full.size(); - while (p < line.size() && std::isspace((u8)line[p])) ++p; - if (p < line.size() && line[p] == '=') - return line.c_str() + p + 1; - return nullptr; - }; + size_t p = full.size(); + while (p < line.size() && std::isspace((u8)line[p])) + ++p; + if (p < line.size() && line[p] == '=') + return line.c_str() + p + 1; + return nullptr; + }; - const char* value = nullptr; + const char* value = nullptr; - switch (entry.type) - { - case TYPE_INT: - if ((value = match_key(key))) { *(s32*)target = std::atoi(value); return; } - break; - case TYPE_BOOL: - if ((value = match_key(key))) { *(bool*)target = string_to_bool(value); return; } - break; - case TYPE_FLOAT: - if ((value = match_key(key))) { *(f32*)target = std::atof(value); return; } - break; - case TYPE_STRING: - if ((value = match_key(key))) { *(std::string*)target = value; return; } - break; - case TYPE_IVEC2: - { - ivec2* v = (ivec2*)target; - if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; } - if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; } - break; - } - case TYPE_IVEC2_WH: - { - ivec2* v = (ivec2*)target; - if ((value = match_key(key + "W"))) { v->x = std::atoi(value); return; } - if ((value = match_key(key + "H"))) { v->y = std::atoi(value); return; } - break; - }; - case TYPE_VEC2: - { - vec2* v = (vec2*)target; - if ((value = match_key(key + "X"))) { v->x = std::atof(value); return; } - if ((value = match_key(key + "Y"))) { v->y = std::atof(value); return; } - break; - } - case TYPE_VEC2_WH: - { - vec2* v = (vec2*)target; - if ((value = match_key(key + "W"))) { v->x = std::atof(value); return; } - if ((value = match_key(key + "H"))) { v->y = std::atof(value); return; } - break; - }; - case TYPE_VEC3: - { - vec3* v = (vec3*)target; - if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } - if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } - if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } - break; - } - case TYPE_VEC4: - { - vec4* v = (vec4*)target; - if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } - if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } - if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } - if ((value = match_key(key + "A"))) { v->w = std::atof(value); return; } - break; - } - default: - break; - } - } - - log_warning(std::format(SETTINGS_VALUE_INIT_WARNING, line)); -} - -std::string settings_path_get(void) -{ - std::string filePath = preferences_path_get() + SETTINGS_PATH; - return filePath; -} - -static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) -{ - u8* selfPointer = (u8*)self; - std::string value; - - switch (entry.type) - { - case TYPE_INT: - value = std::format("{}", *(s32*)(selfPointer + entry.offset)); - out << entry.key << "=" << value << "\n"; - break; - case TYPE_BOOL: - value = std::format("{}", *(bool*)(selfPointer + entry.offset)); - out << entry.key << "=" << value << "\n"; - break; - case TYPE_FLOAT: - value = std::format("{:.3f}", *(f32*)(selfPointer + entry.offset)); - out << entry.key << "=" << value << "\n"; - break; - case TYPE_STRING: - { - const std::string data = *reinterpret_cast(selfPointer + entry.offset); - if (!data.empty()) - out << entry.key << "=" << data.c_str() << "\n"; - break; - } - case TYPE_IVEC2: - { - ivec2* data = (ivec2*)(selfPointer + entry.offset); - out << entry.key << "X=" << data->x << "\n"; - out << entry.key << "Y=" << data->y << "\n"; - break; - } - case TYPE_IVEC2_WH: - { - ivec2* data = (ivec2*)(selfPointer + entry.offset); - out << entry.key << "W=" << data->x << "\n"; - out << entry.key << "H=" << data->y << "\n"; - break; - } - case TYPE_VEC2: - { - vec2* data = (vec2*)(selfPointer + entry.offset); - out << entry.key << "X=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; - out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; - break; - } - case TYPE_VEC2_WH: - { - vec2* data = (vec2*)(selfPointer + entry.offset); - out << entry.key << "W=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; - out << entry.key << "H=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; - break; - } - case TYPE_VEC3: - { - vec3* data = (vec3*)(selfPointer + entry.offset); - out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; - out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; - out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; - break; - } - case TYPE_VEC4: - { - vec4* data = (vec4*)(selfPointer + entry.offset); - out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; - out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; - out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; - out << entry.key << "A=" << std::format(SETTINGS_FLOAT_FORMAT, data->a) << "\n"; - break; - } - default: - break; - } -} - -void settings_save(Settings* self) -{ - const std::string path = settings_path_get(); - const std::filesystem::path filesystemPath(path); - const std::filesystem::path directory = filesystemPath.parent_path(); - - if (!directory.empty()) - { - std::error_code errorCode; - std::filesystem::create_directories(directory, errorCode); - if (errorCode) - { - log_error(std::format(SETTINGS_DIRECTORY_ERROR, directory.string(), errorCode.message())); - return; - } - } - - std::string data; - if (std::filesystem::exists(filesystemPath)) - { - if (std::ifstream in(path, std::ios::binary); in) - data.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); - } - - std::filesystem::path temp = filesystemPath; - temp += SETTINGS_TEMPORARY_EXTENSION; - - std::ofstream out(temp, std::ios::binary | std::ios::trunc); - if (!out) - { - log_error(std::format(SETTINGS_INIT_ERROR, temp.string())); + switch (entry.type) { + case TYPE_INT: + if ((value = match_key(key))) { + *(int*)target = std::atoi(value); return; - } - - out << SETTINGS_SECTION << "\n"; - for (s32 i = 0; i < SETTINGS_COUNT; i++) - _settings_setting_write(self, out, SETTINGS_ENTRIES[i]); - - out << "\n" << SETTINGS_SECTION_IMGUI << "\n"; - out << data; - - out.flush(); - - if (!out.good()) - { - log_error(std::format(SETTINGS_SAVE_ERROR, temp.string())); + } + break; + case TYPE_BOOL: + if ((value = match_key(key))) { + *(bool*)target = string_to_bool(value); return; + } + break; + case TYPE_FLOAT: + if ((value = match_key(key))) { + *(f32*)target = std::atof(value); + return; + } + break; + case TYPE_STRING: + if ((value = match_key(key))) { + *(std::string*)target = value; + return; + } + break; + case TYPE_IVEC2: { + ivec2* v = (ivec2*)target; + if ((value = match_key(key + "X"))) { + v->x = std::atoi(value); + return; + } + if ((value = match_key(key + "Y"))) { + v->y = std::atoi(value); + return; + } + break; } + case TYPE_IVEC2_WH: { + ivec2* v = (ivec2*)target; + if ((value = match_key(key + "W"))) { + v->x = std::atoi(value); + return; + } + if ((value = match_key(key + "H"))) { + v->y = std::atoi(value); + return; + } + break; + }; + case TYPE_VEC2: { + vec2* v = (vec2*)target; + if ((value = match_key(key + "X"))) { + v->x = std::atof(value); + return; + } + if ((value = match_key(key + "Y"))) { + v->y = std::atof(value); + return; + } + break; + } + case TYPE_VEC2_WH: { + vec2* v = (vec2*)target; + if ((value = match_key(key + "W"))) { + v->x = std::atof(value); + return; + } + if ((value = match_key(key + "H"))) { + v->y = std::atof(value); + return; + } + break; + }; + case TYPE_VEC3: { + vec3* v = (vec3*)target; + if ((value = match_key(key + "R"))) { + v->x = std::atof(value); + return; + } + if ((value = match_key(key + "G"))) { + v->y = std::atof(value); + return; + } + if ((value = match_key(key + "B"))) { + v->z = std::atof(value); + return; + } + break; + } + case TYPE_VEC4: { + vec4* v = (vec4*)target; + if ((value = match_key(key + "R"))) { + v->x = std::atof(value); + return; + } + if ((value = match_key(key + "G"))) { + v->y = std::atof(value); + return; + } + if ((value = match_key(key + "B"))) { + v->z = std::atof(value); + return; + } + if ((value = match_key(key + "A"))) { + v->w = std::atof(value); + return; + } + break; + } + default: + break; + } + } - out.close(); + log_warning(std::format(SETTINGS_VALUE_INIT_WARNING, line)); +} +std::string settings_path_get(void) { + std::string filePath = preferences_path_get() + SETTINGS_PATH; + return filePath; +} + +static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) { + u8* selfPointer = (u8*)self; + std::string value; + + switch (entry.type) { + case TYPE_INT: + value = std::format("{}", *(int*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; + break; + case TYPE_BOOL: + value = std::format("{}", *(bool*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; + break; + case TYPE_FLOAT: + value = std::format("{:.3f}", *(f32*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; + break; + case TYPE_STRING: { + const std::string data = *reinterpret_cast(selfPointer + entry.offset); + if (!data.empty()) + out << entry.key << "=" << data.c_str() << "\n"; + break; + } + case TYPE_IVEC2: { + ivec2* data = (ivec2*)(selfPointer + entry.offset); + out << entry.key << "X=" << data->x << "\n"; + out << entry.key << "Y=" << data->y << "\n"; + break; + } + case TYPE_IVEC2_WH: { + ivec2* data = (ivec2*)(selfPointer + entry.offset); + out << entry.key << "W=" << data->x << "\n"; + out << entry.key << "H=" << data->y << "\n"; + break; + } + case TYPE_VEC2: { + vec2* data = (vec2*)(selfPointer + entry.offset); + out << entry.key << "X=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; + out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; + break; + } + case TYPE_VEC2_WH: { + vec2* data = (vec2*)(selfPointer + entry.offset); + out << entry.key << "W=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; + out << entry.key << "H=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; + break; + } + case TYPE_VEC3: { + vec3* data = (vec3*)(selfPointer + entry.offset); + out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; + out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; + out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; + break; + } + case TYPE_VEC4: { + vec4* data = (vec4*)(selfPointer + entry.offset); + out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; + out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; + out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; + out << entry.key << "A=" << std::format(SETTINGS_FLOAT_FORMAT, data->a) << "\n"; + break; + } + default: + break; + } +} + +void settings_save(Settings* self) { + const std::string path = settings_path_get(); + const std::filesystem::path filesystemPath(path); + const std::filesystem::path directory = filesystemPath.parent_path(); + + if (!directory.empty()) { std::error_code errorCode; - std::filesystem::rename(temp, filesystemPath, errorCode); - if (errorCode) - { - // Windows can block rename if target exists; try remove+rename - std::filesystem::remove(filesystemPath, errorCode); - errorCode = {}; - std::filesystem::rename(temp, filesystemPath, errorCode); - if (errorCode) - { - log_error(std::format(SETTINGS_SAVE_FINALIZE_ERROR, filesystemPath.string(), errorCode.message())); - std::filesystem::remove(temp); - return; - } + std::filesystem::create_directories(directory, errorCode); + if (errorCode) { + log_error(std::format(SETTINGS_DIRECTORY_ERROR, directory.string(), errorCode.message())); + return; } + } - log_info(std::format(SETTINGS_SAVE_INFO, path)); + std::string data; + if (std::filesystem::exists(filesystemPath)) { + if (std::ifstream in(path, std::ios::binary); in) + data.assign(std::istreambuf_iterator(in), std::istreambuf_iterator()); + } + + std::filesystem::path temp = filesystemPath; + temp += SETTINGS_TEMPORARY_EXTENSION; + + std::ofstream out(temp, std::ios::binary | std::ios::trunc); + if (!out) { + log_error(std::format(SETTINGS_INIT_ERROR, temp.string())); + return; + } + + out << SETTINGS_SECTION << "\n"; + for (int i = 0; i < SETTINGS_COUNT; i++) + _settings_setting_write(self, out, SETTINGS_ENTRIES[i]); + + out << "\n" << SETTINGS_SECTION_IMGUI << "\n"; + out << data; + + out.flush(); + + if (!out.good()) { + log_error(std::format(SETTINGS_SAVE_ERROR, temp.string())); + return; + } + + out.close(); + + std::error_code errorCode; + std::filesystem::rename(temp, filesystemPath, errorCode); + if (errorCode) { + // Windows can block rename if target exists; try remove+rename + std::filesystem::remove(filesystemPath, errorCode); + errorCode = {}; + std::filesystem::rename(temp, filesystemPath, errorCode); + if (errorCode) { + log_error(std::format(SETTINGS_SAVE_FINALIZE_ERROR, filesystemPath.string(), errorCode.message())); + std::filesystem::remove(temp); + return; + } + } + + log_info(std::format(SETTINGS_SAVE_INFO, path)); } -void settings_init(Settings* self) -{ - const std::string path = settings_path_get(); - std::ifstream file(path, std::ios::binary); +void settings_init(Settings* self) { + const std::string path = settings_path_get(); + std::ifstream file(path, std::ios::binary); - if (file) - log_info(std::format(SETTINGS_INIT_INFO, path)); - else - { - log_warning(std::format(SETTINGS_INIT_WARNING, path)); - settings_save(self); - std::ofstream out(path, std::ios::binary | std::ios::app); - out << SETTINGS_IMGUI_DEFAULT; - out.flush(); - out.close(); - file.open(path, std::ios::binary); - } - - std::string line; - bool inSettingsSection = false; - - while (std::getline(file, line)) - { - if (line == SETTINGS_SECTION) - { - inSettingsSection = true; - continue; - } - if (line.empty()) continue; - if (line == SETTINGS_SECTION_IMGUI) break; - if (inSettingsSection) _settings_setting_load(self, line); + if (file) + log_info(std::format(SETTINGS_INIT_INFO, path)); + else { + log_warning(std::format(SETTINGS_INIT_WARNING, path)); + settings_save(self); + std::ofstream out(path, std::ios::binary | std::ios::app); + out << SETTINGS_IMGUI_DEFAULT; + out.flush(); + out.close(); + file.open(path, std::ios::binary); + } + + std::string line; + bool inSettingsSection = false; + + while (std::getline(file, line)) { + if (line == SETTINGS_SECTION) { + inSettingsSection = true; + continue; } + if (line.empty()) + continue; + if (line == SETTINGS_SECTION_IMGUI) + break; + if (inSettingsSection) + _settings_setting_load(self, line); + } } \ No newline at end of file diff --git a/src/settings.h b/src/settings.h index d56edd9..5271063 100644 --- a/src/settings.h +++ b/src/settings.h @@ -26,235 +26,240 @@ #define SETTINGS_RENDER_FFMPEG_PATH_VALUE_DEFAULT "/usr/bin/ffmpeg" #endif -#define SETTINGS_LIST \ - /* name, symbol, type, defaultValue */ \ - X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1600, 900}) \ - X(isVsync, IS_VSYNC, TYPE_BOOL, true) \ - \ - X(hotkeyCenterView, HOTKEY_CENTER_VIEW, TYPE_STRING, "Home") \ - X(hotkeyFit, HOTKEY_FIT, TYPE_STRING, "F") \ - X(hotkeyZoomIn, HOTKEY_ZOOM_IN, TYPE_STRING, "Ctrl++") \ - X(hotkeyZoomOut, HOTKEY_ZOOM_OUT, TYPE_STRING, "Ctrl+-") \ - X(hotkeyPlayPause, HOTKEY_PLAY_PAUSE, TYPE_STRING, "Space") \ - X(hotkeyOnionskin, HOTKEY_ONIONSKIN, TYPE_STRING, "O") \ - X(hotkeyNew, HOTKEY_NEW, TYPE_STRING, "Ctrl+N") \ - X(hotkeyOpen, HOTKEY_OPEN, TYPE_STRING, "Ctrl+O") \ - X(hotkeySave, HOTKEY_SAVE, TYPE_STRING, "Ctrl+S") \ - X(hotkeySaveAs, HOTKEY_SAVE_AS, TYPE_STRING, "Ctrl+Shift+S") \ - X(hotkeyExit, HOTKEY_EXIT, TYPE_STRING, "Alt+F4") \ - X(hotkeyPan, HOTKEY_PAN, TYPE_STRING, "P") \ - X(hotkeyMove, HOTKEY_MOVE, TYPE_STRING, "V") \ - X(hotkeyRotate, HOTKEY_ROTATE, TYPE_STRING, "R") \ - X(hotkeyScale, HOTKEY_SCALE, TYPE_STRING, "S") \ - X(hotkeyCrop, HOTKEY_CROP, TYPE_STRING, "C") \ - X(hotkeyDraw, HOTKEY_DRAW, TYPE_STRING, "B") \ - X(hotkeyErase, HOTKEY_ERASE, TYPE_STRING, "E") \ - X(hotkeyColorPicker, HOTKEY_COLOR_PICKER, TYPE_STRING, "I") \ - X(hotkeyUndo, HOTKEY_UNDO, TYPE_STRING, "Ctrl+Z") \ - X(hotkeyRedo, HOTKEY_REDO, TYPE_STRING, "Ctrl+Shift+Z") \ - X(hotkeyCopy, HOTKEY_COPY, TYPE_STRING, "Ctrl+C") \ - X(hotkeyCut, HOTKEY_CUT, TYPE_STRING, "Ctrl+X") \ - X(hotkeyPaste, HOTKEY_PASTE, TYPE_STRING, "Ctrl+V") \ - \ - X(playbackIsLoop, PLAYBACK_IS_LOOP, TYPE_BOOL, true) \ - X(playbackIsClampPlayhead,PLAYBACK_IS_CLAMP_PLAYHEAD, TYPE_BOOL, true) \ - \ - X(changeIsCrop, CHANGE_IS_CROP, TYPE_BOOL, false) \ - X(changeIsSize, CHANGE_IS_SIZE, TYPE_BOOL, false) \ - X(changeIsPosition, CHANGE_IS_POSITION, TYPE_BOOL, false) \ - X(changeIsPivot, CHANGE_IS_PIVOT, TYPE_BOOL, false) \ - X(changeIsScale, CHANGE_IS_SCALE, TYPE_BOOL, false) \ - X(changeIsRotation, CHANGE_IS_ROTATION, TYPE_BOOL, false) \ - X(changeIsDelay, CHANGE_IS_DELAY, TYPE_BOOL, false) \ - X(changeIsTint, CHANGE_IS_TINT, TYPE_BOOL, false) \ - X(changeIsColorOffset, CHANGE_IS_COLOR_OFFSET, TYPE_BOOL, false) \ - X(changeIsVisibleSet, CHANGE_IS_VISIBLE_SET, TYPE_BOOL, false) \ - X(changeIsInterpolatedSet,CHANGE_IS_INTERPOLATED_SET, TYPE_BOOL, false) \ - X(changeIsFromSelectedFrame,CHANGE_IS_FROM_SELECTED_FRAME,TYPE_BOOL, false) \ - X(changeCrop, CHANGE_CROP, TYPE_VEC2, {}) \ - X(changeSize, CHANGE_SIZE, TYPE_VEC2, {}) \ - X(changePosition, CHANGE_POSITION, TYPE_VEC2, {}) \ - X(changePivot, CHANGE_PIVOT, TYPE_VEC2, {}) \ - X(changeScale, CHANGE_SCALE, TYPE_VEC2, {}) \ - X(changeRotation, CHANGE_ROTATION, TYPE_FLOAT, 0.0f) \ - X(changeDelay, CHANGE_DELAY, TYPE_INT, 0) \ - X(changeTint, CHANGE_TINT, TYPE_VEC4, {}) \ - X(changeColorOffset, CHANGE_COLOR_OFFSET, TYPE_VEC3, {}) \ - X(changeIsVisible, CHANGE_IS_VISIBLE, TYPE_BOOL, false) \ - X(changeIsInterpolated, CHANGE_IS_INTERPOLATED, TYPE_BOOL, false) \ - X(changeNumberFrames, CHANGE_NUMBER_FRAMES, TYPE_INT, 1) \ - \ - X(scaleValue, SCALE_VALUE, TYPE_FLOAT, 1.0f) \ - \ - X(previewIsAxes, PREVIEW_IS_AXES, TYPE_BOOL, true) \ - X(previewIsGrid, PREVIEW_IS_GRID, TYPE_BOOL, true) \ - X(previewIsRootTransform, PREVIEW_IS_ROOT_TRANSFORM, TYPE_BOOL, true) \ - X(previewIsTriggers, PREVIEW_IS_TRIGGERS, TYPE_BOOL, true) \ - X(previewIsPivots, PREVIEW_IS_PIVOTS, TYPE_BOOL, false) \ - X(previewIsIcons, PREVIEW_IS_ICONS, TYPE_BOOL, true) \ - X(previewIsBorder, PREVIEW_IS_BORDER, TYPE_BOOL, false) \ - X(previewIsAltIcons, PREVIEW_IS_ALT_ICONS, TYPE_BOOL, false) \ - X(previewOverlayTransparency,PREVIEW_OVERLAY_TRANSPARENCY,TYPE_FLOAT, 255.0f) \ - X(previewZoom, PREVIEW_ZOOM, TYPE_FLOAT, 200.0f) \ - X(previewPan, PREVIEW_PAN, TYPE_VEC2, {}) \ - X(previewGridSize, PREVIEW_GRID_SIZE, TYPE_IVEC2, {32,32}) \ - X(previewGridOffset, PREVIEW_GRID_OFFSET, TYPE_IVEC2, {}) \ - X(previewGridColor, PREVIEW_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ - X(previewAxesColor, PREVIEW_AXES_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ - X(previewBackgroundColor, PREVIEW_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \ - \ - X(propertiesIsRound, PROPERTIES_IS_ROUND, TYPE_BOOL, false) \ - \ - X(generateStartPosition, GENERATE_START_POSITION, TYPE_IVEC2, {}) \ - X(generateSize, GENERATE_SIZE, TYPE_IVEC2, {64,64}) \ - X(generatePivot, GENERATE_PIVOT, TYPE_IVEC2, {32,32}) \ - X(generateRows, GENERATE_ROWS, TYPE_INT, 4) \ - X(generateColumns, GENERATE_COLUMNS, TYPE_INT, 4) \ - X(generateCount, GENERATE_COUNT, TYPE_INT, 16) \ - X(generateDelay, GENERATE_DELAY, TYPE_INT, 1) \ - \ - X(editorIsGrid, EDITOR_IS_GRID, TYPE_BOOL, true) \ - X(editorIsGridSnap, EDITOR_IS_GRID_SNAP, TYPE_BOOL, true) \ - X(editorIsBorder, EDITOR_IS_BORDER, TYPE_BOOL, true) \ - X(editorZoom, EDITOR_ZOOM, TYPE_FLOAT, 200.0f) \ - X(editorPan, EDITOR_PAN, TYPE_VEC2, {0.0,0.0}) \ - X(editorGridSize, EDITOR_GRID_SIZE, TYPE_IVEC2, {32,32}) \ - X(editorGridOffset, EDITOR_GRID_OFFSET, TYPE_IVEC2, {32,32}) \ - X(editorGridColor, EDITOR_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ - X(editorBackgroundColor, EDITOR_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \ - \ - X(mergeType, MERGE_TYPE, TYPE_INT, ANM2_MERGE_APPEND) \ - X(mergeIsDeleteAnimationsAfter,MERGE_IS_DELETE_ANIMATIONS_AFTER,TYPE_BOOL, false) \ - \ - X(bakeInterval, BAKE_INTERVAL, TYPE_INT, 1) \ - X(bakeIsRoundScale, BAKE_IS_ROUND_SCALE, TYPE_BOOL, true) \ - X(bakeIsRoundRotation, BAKE_IS_ROUND_ROTATION, TYPE_BOOL, true) \ - \ - X(timelineAddItemType, TIMELINE_ADD_ITEM_TYPE, TYPE_INT, ANM2_LAYER) \ - X(timelineIsShowUnused, TIMELINE_IS_SHOW_UNUSED, TYPE_BOOL, true) \ - \ - X(onionskinIsEnabled, ONIONSKIN_IS_ENABLED, TYPE_BOOL, false) \ - X(onionskinDrawOrder, ONIONSKIN_DRAW_ORDER, TYPE_INT, ONIONSKIN_BELOW) \ - X(onionskinBeforeCount, ONIONSKIN_BEFORE_COUNT, TYPE_INT, 0) \ - X(onionskinAfterCount, ONIONSKIN_AFTER_COUNT, TYPE_INT, 0) \ - X(onionskinBeforeColorOffset,ONIONSKIN_BEFORE_COLOR_OFFSET,TYPE_VEC3, COLOR_RED) \ - X(onionskinAfterColorOffset, ONIONSKIN_AFTER_COLOR_OFFSET,TYPE_VEC3, COLOR_BLUE) \ - \ - X(tool, TOOL, TYPE_INT, TOOL_PAN) \ - X(toolColor, TOOL_COLOR, TYPE_VEC4, {1.0,1.0,1.0,1.0}) \ - \ - X(renderType, RENDER_TYPE, TYPE_INT, RENDER_PNG) \ - X(renderPath, RENDER_PATH, TYPE_STRING, ".") \ - X(renderFormat, RENDER_FORMAT, TYPE_STRING, "{}.png") \ - X(renderIsUseAnimationBounds,RENDER_IS_USE_ANIMATION_BOUNDS,TYPE_BOOL, true) \ - X(renderIsTransparent, RENDER_IS_TRANSPARENT, TYPE_BOOL, true) \ - X(renderScale, RENDER_SCALE, TYPE_FLOAT, 1.0f) \ - X(renderFFmpegPath, RENDER_FFMPEG_PATH, TYPE_STRING, SETTINGS_RENDER_FFMPEG_PATH_VALUE_DEFAULT) +#define SETTINGS_LIST \ + /* Symbol / Name / Type / Default */ \ + X(WINDOW_SIZE, windowSize, TYPE_IVEC2_WH, {1600, 900}) \ + X(IS_VSYNC, isVsync, TYPE_BOOL, true) \ + X(DISPLAY_SCALE, displayScale, TYPE_FLOAT, 1.0f) \ + \ + X(HOTKEY_CENTER_VIEW, hotkeyCenterView, TYPE_STRING, "Home") \ + X(HOTKEY_FIT, hotkeyFit, TYPE_STRING, "F") \ + X(HOTKEY_ZOOM_IN, hotkeyZoomIn, TYPE_STRING, "Ctrl++") \ + X(HOTKEY_ZOOM_OUT, hotkeyZoomOut, TYPE_STRING, "Ctrl+-") \ + X(HOTKEY_PLAY_PAUSE, hotkeyPlayPause, TYPE_STRING, "Space") \ + X(HOTKEY_ONIONSKIN, hotkeyOnionskin, TYPE_STRING, "O") \ + X(HOTKEY_NEW, hotkeyNew, TYPE_STRING, "Ctrl+N") \ + X(HOTKEY_OPEN, hotkeyOpen, TYPE_STRING, "Ctrl+O") \ + X(HOTKEY_SAVE, hotkeySave, TYPE_STRING, "Ctrl+S") \ + X(HOTKEY_SAVE_AS, hotkeySaveAs, TYPE_STRING, "Ctrl+Shift+S") \ + X(HOTKEY_EXIT, hotkeyExit, TYPE_STRING, "Alt+F4") \ + X(HOTKEY_SHORTEN_FRAME, hotkeyShortenFrame, TYPE_STRING, "F4") \ + X(HOTKEY_EXTEND_FRAME, hotkeyExtendFrame, TYPE_STRING, "F5") \ + X(HOTKEY_INSERT_FRAME, hotkeyInsertFrame, TYPE_STRING, "F6") \ + X(HOTKEY_PREVIOUS_FRAME, hotkeyPreviousFrame, TYPE_STRING, "Comma") \ + X(HOTKEY_NEXT_FRAME, hotkeyNextFrame, TYPE_STRING, "Period") \ + X(HOTKEY_PAN, hotkeyPan, TYPE_STRING, "P") \ + X(HOTKEY_MOVE, hotkeyMove, TYPE_STRING, "V") \ + X(HOTKEY_ROTATE, hotkeyRotate, TYPE_STRING, "R") \ + X(HOTKEY_SCALE, hotkeyScale, TYPE_STRING, "S") \ + X(HOTKEY_CROP, hotkeyCrop, TYPE_STRING, "C") \ + X(HOTKEY_DRAW, hotkeyDraw, TYPE_STRING, "B") \ + X(HOTKEY_ERASE, hotkeyErase, TYPE_STRING, "E") \ + X(HOTKEY_COLOR_PICKER, hotkeyColorPicker, TYPE_STRING, "I") \ + X(HOTKEY_UNDO, hotkeyUndo, TYPE_STRING, "Ctrl+Z") \ + X(HOTKEY_REDO, hotkeyRedo, TYPE_STRING, "Ctrl+Shift+Z") \ + X(HOTKEY_COPY, hotkeyCopy, TYPE_STRING, "Ctrl+C") \ + X(HOTKEY_CUT, hotkeyCut, TYPE_STRING, "Ctrl+X") \ + X(HOTKEY_PASTE, hotkeyPaste, TYPE_STRING, "Ctrl+V") \ + \ + X(PLAYBACK_IS_LOOP, playbackIsLoop, TYPE_BOOL, true) \ + X(PLAYBACK_IS_CLAMP_PLAYHEAD, playbackIsClampPlayhead, TYPE_BOOL, true) \ + \ + X(CHANGE_IS_CROP, changeIsCrop, TYPE_BOOL, false) \ + X(CHANGE_IS_SIZE, changeIsSize, TYPE_BOOL, false) \ + X(CHANGE_IS_POSITION, changeIsPosition, TYPE_BOOL, false) \ + X(CHANGE_IS_PIVOT, changeIsPivot, TYPE_BOOL, false) \ + X(CHANGE_IS_SCALE, changeIsScale, TYPE_BOOL, false) \ + X(CHANGE_IS_ROTATION, changeIsRotation, TYPE_BOOL, false) \ + X(CHANGE_IS_DELAY, changeIsDelay, TYPE_BOOL, false) \ + X(CHANGE_IS_TINT, changeIsTint, TYPE_BOOL, false) \ + X(CHANGE_IS_COLOR_OFFSET, changeIsColorOffset, TYPE_BOOL, false) \ + X(CHANGE_IS_VISIBLE_SET, changeIsVisibleSet, TYPE_BOOL, false) \ + X(CHANGE_IS_INTERPOLATED_SET, changeIsInterpolatedSet, TYPE_BOOL, false) \ + X(CHANGE_IS_FROM_SELECTED_FRAME, changeIsFromSelectedFrame, TYPE_BOOL, false) \ + X(CHANGE_CROP, changeCrop, TYPE_VEC2, {}) \ + X(CHANGE_SIZE, changeSize, TYPE_VEC2, {}) \ + X(CHANGE_POSITION, changePosition, TYPE_VEC2, {}) \ + X(CHANGE_PIVOT, changePivot, TYPE_VEC2, {}) \ + X(CHANGE_SCALE, changeScale, TYPE_VEC2, {}) \ + X(CHANGE_ROTATION, changeRotation, TYPE_FLOAT, 0.0f) \ + X(CHANGE_DELAY, changeDelay, TYPE_INT, 0) \ + X(CHANGE_TINT, changeTint, TYPE_VEC4, {}) \ + X(CHANGE_COLOR_OFFSET, changeColorOffset, TYPE_VEC3, {}) \ + X(CHANGE_IS_VISIBLE, changeIsVisible, TYPE_BOOL, false) \ + X(CHANGE_IS_INTERPOLATED, changeIsInterpolated, TYPE_BOOL, false) \ + X(CHANGE_NUMBER_FRAMES, changeNumberFrames, TYPE_INT, 1) \ + \ + X(SCALE_VALUE, scaleValue, TYPE_FLOAT, 1.0f) \ + \ + X(PREVIEW_IS_AXES, previewIsAxes, TYPE_BOOL, true) \ + X(PREVIEW_IS_GRID, previewIsGrid, TYPE_BOOL, true) \ + X(PREVIEW_IS_ROOT_TRANSFORM, previewIsRootTransform, TYPE_BOOL, true) \ + X(PREVIEW_IS_TRIGGERS, previewIsTriggers, TYPE_BOOL, true) \ + X(PREVIEW_IS_PIVOTS, previewIsPivots, TYPE_BOOL, false) \ + X(PREVIEW_IS_ICONS, previewIsIcons, TYPE_BOOL, true) \ + X(PREVIEW_IS_BORDER, previewIsBorder, TYPE_BOOL, false) \ + X(PREVIEW_IS_ALT_ICONS, previewIsAltIcons, TYPE_BOOL, false) \ + X(PREVIEW_OVERLAY_TRANSPARENCY, previewOverlayTransparency, TYPE_FLOAT, 255.0f) \ + X(PREVIEW_ZOOM, previewZoom, TYPE_FLOAT, 200.0f) \ + X(PREVIEW_PAN, previewPan, TYPE_VEC2, {}) \ + X(PREVIEW_GRID_SIZE, previewGridSize, TYPE_IVEC2, {32, 32}) \ + X(PREVIEW_GRID_OFFSET, previewGridOffset, TYPE_IVEC2, {}) \ + X(PREVIEW_GRID_COLOR, previewGridColor, TYPE_VEC4, {1.0, 1.0, 1.0, 0.125}) \ + X(PREVIEW_AXES_COLOR, previewAxesColor, TYPE_VEC4, {1.0, 1.0, 1.0, 0.125}) \ + X(PREVIEW_BACKGROUND_COLOR, previewBackgroundColor, TYPE_VEC4, {0.113, 0.184, 0.286, 1.0}) \ + \ + X(PROPERTIES_IS_ROUND, propertiesIsRound, TYPE_BOOL, false) \ + \ + X(GENERATE_START_POSITION, generateStartPosition, TYPE_IVEC2, {}) \ + X(GENERATE_SIZE, generateSize, TYPE_IVEC2, {64, 64}) \ + X(GENERATE_PIVOT, generatePivot, TYPE_IVEC2, {32, 32}) \ + X(GENERATE_ROWS, generateRows, TYPE_INT, 4) \ + X(GENERATE_COLUMNS, generateColumns, TYPE_INT, 4) \ + X(GENERATE_COUNT, generateCount, TYPE_INT, 16) \ + X(GENERATE_DELAY, generateDelay, TYPE_INT, 1) \ + \ + X(EDITOR_IS_GRID, editorIsGrid, TYPE_BOOL, true) \ + X(EDITOR_IS_GRID_SNAP, editorIsGridSnap, TYPE_BOOL, true) \ + X(EDITOR_IS_BORDER, editorIsBorder, TYPE_BOOL, true) \ + X(EDITOR_ZOOM, editorZoom, TYPE_FLOAT, 200.0f) \ + X(EDITOR_PAN, editorPan, TYPE_VEC2, {0.0, 0.0}) \ + X(EDITOR_GRID_SIZE, editorGridSize, TYPE_IVEC2, {32, 32}) \ + X(EDITOR_GRID_OFFSET, editorGridOffset, TYPE_IVEC2, {32, 32}) \ + X(EDITOR_GRID_COLOR, editorGridColor, TYPE_VEC4, {1.0, 1.0, 1.0, 0.125}) \ + X(EDITOR_BACKGROUND_COLOR, editorBackgroundColor, TYPE_VEC4, {0.113, 0.184, 0.286, 1.0}) \ + \ + X(MERGE_TYPE, mergeType, TYPE_INT, ANM2_MERGE_APPEND) \ + X(MERGE_IS_DELETE_ANIMATIONS_AFTER, mergeIsDeleteAnimationsAfter, TYPE_BOOL, false) \ + \ + X(BAKE_INTERVAL, bakeInterval, TYPE_INT, 1) \ + X(BAKE_IS_ROUND_SCALE, bakeIsRoundScale, TYPE_BOOL, true) \ + X(BAKE_IS_ROUND_ROTATION, bakeIsRoundRotation, TYPE_BOOL, true) \ + \ + X(TIMELINE_ADD_ITEM_TYPE, timelineAddItemType, TYPE_INT, ANM2_LAYER) \ + X(TIMELINE_IS_SHOW_UNUSED, timelineIsShowUnused, TYPE_BOOL, true) \ + \ + X(ONIONSKIN_IS_ENABLED, onionskinIsEnabled, TYPE_BOOL, false) \ + X(ONIONSKIN_DRAW_ORDER, onionskinDrawOrder, TYPE_INT, ONIONSKIN_BELOW) \ + X(ONIONSKIN_BEFORE_COUNT, onionskinBeforeCount, TYPE_INT, 0) \ + X(ONIONSKIN_AFTER_COUNT, onionskinAfterCount, TYPE_INT, 0) \ + X(ONIONSKIN_BEFORE_COLOR_OFFSET, onionskinBeforeColorOffset, TYPE_VEC3, COLOR_RED) \ + X(ONIONSKIN_AFTER_COLOR_OFFSET, onionskinAfterColorOffset, TYPE_VEC3, COLOR_BLUE) \ + \ + X(TOOL, tool, TYPE_INT, TOOL_PAN) \ + X(TOOL_COLOR, toolColor, TYPE_VEC4, {1.0, 1.0, 1.0, 1.0}) \ + \ + X(RENDER_TYPE, renderType, TYPE_INT, RENDER_PNG) \ + X(RENDER_PATH, renderPath, TYPE_STRING, ".") \ + X(RENDER_FORMAT, renderFormat, TYPE_STRING, "{}.png") \ + X(RENDER_IS_USE_ANIMATION_BOUNDS, renderIsUseAnimationBounds, TYPE_BOOL, true) \ + X(RENDER_IS_TRANSPARENT, renderIsTransparent, TYPE_BOOL, true) \ + X(RENDER_SCALE, renderScale, TYPE_FLOAT, 1.0f) \ + X(RENDER_FFMPEG_PATH, renderFFmpegPath, TYPE_STRING, SETTINGS_RENDER_FFMPEG_PATH_VALUE_DEFAULT) -#define X(name, symbol, type, ...) \ -const inline DATATYPE_TO_CTYPE(type) SETTINGS_##symbol##_DEFAULT = __VA_ARGS__; +#define X(symbol, name, type, ...) const inline DATATYPE_TO_CTYPE(type) SETTINGS_##symbol##_DEFAULT = __VA_ARGS__; SETTINGS_LIST #undef X -struct Settings -{ - #define X(name, symbol, type, ...) \ - DATATYPE_TO_CTYPE(type) name = SETTINGS_##symbol##_DEFAULT; +struct Settings { +#define X(symbol, name, type, ...) DATATYPE_TO_CTYPE(type) name = SETTINGS_##symbol##_DEFAULT; + SETTINGS_LIST +#undef X +}; + +struct SettingsEntry { + std::string key; + DataType type; + int offset; +}; + +const inline SettingsEntry SETTINGS_ENTRIES[] = { +#define X(symbol, name, type, ...) {#name, type, offsetof(Settings, name)}, SETTINGS_LIST - #undef X -}; - -struct SettingsEntry -{ - std::string key; - DataType type; - s32 offset; +#undef X }; -const inline SettingsEntry SETTINGS_ENTRIES[] = -{ - #define X(name, symbol, type, ...) \ - { #name, type, offsetof(Settings, name) }, - SETTINGS_LIST - #undef X -}; +constexpr int SETTINGS_COUNT = (int)std::size(SETTINGS_ENTRIES); -constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); +#define HOTKEY_LIST \ + X(NONE, "None") \ + X(CENTER_VIEW, "Center View") \ + X(FIT, "Fit") \ + X(ZOOM_IN, "Zoom In") \ + X(ZOOM_OUT, "Zoom Out") \ + X(PLAY_PAUSE, "Play/Pause") \ + X(ONIONSKIN, "Onionskin") \ + X(NEW, "New") \ + X(OPEN, "Open") \ + X(SAVE, "Save") \ + X(SAVE_AS, "Save As") \ + X(EXIT, "Exit") \ + X(SHORTEN_FRAME, "Shorten Frame") \ + X(EXTEND_FRAME, "Extend Frame") \ + X(INSERT_FRAME, "Insert Frame") \ + X(PREVIOUS_FRAME, "Previous Frame") \ + X(NEXT_FRAME, "Next Frame") \ + X(PAN, "Pan") \ + X(MOVE, "Move") \ + X(ROTATE, "Rotate") \ + X(SCALE, "Scale") \ + X(CROP, "Crop") \ + X(DRAW, "Draw") \ + X(ERASE, "Erase") \ + X(COLOR_PICKER, "Color Picker") \ + X(UNDO, "Undo") \ + X(REDO, "Redo") \ + X(COPY, "Copy") \ + X(CUT, "Cut") \ + X(PASTE, "Paste") -#define HOTKEY_LIST \ - X(NONE, "None") \ - X(CENTER_VIEW, "Center View") \ - X(FIT, "Fit") \ - X(ZOOM_IN, "Zoom In") \ - X(ZOOM_OUT, "Zoom Out") \ - X(PLAY_PAUSE, "Play/Pause") \ - X(ONIONSKIN, "Onionskin") \ - X(NEW, "New") \ - X(OPEN, "Open") \ - X(SAVE, "Save") \ - X(SAVE_AS, "Save As") \ - X(EXIT, "Exit") \ - X(PAN, "Pan") \ - X(MOVE, "Move") \ - X(ROTATE, "Rotate") \ - X(SCALE, "Scale") \ - X(CROP, "Crop") \ - X(DRAW, "Draw") \ - X(ERASE, "Erase") \ - X(COLOR_PICKER, "Color Picker") \ - X(UNDO, "Undo") \ - X(REDO, "Redo") \ - X(COPY, "Copy") \ - X(CUT, "Cut") \ - X(PASTE, "Paste") \ - -typedef enum -{ - #define X(name, str) HOTKEY_##name, - HOTKEY_LIST - #undef X - HOTKEY_COUNT +typedef enum { +#define X(name, str) HOTKEY_##name, + HOTKEY_LIST +#undef X + HOTKEY_COUNT } HotkeyType; -const inline char* HOTKEY_STRINGS[] = -{ - #define X(name, str) str, +const inline char* HOTKEY_STRINGS[] = { +#define X(name, str) str, HOTKEY_LIST - #undef X +#undef X }; using HotkeyMember = std::string Settings::*; -const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] = -{ - nullptr, - &Settings::hotkeyCenterView, - &Settings::hotkeyFit, - &Settings::hotkeyZoomIn, - &Settings::hotkeyZoomOut, - &Settings::hotkeyPlayPause, - &Settings::hotkeyOnionskin, - &Settings::hotkeyNew, - &Settings::hotkeyOpen, - &Settings::hotkeySave, - &Settings::hotkeySaveAs, - &Settings::hotkeyExit, - &Settings::hotkeyPan, - &Settings::hotkeyMove, - &Settings::hotkeyRotate, - &Settings::hotkeyScale, - &Settings::hotkeyCrop, - &Settings::hotkeyDraw, - &Settings::hotkeyErase, - &Settings::hotkeyColorPicker, - &Settings::hotkeyUndo, - &Settings::hotkeyRedo, - &Settings::hotkeyCopy, - &Settings::hotkeyCut, - &Settings::hotkeyPaste -}; +const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] = {nullptr, + &Settings::hotkeyCenterView, + &Settings::hotkeyFit, + &Settings::hotkeyZoomIn, + &Settings::hotkeyZoomOut, + &Settings::hotkeyPlayPause, + &Settings::hotkeyOnionskin, + &Settings::hotkeyNew, + &Settings::hotkeyOpen, + &Settings::hotkeySave, + &Settings::hotkeySaveAs, + &Settings::hotkeyExit, + &Settings::hotkeyShortenFrame, + &Settings::hotkeyExtendFrame, + &Settings::hotkeyInsertFrame, + &Settings::hotkeyPreviousFrame, + &Settings::hotkeyNextFrame, + &Settings::hotkeyPan, + &Settings::hotkeyMove, + &Settings::hotkeyRotate, + &Settings::hotkeyScale, + &Settings::hotkeyCrop, + &Settings::hotkeyDraw, + &Settings::hotkeyErase, + &Settings::hotkeyColorPicker, + &Settings::hotkeyUndo, + &Settings::hotkeyRedo, + &Settings::hotkeyCopy, + &Settings::hotkeyCut, + &Settings::hotkeyPaste}; -const std::string SETTINGS_IMGUI_DEFAULT = R"( +const inline std::string SETTINGS_IMGUI_DEFAULT = R"( # Dear ImGui [Window][## Window] Pos=0,32 diff --git a/src/shader.cpp b/src/shader.cpp index 514265b..2eac5ec 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -1,53 +1,49 @@ #include "shader.h" -static bool _shader_compile(GLuint* self, const std::string& text) -{ - s32 isCompile; - const GLchar* source = text.c_str(); +static bool _shader_compile(GLuint* self, const std::string& text) { + int isCompile; + const GLchar* source = text.c_str(); - glShaderSource(*self, 1, &source, nullptr); - glCompileShader(*self); - glGetShaderiv(*self, GL_COMPILE_STATUS, &isCompile); + glShaderSource(*self, 1, &source, nullptr); + glCompileShader(*self); + glGetShaderiv(*self, GL_COMPILE_STATUS, &isCompile); - if (!isCompile) - { - std::string compileLog(SHADER_INFO_LOG_MAX, '\0'); - glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, nullptr, compileLog.data()); - log_error(std::format(SHADER_INIT_ERROR, *self, compileLog.c_str())); - return false; - } + if (!isCompile) { + std::string compileLog(SHADER_INFO_LOG_MAX, '\0'); + glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, nullptr, compileLog.data()); + log_error(std::format(SHADER_INIT_ERROR, *self, compileLog.c_str())); + return false; + } - return true; + return true; } -// Initializes a given shader with vertex/fragment -bool shader_init(GLuint* self, const std::string& vertex, const std::string& fragment) -{ - GLuint vertexHandle; - GLuint fragmentHandle; +bool shader_init(GLuint* self, const std::string& vertex, const std::string& fragment) { + GLuint vertexHandle; + GLuint fragmentHandle; - vertexHandle = glCreateShader(GL_VERTEX_SHADER); - fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER); + vertexHandle = glCreateShader(GL_VERTEX_SHADER); + fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER); - if (!_shader_compile(&vertexHandle, vertex) || !_shader_compile(&fragmentHandle, fragment)) - return false; + if (!_shader_compile(&vertexHandle, vertex) || !_shader_compile(&fragmentHandle, fragment)) + return false; - *self = glCreateProgram(); + *self = glCreateProgram(); - glAttachShader(*self, vertexHandle); - glAttachShader(*self, fragmentHandle); + glAttachShader(*self, vertexHandle); + glAttachShader(*self, fragmentHandle); - glLinkProgram(*self); + glLinkProgram(*self); - glDeleteShader(vertexHandle); - glDeleteShader(fragmentHandle); + glDeleteShader(vertexHandle); + glDeleteShader(fragmentHandle); - return true; + return true; } -void shader_free(GLuint* self) -{ - if (*self == GL_ID_NONE) return; - - glDeleteProgram(*self); +void shader_free(GLuint* self) { + if (*self == GL_ID_NONE) + return; + + glDeleteProgram(*self); } \ No newline at end of file diff --git a/src/snapshots.cpp b/src/snapshots.cpp index 2c8cf46..a928af6 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -1,77 +1,67 @@ #include "snapshots.h" -static void _snapshot_stack_push(SnapshotStack* stack, Snapshot* snapshot) -{ - if (stack->top >= SNAPSHOT_STACK_MAX) - { - for (s32 i = 0; i < SNAPSHOT_STACK_MAX - 1; i++) - stack->snapshots[i] = stack->snapshots[i + 1]; - stack->top = SNAPSHOT_STACK_MAX - 1; - } - stack->snapshots[stack->top++] = *snapshot; +static void _snapshot_stack_push(SnapshotStack* stack, Snapshot* snapshot) { + if (stack->top >= SNAPSHOT_STACK_MAX) { + for (int i = 0; i < SNAPSHOT_STACK_MAX - 1; i++) + stack->snapshots[i] = stack->snapshots[i + 1]; + stack->top = SNAPSHOT_STACK_MAX - 1; + } + stack->snapshots[stack->top++] = *snapshot; } -static Snapshot* _snapshot_stack_pop(SnapshotStack* stack) -{ - if (stack->top == 0) return nullptr; - return &stack->snapshots[--stack->top]; +static Snapshot* _snapshot_stack_pop(SnapshotStack* stack) { + if (stack->top == 0) + return nullptr; + return &stack->snapshots[--stack->top]; } -static void _snapshot_set(Snapshots* self, Snapshot* snapshot) -{ - if (!snapshot) return; +static void _snapshot_set(Snapshots* self, Snapshot* snapshot) { + if (!snapshot) + return; - *self->anm2 = snapshot->anm2; - *self->reference = snapshot->reference; - self->preview->time = snapshot->time; - self->action = snapshot->action; + *self->anm2 = snapshot->anm2; + *self->reference = snapshot->reference; + self->preview->time = snapshot->time; + self->action = snapshot->action; - anm2_spritesheet_texture_pixels_upload(self->anm2); + anm2_spritesheet_texture_pixels_upload(self->anm2); } -Snapshot snapshot_get(Snapshots* self) -{ - Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, self->action}; - anm2_spritesheet_texture_pixels_download(&snapshot.anm2); - return snapshot; +Snapshot snapshot_get(Snapshots* self) { + Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, self->action}; + anm2_spritesheet_texture_pixels_download(&snapshot.anm2); + return snapshot; } -void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview) -{ - self->anm2 = anm2; - self->reference = reference; - self->preview = preview; +void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview) { + self->anm2 = anm2; + self->reference = reference; + self->preview = preview; } -void snapshots_reset(Snapshots* self) -{ - self->undoStack = SnapshotStack{}; - self->redoStack = SnapshotStack{}; - self->action.clear(); +void snapshots_reset(Snapshots* self) { + self->undoStack = SnapshotStack{}; + self->redoStack = SnapshotStack{}; + self->action.clear(); } -void snapshots_undo_push(Snapshots* self, Snapshot* snapshot) -{ - self->redoStack.top = 0; - _snapshot_stack_push(&self->undoStack, snapshot); +void snapshots_undo_push(Snapshots* self, Snapshot* snapshot) { + self->redoStack.top = 0; + _snapshot_stack_push(&self->undoStack, snapshot); } -void snapshots_undo(Snapshots* self) -{ - if (Snapshot* snapshot = _snapshot_stack_pop(&self->undoStack)) - { - Snapshot current = snapshot_get(self); - _snapshot_stack_push(&self->redoStack, ¤t); - _snapshot_set(self, snapshot); - } +void snapshots_undo(Snapshots* self) { + if (Snapshot* snapshot = _snapshot_stack_pop(&self->undoStack)) { + Snapshot current = snapshot_get(self); + _snapshot_stack_push(&self->redoStack, ¤t); + _snapshot_set(self, snapshot); + } } -void snapshots_redo(Snapshots* self) -{ - if (Snapshot* snapshot = _snapshot_stack_pop(&self->redoStack)) - { - Snapshot current = snapshot_get(self); - _snapshot_stack_push(&self->undoStack, ¤t); - _snapshot_set(self, snapshot); - } +void snapshots_redo(Snapshots* self) { + if (Snapshot* snapshot = _snapshot_stack_pop(&self->redoStack)) { + Snapshot current = snapshot_get(self); + _snapshot_stack_push(&self->undoStack, ¤t); + _snapshot_set(self, snapshot); + } } \ No newline at end of file diff --git a/src/snapshots.h b/src/snapshots.h index 3399a9d..b7d2cf9 100644 --- a/src/snapshots.h +++ b/src/snapshots.h @@ -2,35 +2,31 @@ #include "anm2.h" #include "preview.h" -#include "texture.h" #define SNAPSHOT_STACK_MAX 100 #define SNAPSHOT_ACTION "Action" -struct Snapshot -{ - Anm2 anm2; - Anm2Reference reference; - f32 time = 0.0f; - std::string action = SNAPSHOT_ACTION; +struct Snapshot { + Anm2 anm2; + Anm2Reference reference; + float time = 0.0f; + std::string action = SNAPSHOT_ACTION; }; -struct SnapshotStack -{ - Snapshot snapshots[SNAPSHOT_STACK_MAX]; - s32 top = 0; +struct SnapshotStack { + Snapshot snapshots[SNAPSHOT_STACK_MAX]; + int top = 0; - bool is_empty() const { return top == 0; } + bool is_empty() const { return top == 0; } }; -struct Snapshots -{ - Anm2* anm2 = nullptr; - Preview* preview = nullptr; - Anm2Reference* reference = nullptr; - std::string action = SNAPSHOT_ACTION; - SnapshotStack undoStack; - SnapshotStack redoStack; +struct Snapshots { + Anm2* anm2 = nullptr; + Preview* preview = nullptr; + Anm2Reference* reference = nullptr; + std::string action = SNAPSHOT_ACTION; + SnapshotStack undoStack; + SnapshotStack redoStack; }; void snapshots_undo_push(Snapshots* self, Snapshot* snapshot); diff --git a/src/state.h b/src/state.h index 79fbe9c..8a084ad 100644 --- a/src/state.h +++ b/src/state.h @@ -27,29 +27,28 @@ #define STATE_GL_VERSION_MAJOR 3 #define STATE_GL_VERSION_MINOR 3 -struct State -{ - SDL_Window* window; - SDL_GLContext glContext; - Imgui imgui; - Dialog dialog; - Editor editor; - Preview preview; - GeneratePreview generatePreview; - Anm2 anm2; - Anm2Reference reference; - Resources resources; - Settings settings; - Snapshots snapshots; - Clipboard clipboard; - std::string argument{}; - std::string lastAction{}; - u64 lastTick{}; - u64 tick{}; - u64 update{}; - u64 lastUpdate{}; - bool isRunning = true; -}; +struct State { + SDL_Window* window; + SDL_GLContext glContext; + Imgui imgui; + Dialog dialog; + Editor editor; + Preview preview; + GeneratePreview generatePreview; + Anm2 anm2; + Anm2Reference reference; + Resources resources; + Settings settings; + Snapshots snapshots; + Clipboard clipboard; + std::string argument{}; + std::string lastAction{}; + uint64_t lastTick{}; + uint64_t tick{}; + uint64_t update{}; + uint64_t lastUpdate{}; + bool isRunning = true; +}; bool sdl_init(State* self, bool isTestMode); void init(State* state); diff --git a/src/texture.cpp b/src/texture.cpp index 8b80bb2..1f2d52e 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -1,129 +1,100 @@ #if defined(__clang__) || defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - #pragma GCC diagnostic ignored "-Wunused-function" - #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif #include "texture.h" -#define STBI_ONLY_PNG -#define STBI_NO_FAILURE_STRINGS +#define STBI_ONLY_PNG +#define STBI_NO_FAILURE_STRINGS #define STBI_NO_HDR #define STB_IMAGE_IMPLEMENTATION #include #define STB_IMAGE_WRITE_IMPLEMENTATION #include -static void _texture_gl_set(Texture* self, const u8* data) -{ - if (self->id == GL_ID_NONE) glGenTextures(1, &self->id); - - glBindTexture(GL_TEXTURE_2D, self->id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); +static void _texture_gl_set(Texture* self, const uint8_t* data) { + if (self->id == GL_ID_NONE) + glGenTextures(1, &self->id); + + glBindTexture(GL_TEXTURE_2D, self->id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); } -std::vector texture_download(const Texture* self) -{ - std::vector pixels(self->size.x * self->size.y * TEXTURE_CHANNELS); - - glBindTexture(GL_TEXTURE_2D, self->id); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - - return pixels; +std::vector texture_download(const Texture* self) { + std::vector pixels(self->size.x * self->size.y * TEXTURE_CHANNELS); + + glBindTexture(GL_TEXTURE_2D, self->id); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + return pixels; } -bool texture_from_path_init(Texture* self, const std::string& path) -{ - u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS); - - if (!data) - { - log_error(std::format(TEXTURE_INIT_ERROR, path)); - return false; - } +bool texture_from_path_init(Texture* self, const std::string& path) { + uint8_t* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS); - self->isInvalid = false; + if (!data) { + log_error(std::format(TEXTURE_INIT_ERROR, path)); + return false; + } - log_info(std::format(TEXTURE_INIT_INFO, path)); + self->isInvalid = false; - _texture_gl_set(self, data); + log_info(std::format(TEXTURE_INIT_INFO, path)); - return true; + _texture_gl_set(self, data); + + return true; } -bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length) -{ - *self = Texture{}; - self->size = size; +bool texture_from_rgba_init(Texture* self, ivec2 size, const uint8_t* data) { + *self = Texture{}; + self->size = size; + self->isInvalid = false; - u8* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS); + _texture_gl_set(self, data); - if (!textureData) - { - self->isInvalid = true; - return false; - } - - _texture_gl_set(self, textureData); - - return true; + return true; } -bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data) -{ - *self = Texture{}; - self->size = size; - self->isInvalid = false; +bool texture_from_rgba_write(const std::string& path, const uint8_t* data, ivec2 size) { + bool isSuccess = stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS); + if (!isSuccess) + log_error(std::format(TEXTURE_SAVE_ERROR, path)); + else + log_info(std::format(TEXTURE_SAVE_INFO, path)); - _texture_gl_set(self, data); - - return true; + return isSuccess; } -bool texture_from_rgba_write(const std::string& path, const u8* data, ivec2 size) -{ - bool isSuccess = stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS); - if (!isSuccess) log_error(std::format(TEXTURE_SAVE_ERROR, path)); - else log_info(std::format(TEXTURE_SAVE_INFO, path)); - - return isSuccess; +bool texture_from_gl_write(Texture* self, const std::string& path) { return texture_from_rgba_write(path, texture_download(self).data(), self->size); } + +void texture_free(Texture* self) { + if (self->isInvalid) + return; + + glDeleteTextures(1, &self->id); + *self = Texture{}; } -bool texture_from_gl_write(Texture* self, const std::string& path) -{ - return texture_from_rgba_write(path, texture_download(self).data(), self->size); -} - -void texture_free(Texture* self) -{ - if (self->isInvalid) return; - - glDeleteTextures(1, &self->id); - *self = Texture{}; -} - -bool texture_pixel_set(Texture* self, ivec2 position, vec4 color) -{ - if - ( - position.x < 0 || position.y < 0 || - position.x >= self->size.x || position.y >= self->size.y - ) - return false; - - uint8_t rgba8[4] = {FLOAT_TO_U8(color.r), FLOAT_TO_U8(color.g), FLOAT_TO_U8(color.b), FLOAT_TO_U8(color.a)}; - - glBindTexture(GL_TEXTURE_2D, self->id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage2D(GL_TEXTURE_2D, 0,position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8); - - return true; +bool texture_pixel_set(Texture* self, ivec2 position, vec4 color) { + if (position.x < 0 || position.y < 0 || position.x >= self->size.x || position.y >= self->size.y) + return false; + + uint8_t rgba8[4] = {FLOAT_TO_UINT8(color.r), FLOAT_TO_UINT8(color.g), FLOAT_TO_UINT8(color.b), FLOAT_TO_UINT8(color.a)}; + + glBindTexture(GL_TEXTURE_2D, self->id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8); + + return true; } diff --git a/src/texture.h b/src/texture.h index 3c55c1a..8c2e3a2 100644 --- a/src/texture.h +++ b/src/texture.h @@ -8,16 +8,14 @@ #define TEXTURE_SAVE_INFO "Saved texture to: {}" #define TEXTURE_SAVE_ERROR "Failed to save texture to: {}" -struct Texture -{ - GLuint id = GL_ID_NONE; - ivec2 size{}; - bool isInvalid = true; +struct Texture { + GLuint id = GL_ID_NONE; + ivec2 size{}; + bool isInvalid = true; - auto operator<=>(const Texture&) const = default; + auto operator<=>(const Texture&) const = default; }; -bool texture_from_encoded_data_init(Texture* self, ivec2 size, const u8* data, u32 length); bool texture_from_gl_write(Texture* self, const std::string& path); bool texture_from_path_init(Texture* self, const std::string& path); bool texture_from_rgba_init(Texture* self, ivec2 size, const u8* data); diff --git a/src/window.cpp b/src/window.cpp index 553d0f4..6654c77 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,17 +1,11 @@ #include "window.h" +#include -void window_title_from_path_set(SDL_Window* self, const std::string& path) -{ - if (!path.empty()) - SDL_SetWindowTitle(self, std::format(WINDOW_TITLE_FORMAT, path).c_str()); - else - SDL_SetWindowTitle(self, WINDOW_TITLE); +void window_title_from_path_set(SDL_Window* self, const std::string& path) { + if (!path.empty()) + SDL_SetWindowTitle(self, std::format(WINDOW_TITLE_FORMAT, path).c_str()); + else + SDL_SetWindowTitle(self, WINDOW_TITLE); } -void window_vsync_set(bool isVsync) -{ - if (isVsync) - SDL_GL_SetSwapInterval(1); - else - SDL_GL_SetSwapInterval(0); -} \ No newline at end of file +void window_vsync_set(bool isVsync) { SDL_GL_SetSwapInterval(isVsync); } \ No newline at end of file