From fe8bdae9a8d4a2cd6521d884cd702336a3d2cc76 Mon Sep 17 00:00:00 2001 From: shweet Date: Sun, 27 Jul 2025 22:08:57 -0400 Subject: [PATCH] The Update(TM), Part 1 --- CMakeLists.txt | 4 + assets/Icon.ico | Bin 0 -> 16958 bytes assets/Icon.rc | 1 + assets/atlas.kra | Bin 0 -> 375239 bytes assets/atlas.png | Bin 0 -> 1696 bytes assets/bleh.png | Bin 0 -> 88 bytes assets/bleh.txt | 256 ++++ assets/win_icon.ico | Bin 2183 -> 0 bytes assets/win_icon.rc | 1 - src/COMMON.h | 184 ++- src/PACKED.h | 253 ++-- src/STRINGS.h | 297 ---- src/anm2.cpp | 336 ++--- src/anm2.h | 60 +- src/canvas.cpp | 50 + src/canvas.h | 33 + src/clipboard.cpp | 140 ++ src/clipboard.h | 44 + src/dialog.cpp | 38 +- src/dialog.h | 12 +- src/editor.cpp | 35 +- src/editor.h | 11 +- src/imgui.cpp | 3456 ++++++++++++++++++++----------------------- src/imgui.h | 1625 ++++++++++++++++++-- src/input.cpp | 132 -- src/input.h | 310 ---- src/main.cpp | 6 +- src/preview.cpp | 73 +- src/preview.h | 58 +- src/resources.cpp | 39 +- src/resources.h | 2 + src/settings.cpp | 46 +- src/settings.h | 18 +- src/shader.cpp | 25 +- src/shader.h | 1 + src/snapshots.cpp | 118 +- src/snapshots.h | 19 +- src/state.cpp | 191 +-- src/state.h | 20 +- src/texture.cpp | 42 +- src/texture.h | 4 + src/tool.cpp | 31 - src/tool.h | 23 - src/window.cpp | 14 +- src/window.h | 6 + 45 files changed, 4450 insertions(+), 3564 deletions(-) create mode 100644 assets/Icon.ico create mode 100644 assets/Icon.rc create mode 100644 assets/atlas.kra create mode 100644 assets/atlas.png create mode 100644 assets/bleh.png create mode 100644 assets/bleh.txt delete mode 100644 assets/win_icon.ico delete mode 100644 assets/win_icon.rc delete mode 100644 src/STRINGS.h create mode 100644 src/canvas.cpp create mode 100644 src/canvas.h create mode 100644 src/clipboard.cpp create mode 100644 src/clipboard.h delete mode 100644 src/input.cpp delete mode 100644 src/input.h delete mode 100644 src/tool.cpp delete mode 100644 src/tool.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 04b7540..2d61608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ endif() project(anm2ed CXX) + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) find_package(GLEW REQUIRED) find_package(OpenGL REQUIRED) @@ -29,6 +30,8 @@ if (WIN32) endif() add_executable(${PROJECT_NAME} ${SOURCES} ${WIN32_RESOURCES}) +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) + target_include_directories(${PROJECT_NAME} PRIVATE include include/imgui include/tinyxml2 src) if (NOT MSVC) set(CMAKE_CXX_FLAGS "-g -O2 -std=c++23 -Wall -Wextra -pedantic -fmax-errors=1") @@ -41,6 +44,7 @@ if(NOT MSVC) endif() target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL GLEW::GLEW SDL3::SDL3) + message("System: ${CMAKE_SYSTEM_NAME}") message("Project: ${PROJECT_NAME}") message("Build: ${CMAKE_BUILD_TYPE}") diff --git a/assets/Icon.ico b/assets/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c9105c049fbb234969c0180bdaec80b6f1a4ef0b GIT binary patch literal 16958 zcmeI2QEtLO3`Dml=wHA4$uUq*!cltU-XPsTE5YFS#mcT$E1W7sW^B)l*Fqq*l(+hK zK9~CXSx#T2e3Vj7^{#J~?|QHAU$2Vgl@rJbK`j_#XL zPb==?M!#;}tB|MfTI+j}+tBO!Bhcs<(i73X<{srU>dB&u8~xIMa3>=>aCF{Ay?ed{ z8vXM8^ZdK*z|nm(>S@JYT>H%yyXJO)v-wrMJ1n{>+HbblHMawt&9Ca+VbM)7`gQy4 zt3nRxCVuiqxbkUC{F<+E?|Sk_xa!row|?c*nE1&b;mW5m@oT=uz3a&z z;i^~T-ujhKW8x=&ge#xM#IN}p_pT>@gsWbSd+S#|jftQ95l;P85A~Z3PyhY|^86qF zKVY`~CcXaYFHhgkqxr-?8jt#k=8yJA{82yAeBvLCNBu_g=zo?&R zervt0`$hdk^IPj}-7o4Vn%`RQ=zdZE`hAn?NBbjw(yx9srh4KZjgx-$t1;D&_DB4r zU;S!K^~66Lp8g+yZ|3>?6PRtkN$;!gKg}ocoIp-s{{;L`&mHKEYFYm&q+T!he7_B; hY(3YZ)ayDNa8eBMy=^%t8cqn-c& literal 0 HcmV?d00001 diff --git a/assets/Icon.rc b/assets/Icon.rc new file mode 100644 index 0000000..0599d31 --- /dev/null +++ b/assets/Icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "Icon.ico" diff --git a/assets/atlas.kra b/assets/atlas.kra new file mode 100644 index 0000000000000000000000000000000000000000..76039e467f588623b0222d30e612171abe5e1e18 GIT binary patch literal 375239 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 q5Q_QpNUR0@F#OSderZ~@arz(3(`n2=pL$> literal 0 HcmV?d00001 diff --git a/assets/bleh.png b/assets/bleh.png new file mode 100644 index 0000000000000000000000000000000000000000..c656ed55c985fab3ddf4edf2d796eb964baece3a GIT binary patch literal 88 zcmeAS@N?(olHy`uVBq!ia0vp^j3CSbB77@X9|qE#1s;*b3=A=DAj~K-#n}-k$mQwc f7{Vc&{DYr?k&%(XEIz#fD9YgJ>gTe~DWM4feyRt9B{b@9vA1oG-Q6iXT3M^((iqjg(bFJi@Ge`AgMP%(FSv_?PcbS=+9_*aGG6|6ue|E;lJ~!tsrOXfFRwbJasU2z{pjqa|CwV4AZ^0}iH6ySBrBpW zGcS9)_x+RK&*z+c&Uoz2UibU<&zB5**i5f_vKRuxU`|9DyD+o59jL`9hyuo-77c_th5;zJYD@< J);T3K0RZUmF;M^j diff --git a/assets/win_icon.rc b/assets/win_icon.rc deleted file mode 100644 index dacd6e9..0000000 --- a/assets/win_icon.rc +++ /dev/null @@ -1 +0,0 @@ -IDI_ICON1 ICON DISCARDABLE "win_icon.ico" \ No newline at end of file diff --git a/src/COMMON.h b/src/COMMON.h index e48d386..5a4c0dd 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -13,17 +13,19 @@ #include #include #include +#include #include -#include +#include +#include #include #include -#include #include +#include +#include #include #include #include - -#include "STRINGS.h" +#include typedef uint8_t u8; typedef uint16_t u16; @@ -46,57 +48,17 @@ using namespace glm; #define MIN(x, min) (x < min ? min : x) #define MAX(x, max) (x > max ? max : x) #define CLAMP(x, min, max) (MIN(MAX(x, max), min)) - -// Converts a string to a bool -static inline bool string_to_bool(const std::string& string) -{ - return string == "true" || string == "1"; -}; - -// Given a file path, sets the working directory to it -static inline void 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); -}; - -// Converts an enum to a string value -static inline const char* enum_to_string(const char* array[], s32 count, s32 index) -{ - return (index >= 0 && index < count) ? array[index] : ""; -}; - -// Converts a string to an enum value -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; -}; - -// Macro to define enum to string functions -#define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \ - static inline std::string function(s32 index) \ - { \ - return enum_to_string(array, count, index); \ - }; - -// Macro to define string to enum functions -#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 ROUND_NEAREST_FLOAT(value, multiple) (roundf((value) / (multiple)) * (multiple)) #define COLOR_FLOAT_TO_INT(x) (static_cast((x) * 255.0f)) #define COLOR_INT_TO_FLOAT(x) ((x) / 255.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f) - #define TICK_DELAY 33.3f +#define TICK_CATCH_UP_MAX (33.3f * 5) #define SECOND 1000.0f #define TICK_RATE (SECOND / TICK_DELAY) +#define ID_NONE -1 +#define INDEX_NONE -1 +#define LENGTH_NONE -1 #define UV_VERTICES(uvMin, uvMax) \ { \ @@ -122,11 +84,7 @@ static const f32 GL_UV_VERTICES[] = 0, 1, 0.0f, 1.0f }; - -#define IMVEC4_VEC4(value) ImVec4(value.r, value.g, value.b, value.a) - static const GLuint GL_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0}; - static const vec4 COLOR_RED = {1.0f, 0.0f, 0.0f, 1.0f}; static const vec4 COLOR_GREEN = {0.0f, 1.0f, 0.0f, 1.0f}; static const vec4 COLOR_BLUE = {0.0f, 0.0f, 1.0f, 1.0f}; @@ -134,15 +92,85 @@ static const vec4 COLOR_OPAQUE = {1.0f, 1.0f, 1.0f, 1.0f}; static const vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 1.0f}; static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f}; -template -static inline s32 map_next_id_get(const std::map& map) { - s32 id = 0; for (const auto& [key, _] : map) if (key != id) break; else ++id; return id; +static inline void log_error(const std::string& string) +{ + std::println("[ERROR] {}", string); +} + +static inline void log_info(const std::string& string) +{ + std::println("[INFO] {}", string); +} + +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"; +} + +static inline void 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); +}; + +static inline const char* enum_to_string(const char* array[], s32 count, s32 index) +{ + return (index >= 0 && index < count) ? array[index] : ""; +}; + +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; +}; + +template +static inline s32 map_next_id_get(const std::map& map) +{ + s32 id = 0; + + for (const auto& [key, _] : map) + if (key != id) + break; + else + ++id; + + return id; +} + +template +static inline s32 vector_next_id_get(const std::vector& vec) +{ + std::unordered_set usedIDs; + for (const auto& item : vec) + usedIDs.insert(item.id); + + for (s32 i = 0; ; ++i) + if (!usedIDs.contains(i)) + return i; +} + +template +void vector_swap_by_id(std::vector& vec, s32 idA, s32 idB) +{ + if (idA == idB) + return; + + auto itA = std::find_if(vec.begin(), vec.end(), [=](const T& item) { return item.id == idA; }); + auto itB = std::find_if(vec.begin(), vec.end(), [=](const T& item) { return item.id == idB; }); + + if (itA != vec.end() && itB != vec.end()) + std::swap(*itA, *itB); } -// Swaps elements in a map -// If neither key exists, do nothing -// If one key exists, change its ID -// If both keys exist, swap template static inline void map_swap(Map& map, const Key& key1, const Key& key2) { @@ -167,4 +195,38 @@ static inline void map_swap(Map& map, const Key& key1, const Key& key2) map[key1] = std::move(it2->second); map.erase(it2); } -}; \ No newline at end of file +}; + +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; +} + +#define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \ + static inline std::string function(s32 index) \ + { \ + return enum_to_string(array, count, index); \ + }; + +#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)); \ + }; diff --git a/src/PACKED.h b/src/PACKED.h index 8d2698e..3092378 100644 --- a/src/PACKED.h +++ b/src/PACKED.h @@ -1,130 +1,197 @@ +// Includes packed resources (i.e., textures, shaders) + #pragma once #include "COMMON.h" -const u32 TEXTURE_ATLAS_LENGTH = 916; +const u32 TEXTURE_ATLAS_LENGTH = 1696; 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, 0x68, - 0x02, 0x03, 0x00, 0x00, 0x00, 0x73, 0xa5, 0x1d, 0xc6, 0x00, 0x00, 0x00, - 0x09, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x60, - 0x60, 0x60, 0x13, 0x5e, 0x42, 0xae, 0x00, 0x00, 0x00, 0x01, 0x74, 0x52, - 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 0x00, 0x00, 0x03, 0x39, 0x49, - 0x44, 0x41, 0x54, 0x48, 0xc7, 0xcd, 0x95, 0xcf, 0x6b, 0x14, 0x31, 0x14, - 0xc7, 0xbf, 0x19, 0x26, 0xb2, 0xb3, 0xa7, 0x11, 0x26, 0xcb, 0xea, 0x69, - 0x90, 0x46, 0xb4, 0x7f, 0x45, 0x0a, 0xf5, 0xe0, 0x2d, 0x42, 0xde, 0x30, - 0x9d, 0xd3, 0x28, 0x56, 0xa4, 0x7f, 0x45, 0x0e, 0x0a, 0xf5, 0xe6, 0x61, - 0x47, 0xaa, 0xa7, 0x22, 0xac, 0xb4, 0xf3, 0x57, 0x9a, 0x97, 0x34, 0x16, - 0x71, 0x57, 0x29, 0x82, 0xf8, 0x20, 0xbb, 0x2f, 0xf3, 0x79, 0xf9, 0xbe, - 0x1f, 0x19, 0x76, 0xf1, 0x7b, 0x13, 0x06, 0xa8, 0x46, 0xc8, 0xbc, 0xd7, - 0x3d, 0x92, 0x29, 0x07, 0x48, 0x0b, 0xf9, 0x0d, 0x2d, 0xd8, 0x88, 0xf8, - 0x73, 0x22, 0x41, 0x64, 0x18, 0x2c, 0x15, 0xea, 0x28, 0x60, 0xa5, 0x89, - 0x58, 0x11, 0xb9, 0x28, 0xa5, 0x5e, 0x59, 0x96, 0x91, 0xf3, 0xd6, 0x02, - 0x85, 0x41, 0x04, 0xc2, 0x04, 0x40, 0xb6, 0x20, 0xf2, 0x7a, 0x9e, 0xfa, - 0x98, 0x98, 0xa5, 0x32, 0x10, 0x67, 0xd7, 0x66, 0x35, 0x77, 0x1b, 0x34, - 0xc2, 0xe0, 0x70, 0xea, 0xae, 0x6e, 0xc0, 0xf8, 0xf8, 0xf3, 0x9b, 0x8b, - 0xd5, 0xf5, 0xb0, 0xc1, 0xaa, 0xf2, 0x38, 0xbc, 0x3e, 0xbb, 0x8a, 0xda, - 0x17, 0x64, 0xfa, 0xfb, 0xc7, 0x7a, 0xb3, 0x3d, 0x5b, 0x41, 0x4b, 0xa0, - 0x98, 0x67, 0x8f, 0xcd, 0x0a, 0x12, 0x9f, 0x70, 0xb0, 0x3e, 0x58, 0xaf, - 0x14, 0x36, 0x90, 0x01, 0x2c, 0xe6, 0xab, 0x73, 0x6d, 0x65, 0x2f, 0xb9, - 0x54, 0x69, 0x8d, 0xee, 0xe6, 0x1e, 0x55, 0x07, 0x3c, 0x7a, 0xf4, 0xb5, - 0x55, 0x46, 0x38, 0x06, 0xc5, 0x58, 0x79, 0x39, 0xcc, 0x16, 0xc2, 0x01, - 0x38, 0x3f, 0xf7, 0x19, 0x60, 0xea, 0x20, 0x86, 0xad, 0x01, 0x2c, 0xc2, - 0x91, 0x16, 0xda, 0x17, 0x2c, 0x95, 0x8c, 0x28, 0x7b, 0x3e, 0x3b, 0xb7, - 0x43, 0xdc, 0x6f, 0xd2, 0x72, 0xf7, 0x3b, 0x00, 0x51, 0x86, 0xfc, 0x9d, - 0xa5, 0xd2, 0x88, 0xf3, 0x7d, 0xf0, 0xaa, 0x88, 0xc6, 0x25, 0xd1, 0x25, - 0x64, 0xd8, 0x71, 0x74, 0x86, 0xf7, 0x80, 0x8f, 0x95, 0x95, 0x7c, 0x9d, - 0x2a, 0x82, 0x2c, 0x59, 0x02, 0xb5, 0xf4, 0xc1, 0x69, 0xe0, 0x9a, 0x52, - 0x98, 0x9a, 0x47, 0x9f, 0x81, 0x86, 0xea, 0xd1, 0x08, 0xdb, 0x34, 0xd5, - 0xd8, 0x48, 0x22, 0x9b, 0x01, 0x07, 0xa3, 0x91, 0xa6, 0x21, 0x4a, 0xd7, - 0x9b, 0x01, 0x07, 0xa3, 0x51, 0x88, 0x80, 0x1f, 0x32, 0x0c, 0xa0, 0xe5, - 0x60, 0x48, 0x07, 0xcb, 0x52, 0x0c, 0x58, 0x2e, 0x00, 0x0e, 0x8e, 0x7b, - 0xcb, 0xc9, 0xd9, 0xe1, 0x02, 0xca, 0xe0, 0x68, 0xb7, 0xec, 0x53, 0x83, - 0xb9, 0x6b, 0x22, 0x94, 0x78, 0xf0, 0x5a, 0xda, 0xc3, 0x3e, 0x6e, 0x32, - 0xe0, 0x26, 0xef, 0xe1, 0xe5, 0xf3, 0xca, 0x6e, 0xcf, 0xf2, 0x83, 0xdb, - 0x91, 0xf4, 0xeb, 0x83, 0x25, 0xd1, 0x22, 0x4f, 0x38, 0x0f, 0x32, 0x6d, - 0xf4, 0x70, 0x79, 0xfb, 0xb6, 0xff, 0xc6, 0x7e, 0x09, 0xba, 0x8a, 0xfb, - 0x67, 0x8d, 0xbd, 0x91, 0xcd, 0x7e, 0x11, 0xc0, 0x13, 0xd4, 0x91, 0x7e, - 0x18, 0xa1, 0x5c, 0xf2, 0x81, 0x75, 0x00, 0x5b, 0xd4, 0x82, 0x8e, 0x51, - 0x9c, 0x72, 0x24, 0x03, 0x1e, 0xc0, 0x69, 0x8b, 0x62, 0x32, 0x40, 0x00, - 0x98, 0x6c, 0xbc, 0xd6, 0x08, 0xc4, 0xe4, 0xb1, 0xa4, 0xb1, 0x46, 0x80, - 0x42, 0x8e, 0xe5, 0xc5, 0xe3, 0x3a, 0x49, 0xdd, 0xeb, 0x0c, 0x9e, 0xd2, - 0x65, 0xcd, 0x27, 0x4a, 0xe1, 0x9b, 0xb1, 0x6a, 0x92, 0xd4, 0x83, 0x13, - 0xe0, 0x7d, 0xe7, 0x21, 0x4e, 0x8e, 0x51, 0x17, 0xa3, 0x3b, 0x3a, 0x52, - 0x49, 0xea, 0xf4, 0x35, 0x8a, 0x17, 0x03, 0x6a, 0x69, 0x0c, 0xcc, 0xbb, - 0x8f, 0x0c, 0x92, 0xd4, 0xf4, 0x11, 0x95, 0x7b, 0x0b, 0x45, 0x41, 0x08, - 0x35, 0x92, 0x94, 0x38, 0x86, 0x58, 0x19, 0x68, 0x6a, 0xb9, 0xfc, 0xb2, - 0x5c, 0x18, 0x70, 0xf2, 0xd8, 0xbe, 0x7c, 0x0e, 0x48, 0xf2, 0x0c, 0xf8, - 0x08, 0xb8, 0xdc, 0x9a, 0x77, 0x71, 0xa8, 0x84, 0x74, 0xa2, 0x04, 0x5b, - 0x02, 0x4f, 0x0d, 0x80, 0xee, 0xeb, 0x82, 0x41, 0x65, 0x11, 0x23, 0xa2, - 0xd4, 0x13, 0x0e, 0x39, 0x3c, 0xf7, 0xca, 0xbd, 0x30, 0x79, 0x88, 0xb2, - 0x61, 0x7f, 0xff, 0xd8, 0xf7, 0xb2, 0x82, 0x95, 0x17, 0xc0, 0x01, 0x57, - 0xc4, 0x2b, 0x80, 0x88, 0x06, 0x06, 0xef, 0x80, 0xf4, 0xc6, 0xf0, 0x4a, - 0x60, 0xc5, 0x51, 0xa3, 0x83, 0x48, 0x2f, 0x5c, 0x5c, 0x86, 0xfb, 0xf8, - 0x04, 0x14, 0xdd, 0x45, 0x70, 0x0c, 0x64, 0xaf, 0x2d, 0xaf, 0x04, 0x3c, - 0x50, 0x7d, 0xfb, 0xd2, 0x43, 0x01, 0xfc, 0x23, 0xc0, 0x2b, 0xa6, 0x5f, - 0x0e, 0x83, 0xd5, 0xdb, 0xc1, 0xa3, 0xf3, 0x50, 0x1e, 0x4e, 0xa1, 0x70, - 0xc2, 0xdc, 0x00, 0x45, 0x27, 0xa6, 0x20, 0x82, 0x02, 0x03, 0x04, 0x90, - 0xea, 0x6c, 0x6b, 0x3a, 0x81, 0xa0, 0x2e, 0x00, 0xe3, 0x94, 0x80, 0x03, - 0xdb, 0x72, 0x9e, 0x2f, 0x6b, 0x3a, 0x0d, 0x8e, 0x0b, 0x52, 0x2f, 0x9c, - 0x7a, 0x56, 0x38, 0xe5, 0xa4, 0xc5, 0x02, 0x6d, 0xab, 0x86, 0x00, 0x84, - 0x85, 0x36, 0xd4, 0x6b, 0x12, 0xbd, 0x0a, 0x28, 0x82, 0xe6, 0xe1, 0x6b, - 0xc4, 0x72, 0x1d, 0x59, 0x49, 0xca, 0xfe, 0x00, 0x50, 0x1b, 0x40, 0x03, - 0xc2, 0x71, 0x83, 0xca, 0x30, 0x88, 0x39, 0xa0, 0x03, 0x58, 0x03, 0x48, - 0x27, 0x10, 0xc1, 0xcf, 0x46, 0x4e, 0x11, 0x10, 0x93, 0xef, 0xb5, 0x39, - 0x98, 0x07, 0xe0, 0xff, 0x00, 0x6e, 0xef, 0x6f, 0x22, 0xea, 0xee, 0x0e, - 0xf0, 0xbf, 0x03, 0xe5, 0xf6, 0x00, 0xa2, 0xdd, 0x40, 0x1c, 0x1d, 0x99, - 0x9d, 0x40, 0x0a, 0x63, 0xef, 0x02, 0x14, 0x45, 0x73, 0x7b, 0xc1, 0x5f, - 0xe7, 0xc8, 0xe5, 0xde, 0xad, 0x41, 0x28, 0xf7, 0x3f, 0x5e, 0xd4, 0xbf, - 0x00, 0xc0, 0x1f, 0x81, 0x26, 0xea, 0x77, 0x02, 0xfe, 0xff, 0xde, 0x09, - 0x0a, 0x22, 0xef, 0x77, 0x01, 0x4c, 0x1d, 0x76, 0x03, 0xdd, 0xef, 0x01, - 0xd5, 0xb8, 0x07, 0x14, 0x7e, 0x27, 0xf8, 0x0e, 0xc6, 0xe6, 0x29, 0x49, - 0x20, 0x43, 0x4e, 0xe3, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x0e, 0xcb, 0xf5, 0x55, 0x00, 0x00, 0x00, + 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, + 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x06, 0x52, 0x49, 0x44, + 0x41, 0x54, 0x78, 0xda, 0xed, 0x5d, 0x8b, 0x4e, 0xeb, 0x30, 0x0c, 0x65, + 0xd5, 0x3e, 0x14, 0xbe, 0x0c, 0xfe, 0x74, 0x97, 0x4a, 0x04, 0x05, 0x5f, + 0x3f, 0x8e, 0x1d, 0xa7, 0xe9, 0x56, 0x5b, 0x42, 0x6c, 0xb4, 0x69, 0x52, + 0x1f, 0xbf, 0xf3, 0xe0, 0xed, 0xad, 0xe8, 0x9c, 0xf4, 0x20, 0xf4, 0xcc, + 0xef, 0x10, 0x69, 0xfb, 0xfe, 0xfe, 0xfe, 0xfb, 0xee, 0xfb, 0xe7, 0x99, + 0x7d, 0xa9, 0x8c, 0x7f, 0x46, 0x20, 0x32, 0xc6, 0xee, 0x6d, 0xdb, 0x83, + 0xd5, 0xc0, 0x93, 0x80, 0x13, 0x01, 0xd5, 0x3a, 0xd4, 0xae, 0xf5, 0xd2, + 0x22, 0xd1, 0xcf, 0xc0, 0x86, 0x24, 0xd1, 0x62, 0xea, 0x03, 0xa4, 0x19, + 0x00, 0x34, 0xc6, 0x5b, 0xbc, 0x6a, 0xd7, 0xdd, 0x2a, 0x94, 0xf1, 0xe2, + 0x3b, 0x00, 0xfb, 0xcf, 0x08, 0x23, 0xb8, 0x71, 0x3c, 0x82, 0x34, 0xcb, + 0x04, 0x71, 0xc2, 0x47, 0x9f, 0xe9, 0x02, 0x20, 0x4b, 0xf2, 0xa8, 0x34, + 0xa3, 0xd2, 0x3f, 0x0a, 0x52, 0x54, 0x13, 0x22, 0x82, 0x82, 0x8e, 0xa7, + 0xb5, 0xbb, 0xf5, 0x2a, 0x71, 0xfb, 0x26, 0x4d, 0x5d, 0xda, 0x75, 0x7a, + 0x7f, 0xd4, 0x37, 0x70, 0xfd, 0x71, 0x7d, 0x5b, 0xf7, 0xa9, 0xaa, 0x1d, + 0xe8, 0x37, 0xd2, 0x7f, 0x7f, 0x2f, 0x32, 0x8e, 0xf6, 0x4c, 0x15, 0x00, + 0x8e, 0xf9, 0x33, 0x00, 0xb0, 0xfa, 0xb7, 0x18, 0x40, 0x4d, 0xc4, 0xe7, + 0xe7, 0x27, 0x7b, 0xdf, 0xc7, 0xc7, 0xc7, 0x9f, 0xef, 0x5f, 0x5f, 0x5f, + 0xb7, 0x15, 0x00, 0xec, 0xe3, 0x68, 0x7d, 0x6f, 0x88, 0x34, 0xa1, 0x12, + 0x33, 0x4a, 0x3d, 0x23, 0xdb, 0x67, 0xca, 0x34, 0x84, 0xf6, 0x36, 0x7d, + 0x3b, 0xfa, 0xdd, 0x6b, 0xfa, 0x50, 0x3f, 0x40, 0xc7, 0x2d, 0xf1, 0xad, + 0x07, 0xde, 0x34, 0x29, 0x9a, 0x59, 0xca, 0xd2, 0x80, 0x7d, 0xd0, 0x4d, + 0x6a, 0x2d, 0xad, 0xf0, 0x68, 0x82, 0x44, 0x9a, 0xe4, 0x6b, 0x63, 0xe9, + 0x25, 0x17, 0xd5, 0x96, 0xfe, 0x79, 0x5c, 0xfb, 0x8d, 0x76, 0xe4, 0x65, + 0xbe, 0x57, 0x32, 0xbd, 0x0c, 0xf1, 0x30, 0xdf, 0x7a, 0x4e, 0x7f, 0x9d, + 0x6a, 0x5b, 0x23, 0xca, 0xfc, 0xfe, 0xf3, 0x7e, 0xcd, 0x13, 0x19, 0xb5, + 0x3e, 0x1b, 0xa9, 0xe3, 0x43, 0x23, 0x84, 0xd1, 0x30, 0x54, 0x7a, 0x06, + 0x17, 0xef, 0x7b, 0xd5, 0x9e, 0xfb, 0xdd, 0xc7, 0xe7, 0xf4, 0x77, 0xf4, + 0xdd, 0x35, 0x1e, 0xa4, 0x65, 0x90, 0x2b, 0x12, 0xb1, 0x8c, 0xac, 0x5b, + 0x03, 0x43, 0x32, 0x53, 0xa7, 0x01, 0xe0, 0x0c, 0xa5, 0x88, 0x8c, 0x7e, + 0x10, 0xa6, 0x47, 0x92, 0x2e, 0xed, 0xbe, 0x74, 0xfe, 0xbc, 0x42, 0x31, + 0xce, 0xe3, 0x98, 0x8b, 0x8a, 0x8a, 0xb2, 0xcd, 0x21, 0xd2, 0x7e, 0x9b, + 0xa5, 0xf6, 0xd1, 0x88, 0xe6, 0x2c, 0xfd, 0xd3, 0x44, 0x2c, 0xc2, 0xfc, + 0x91, 0xf6, 0x87, 0x3b, 0x71, 0x24, 0x92, 0xd2, 0x98, 0xd9, 0xd7, 0xe1, + 0x81, 0x68, 0xec, 0x11, 0x79, 0x87, 0x51, 0x1e, 0x1c, 0xca, 0x7c, 0xcf, + 0x35, 0x4f, 0xba, 0x2f, 0x55, 0x48, 0xad, 0x4a, 0xeb, 0x0a, 0x6d, 0x0c, + 0x33, 0x3e, 0xa2, 0xc2, 0x54, 0xc2, 0xe8, 0x8f, 0x25, 0x0d, 0x5e, 0x09, + 0xd3, 0xfa, 0x1f, 0x8d, 0xea, 0xb2, 0x4c, 0xa8, 0x1b, 0x00, 0x54, 0x85, + 0xad, 0xf8, 0xd8, 0x62, 0xbe, 0x14, 0x47, 0x47, 0x01, 0x50, 0x27, 0x3a, + 0x04, 0x93, 0x20, 0xf5, 0xcf, 0x4d, 0x27, 0xd2, 0xbf, 0xa1, 0x02, 0x84, + 0x08, 0x1b, 0x5b, 0xfe, 0x95, 0x6a, 0x36, 0x5c, 0x9d, 0xc4, 0x6a, 0xd7, + 0x97, 0x86, 0xdb, 0x75, 0xee, 0x39, 0xfb, 0x33, 0x3c, 0x75, 0x7a, 0xad, + 0x3c, 0x6e, 0x55, 0x75, 0xa5, 0x62, 0x62, 0x2b, 0x25, 0x73, 0x45, 0xb3, + 0x56, 0x54, 0xeb, 0xcb, 0xcd, 0x5a, 0xe1, 0x50, 0x2b, 0xc2, 0xfd, 0x77, + 0x7f, 0x8f, 0x70, 0xff, 0x99, 0xce, 0x64, 0x49, 0x13, 0xce, 0x92, 0xf4, + 0x73, 0xce, 0x4f, 0x92, 0xd6, 0x11, 0x0d, 0xf0, 0xfa, 0x1d, 0x69, 0xf6, + 0x8a, 0x8b, 0x5e, 0xb8, 0x68, 0xe6, 0x91, 0x48, 0xac, 0xed, 0xa4, 0x1d, + 0xd1, 0x81, 0x71, 0xe0, 0xf4, 0x2a, 0xac, 0x31, 0x5f, 0xb2, 0xd7, 0x1e, + 0x00, 0x90, 0x32, 0x00, 0xe2, 0x90, 0x4f, 0x01, 0x00, 0x32, 0x01, 0x61, + 0x5d, 0xa7, 0x0c, 0x45, 0x0b, 0x77, 0x51, 0x0d, 0xb0, 0x6c, 0x2d, 0x12, + 0x71, 0x49, 0x8c, 0xb6, 0xfc, 0x9c, 0xd5, 0x7e, 0xa8, 0x6e, 0x24, 0xbd, + 0x84, 0x95, 0x54, 0x68, 0x8e, 0x56, 0x32, 0x4b, 0x5e, 0x27, 0xac, 0x45, + 0x31, 0x52, 0xff, 0x5a, 0x9e, 0x70, 0x16, 0x27, 0xbc, 0xd1, 0x09, 0x93, + 0xd6, 0xd1, 0x8d, 0x50, 0x1b, 0x04, 0xe7, 0x68, 0xb9, 0x89, 0x06, 0x6b, + 0x0a, 0xd0, 0x33, 0x09, 0x43, 0xfb, 0x6d, 0xe3, 0xd4, 0x9e, 0x45, 0x1d, + 0xa2, 0xd5, 0x47, 0xbb, 0x7f, 0x7f, 0xd6, 0xfe, 0xfc, 0x7e, 0xf2, 0x65, + 0xff, 0xdc, 0x3b, 0x52, 0xce, 0xd9, 0x7b, 0x26, 0xa8, 0xd4, 0x7b, 0xb5, + 0x50, 0xd4, 0x93, 0x41, 0x6a, 0x0e, 0xd9, 0x93, 0x88, 0x71, 0xa6, 0x8d, + 0x33, 0x8b, 0x52, 0x1f, 0x88, 0x4f, 0x3a, 0x73, 0x69, 0x7d, 0x59, 0x29, + 0x82, 0xfa, 0x18, 0xcd, 0x27, 0x79, 0x4a, 0x11, 0x88, 0x56, 0x3c, 0x6d, + 0x29, 0x62, 0x66, 0x31, 0x2e, 0x23, 0x93, 0xf5, 0x48, 0xfe, 0x28, 0xf3, + 0x5e, 0x65, 0x3e, 0xe5, 0xe5, 0xcb, 0xd1, 0x45, 0x67, 0xa4, 0xa8, 0x09, + 0xe1, 0x12, 0x1c, 0xed, 0x73, 0x76, 0xfb, 0xd9, 0xb5, 0xf7, 0x8c, 0x67, + 0x53, 0x33, 0x78, 0xf3, 0x76, 0x22, 0xd5, 0x5b, 0x46, 0x56, 0xcf, 0x8d, + 0xb6, 0x47, 0x6a, 0x42, 0x99, 0x00, 0x44, 0x9f, 0x4f, 0x85, 0x78, 0x0f, + 0x6b, 0x37, 0x2f, 0xc2, 0x59, 0xd1, 0xc0, 0xa8, 0x34, 0x65, 0x66, 0xd2, + 0xb3, 0x35, 0xa1, 0x59, 0x10, 0xd7, 0xc2, 0x2c, 0x69, 0xe5, 0x58, 0x46, + 0x48, 0x96, 0x0d, 0x80, 0xa7, 0xf6, 0x82, 0x2e, 0x3b, 0xc9, 0xdc, 0xe8, + 0xc1, 0xd5, 0xcb, 0x42, 0x21, 0x93, 0xa7, 0x3c, 0xe1, 0x01, 0x2f, 0x23, + 0xd2, 0x18, 0xdd, 0x1b, 0x30, 0x63, 0x8f, 0x01, 0xb7, 0x27, 0x82, 0x15, + 0x80, 0x68, 0xb6, 0x8b, 0x30, 0x10, 0xd1, 0x80, 0x8c, 0x49, 0xef, 0x15, + 0x00, 0x44, 0x98, 0xaf, 0xee, 0x8e, 0xf1, 0xee, 0x46, 0xf1, 0xdc, 0x4b, + 0xd5, 0x70, 0x86, 0x0f, 0xf0, 0x26, 0x69, 0x52, 0x35, 0x77, 0xf4, 0xdd, + 0x5d, 0xcc, 0x47, 0x6a, 0xe7, 0x11, 0x0d, 0xd0, 0x26, 0x43, 0x66, 0x85, + 0x8e, 0xd1, 0xad, 0x47, 0x99, 0x5b, 0x96, 0xdc, 0xcc, 0xb7, 0x4c, 0x90, + 0xb6, 0xf7, 0x09, 0x51, 0x5f, 0xd4, 0x84, 0x78, 0x19, 0xec, 0xda, 0x81, + 0x98, 0x08, 0x80, 0x37, 0x87, 0x82, 0xf6, 0xb9, 0x69, 0xd5, 0x4a, 0x24, + 0x9a, 0x88, 0xda, 0xc8, 0x55, 0xa9, 0x7d, 0x14, 0x00, 0x14, 0x20, 0x17, + 0xf3, 0x11, 0x67, 0x19, 0xdd, 0x25, 0x99, 0x15, 0x57, 0x67, 0x56, 0x17, + 0x47, 0x7c, 0x80, 0xc7, 0x3f, 0xa0, 0xcc, 0xbf, 0x69, 0x8c, 0x90, 0x36, + 0x9c, 0x69, 0x9b, 0xf9, 0xd0, 0x89, 0x87, 0x3e, 0xfb, 0xb5, 0x32, 0xe1, + 0xc8, 0xf3, 0x2d, 0x60, 0xd1, 0x77, 0x40, 0x33, 0x5f, 0x8e, 0x77, 0x08, + 0x70, 0x5b, 0xa4, 0x51, 0x76, 0xd6, 0x69, 0xf5, 0xdb, 0x66, 0xe5, 0xa4, + 0xef, 0x67, 0xa8, 0x9d, 0xf5, 0x33, 0x76, 0x9e, 0xb1, 0x6d, 0xd2, 0xcb, + 0x8e, 0x48, 0x17, 0x52, 0x60, 0xe3, 0x24, 0x30, 0x52, 0x7c, 0xf3, 0x92, + 0xb4, 0x7b, 0x71, 0xff, 0x5b, 0x64, 0x47, 0xa6, 0x26, 0x28, 0x45, 0x45, + 0xd7, 0xa2, 0x88, 0xb6, 0x6e, 0xcf, 0xf4, 0x82, 0xd6, 0x1a, 0xd0, 0x91, + 0xd2, 0x30, 0xd2, 0xef, 0x0a, 0x80, 0x7e, 0x07, 0x10, 0x5d, 0x1d, 0xcc, + 0x9d, 0x14, 0xe2, 0x79, 0x16, 0x77, 0x0e, 0xcf, 0x08, 0x33, 0xb4, 0xb5, + 0x3d, 0x56, 0xff, 0xa3, 0x0c, 0x0e, 0x8d, 0x7d, 0xe4, 0xc5, 0xb9, 0x75, + 0xa6, 0x68, 0x92, 0xc6, 0xad, 0x4f, 0x8d, 0x68, 0x8a, 0x96, 0x34, 0x66, + 0xd6, 0xbd, 0x34, 0x06, 0x87, 0xf3, 0x18, 0x74, 0x6d, 0xbf, 0x87, 0x19, + 0xd6, 0x20, 0xa3, 0x4c, 0x97, 0x18, 0xcf, 0xad, 0x6a, 0xf3, 0x08, 0x41, + 0x64, 0xee, 0xc3, 0x03, 0xc0, 0xdd, 0x52, 0x55, 0xee, 0xe4, 0x11, 0xe4, + 0x58, 0x80, 0x3e, 0xc1, 0xfa, 0xbe, 0xf7, 0x8f, 0xca, 0x5b, 0xe1, 0x5e, + 0x24, 0x94, 0x6b, 0xcb, 0xc1, 0xe9, 0x12, 0x78, 0xc9, 0xee, 0x6b, 0x63, + 0x88, 0x1e, 0x54, 0x22, 0xdd, 0xeb, 0x3e, 0xff, 0xc2, 0xda, 0x58, 0x81, + 0xda, 0x43, 0xcd, 0x27, 0x58, 0x7d, 0x5a, 0xdf, 0x25, 0xc9, 0x97, 0xc6, + 0x8e, 0xae, 0x09, 0x8a, 0xbc, 0x6b, 0xaa, 0x0f, 0xe0, 0x54, 0x35, 0x83, + 0xf9, 0xd4, 0x27, 0x64, 0x02, 0xe0, 0x3d, 0xad, 0xcb, 0x6b, 0x2e, 0xbd, + 0x66, 0x2b, 0x2d, 0xcc, 0xcb, 0x60, 0x3e, 0x8d, 0x3a, 0xb4, 0x4d, 0x76, + 0xd1, 0x10, 0x12, 0x29, 0x8d, 0x23, 0xa1, 0xe4, 0xc8, 0x7c, 0x72, 0x38, + 0x7b, 0xa6, 0x36, 0x94, 0x2b, 0xc6, 0x35, 0x7b, 0x89, 0x9c, 0x30, 0x15, + 0x5d, 0xb6, 0xe2, 0x7d, 0x39, 0xba, 0xe5, 0x47, 0x3b, 0xbb, 0xa8, 0xad, + 0x76, 0xf6, 0xd8, 0x74, 0xce, 0x07, 0x4a, 0x6d, 0x91, 0x62, 0xa1, 0x5a, + 0xac, 0xd3, 0x54, 0x0f, 0x65, 0x8e, 0x25, 0xf9, 0x23, 0x89, 0x11, 0xbd, + 0x66, 0x1d, 0x37, 0xa3, 0x6d, 0xb7, 0x1a, 0x29, 0x31, 0x7b, 0x35, 0x07, + 0xb9, 0xbe, 0x59, 0x92, 0x8d, 0x46, 0x3c, 0x9c, 0x84, 0x66, 0x2d, 0xb8, + 0xca, 0x2a, 0x86, 0x79, 0xc6, 0x72, 0xd4, 0x91, 0x6d, 0x77, 0x6a, 0x6e, + 0x34, 0x55, 0xf7, 0x4a, 0xcf, 0x51, 0xcc, 0xe7, 0xfa, 0xe9, 0x77, 0x28, + 0x4a, 0x87, 0xf8, 0x21, 0x1a, 0x37, 0xfb, 0x1d, 0xee, 0x9c, 0xcd, 0xef, + 0x6d, 0x67, 0x1b, 0xcc, 0x0e, 0x44, 0xaf, 0x0d, 0x54, 0x33, 0xe8, 0xae, + 0x95, 0x68, 0x2c, 0x9f, 0xe1, 0x23, 0x22, 0x4c, 0xa7, 0xe0, 0x69, 0xe7, + 0xc3, 0x4d, 0x71, 0xc2, 0xda, 0xcb, 0xf6, 0xcc, 0x8f, 0x9a, 0xa4, 0x57, + 0xaf, 0x82, 0x0e, 0x39, 0xe1, 0xa2, 0x75, 0x74, 0xcb, 0x88, 0x6d, 0x47, + 0x34, 0xe2, 0x2c, 0x1a, 0xb5, 0xe2, 0x9c, 0xd4, 0xdf, 0x28, 0xa8, 0x68, + 0x1d, 0xdd, 0xa5, 0xc2, 0x15, 0xea, 0xec, 0x8a, 0x4a, 0x03, 0x0a, 0x80, + 0x55, 0xfe, 0xa3, 0x00, 0x28, 0x2a, 0x00, 0x0a, 0x80, 0xa2, 0x02, 0xa0, + 0x00, 0x28, 0x2a, 0x00, 0x0a, 0x80, 0xa2, 0x78, 0x26, 0x7c, 0x65, 0xd2, + 0xf6, 0xac, 0x1d, 0x51, 0x13, 0xba, 0x17, 0xe3, 0xed, 0x7b, 0x66, 0x02, + 0xb1, 0x5d, 0x9d, 0xf9, 0xdc, 0x34, 0x26, 0xfd, 0xdb, 0xcc, 0x63, 0x10, + 0x2e, 0xed, 0x03, 0x00, 0xc9, 0x9e, 0x6e, 0x82, 0xb6, 0xab, 0x4a, 0x3f, + 0x62, 0x56, 0xf6, 0x5b, 0x46, 0xff, 0x55, 0x57, 0x01, 0x50, 0x51, 0xd0, + 0x7a, 0x67, 0xea, 0x6d, 0x37, 0xb2, 0x09, 0xdc, 0x69, 0xe2, 0x4a, 0x03, + 0x2a, 0x11, 0x2b, 0x13, 0xf4, 0xf2, 0x91, 0x0c, 0xe4, 0x84, 0xb5, 0x49, + 0xf9, 0x99, 0xf9, 0x40, 0x69, 0x40, 0x99, 0xa0, 0x35, 0x1a, 0x03, 0x66, + 0xc2, 0xd3, 0xb3, 0xe1, 0x4b, 0x6b, 0x00, 0x00, 0xc2, 0xf4, 0x83, 0x00, + 0x2f, 0x59, 0x0b, 0xea, 0xd7, 0xc0, 0x22, 0x07, 0x48, 0x1d, 0xb2, 0x3a, + 0xba, 0x4c, 0xd1, 0xb8, 0x83, 0x2f, 0x00, 0x92, 0x80, 0xa8, 0xa5, 0x89, + 0x15, 0x05, 0x15, 0x15, 0x00, 0x05, 0x40, 0x51, 0x01, 0x70, 0x15, 0xf2, + 0x1e, 0x5a, 0x4d, 0x8f, 0xa1, 0x79, 0x95, 0xa4, 0x6c, 0xe9, 0x7f, 0xbc, + 0x88, 0xfe, 0x3f, 0xdf, 0x02, 0xe0, 0x60, 0x10, 0xac, 0x4d, 0xd3, 0x91, + 0x3e, 0xaf, 0x0c, 0xc0, 0x1f, 0x1f, 0xd0, 0xfe, 0x89, 0x99, 0x44, 0x47, + 0x6d, 0xdd, 0xbc, 0xb4, 0x13, 0x96, 0x40, 0x28, 0xe6, 0x1f, 0x18, 0x05, + 0x51, 0x10, 0x8a, 0xf9, 0xf3, 0x48, 0xac, 0x05, 0xfd, 0x30, 0xfc, 0xd1, + 0x7d, 0x7e, 0x69, 0x5a, 0xb5, 0x71, 0xfa, 0x1f, 0xcc, 0xd3, 0x69, 0xcc, + 0x40, 0x9f, 0x7b, 0x52, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; enum TextureType { + TEXTURE_NONE, + TEXTURE_RECORD, TEXTURE_ROOT, TEXTURE_LAYER, TEXTURE_NULL, TEXTURE_TRIGGERS, TEXTURE_VISIBLE, TEXTURE_INVISIBLE, - TEXTURE_ERROR, + TEXTURE_SHOW_RECT, + TEXTURE_HIDE_RECT, + TEXTURE_PAN, TEXTURE_MOVE, - TEXTURE_UP, - TEXTURE_DOWN, - TEXTURE_RECT, - TEXTURE_RECT_HIDE, + TEXTURE_ROTATE, + TEXTURE_SCALE, + TEXTURE_CROP, + TEXTURE_UNDO, + TEXTURE_REDO, TEXTURE_DRAW, TEXTURE_ERASE, - TEXTURE_SCALE, - TEXTURE_COLOR, - TEXTURE_PAN, - TEXTURE_CROP, + TEXTURE_COLOR_PICKER, TEXTURE_ANIMATION, TEXTURE_SPRITESHEET, TEXTURE_EVENT, - TEXTURE_ROTATE, - TEXTURE_REDO, TEXTURE_TRIGGER, TEXTURE_PIVOT, - TEXTURE_SQUARE, - TEXTURE_CIRCLE, + TEXTURE_UNINTERPOLATED, + TEXTURE_INTERPOLATED, TEXTURE_PICKER, - TEXTURE_FRAME, TEXTURE_FRAME_ALT, + TEXTURE_FRAME, TEXTURE_TARGET, TEXTURE_COUNT }; const vec2 ATLAS_SIZE = {96, 104}; -const vec2 TEXTURE_SIZE = {16, 16}; const vec2 TEXTURE_SIZE_SMALL = {8, 8}; -const vec2 TEXTURE_SIZE_BIG = {32, 32}; +const vec2 TEXTURE_SIZE = {16, 16}; const vec2 TEXTURE_SIZE_OBLONG = {16, 40}; +const vec2 TEXTURE_SIZE_BIG = {40, 40}; #define ATLAS_UV(x,y){(f32)x / ATLAS_SIZE[0], (f32) y / ATLAS_SIZE[1]} const vec2 ATLAS_UVS[TEXTURE_COUNT][2] = @@ -159,7 +226,7 @@ const vec2 ATLAS_UVS[TEXTURE_COUNT][2] = { ATLAS_UV( 0, 64), ATLAS_UV( 16,104) }, /* 16 x 40 */ { ATLAS_UV( 16, 64), ATLAS_UV( 32,104) }, { ATLAS_UV( 32, 64), ATLAS_UV( 48,104) }, - { ATLAS_UV( 48, 64), ATLAS_UV( 80, 96) } /* 32 x 32 */ + { ATLAS_UV( 48, 64), ATLAS_UV( 88,104) } /* 40 x 40 */ }; const vec2 ATLAS_SIZES[TEXTURE_COUNT] = @@ -287,4 +354,4 @@ const ShaderData SHADER_DATA[SHADER_COUNT] = {SHADER_VERTEX, SHADER_FRAGMENT}, {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT}, {SHADER_VERTEX, SHADER_LINE_DOTTED_FRAGMENT} -}; \ No newline at end of file +}; diff --git a/src/STRINGS.h b/src/STRINGS.h deleted file mode 100644 index d6c4f25..0000000 --- a/src/STRINGS.h +++ /dev/null @@ -1,297 +0,0 @@ -#pragma once - -#define STRING_WINDOW_TITLE "Anm2Ed" - -#define STRING_ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p" - -#define STRING_ERROR_SDL_INIT "[ERROR] Could not initialize SDL: " -#define STRING_ERROR_GL_CONTEXT_INIT "[ERROR] Could not initialize OpenGL context: " -#define STRING_ERROR_FILE_READ "[ERROR] Could not read from file: " -#define STRING_ERROR_SHADER_INIT "[ERROR] Could not initialize shader: " -#define STRING_ERROR_TEXTURE_INIT "[ERROR] Could not initialize texture: " -#define STRING_ERROR_ANM2_READ "[ERROR] Could not read anm2 from file: " -#define STRING_ERROR_ANM2_WRITE "[ERROR] Could not write anm2 to file: " -#define STRING_ERROR_SETTINGS_INIT "[ERROR] Could not load settings file: " - -#define STRING_INFO_INIT "[INFO] Initializing" -#define STRING_INFO_QUIT "[INFO] Exited" -#define STRING_INFO_SDL_INIT "[INFO] Initialized SDL" -#define STRING_INFO_SDL_QUIT "[INFO] Quit SDL" -#define STRING_INFO_GLEW_INIT "[INFO] Initialized GLEW" -#define STRING_INFO_GL_CONTEXT_INIT "[INFO] Initialized OpenGL context" -#define STRING_INFO_FILE_READ "[INFO] Read from file: " -#define STRING_INFO_TEXTURE_INIT "[INFO] Initialized texture: " -#define STRING_INFO_ANM2_WRITE "[INFO] Wrote anm2 to file: " -#define STRING_INFO_ANM2_READ "[INFO] Read anm2 from file: " -#define STRING_INFO_OPENGL "OpenGL" - -#define STRING_INFO_IMGUI_INIT "[INFO] Initialized Dear Imgui" -#define STRING_INFO_IMGUI_FREE "[INFO] Freed Dear Imgui" - -#define STRING_DIALOG_ANM2_READ "Select .anm2 file to read..." -#define STRING_DIALOG_ANM2_WRITE "Select .anm2 file to write to..." -#define STRING_DIALOG_ANM2_FILTER "Anm2 Files (*.anm2)" -#define STRING_DIALOG_ANM2_FILTER_WILDCARD "*.anm2" -#define STRING_DIALOG_ALL_FILTER "All Files (*.*)" -#define STRING_DIALOG_ALL_FILTER_WILDCARD "*" - -#define STRING_ANM2_CREATED_BY_DEFAULT "Unknown" -#define STRING_ANM2_NEW_ANIMATION "New Animation" -#define STRING_ANM2_NEW_LAYER "New Layer" -#define STRING_ANM2_NEW_NULL "New Null" -#define STRING_ANM2_NEW_EVENT "New Event" -#define STRING_ANM2_ROOT "Root" - -#define STRING_PREVIEW_FRAMES_DIRECTORY "frames/" -#define STRING_PREVIEW_FRAMES_FORMAT "{}/{:03}.png" - -#define STRING_IMGUI_WINDOW "Window" -#define STRING_IMGUI_DOCKSPACE "Dockspace" - -#define STRING_IMGUI_TASKBAR "Taskbar" -#define STRING_IMGUI_TASKBAR_PLAYBACK "Playback" -#define STRING_IMGUI_TASKBAR_FILE "File" -#define STRING_IMGUI_TASKBAR_WIZARD "Wizard" - -#define STRING_IMGUI_FILE_MENU "File Menu" -#define STRING_IMGUI_FILE_NEW "New" -#define STRING_IMGUI_FILE_OPEN "Open" -#define STRING_IMGUI_FILE_SAVE "Save" -#define STRING_IMGUI_FILE_SAVE_AS "Save As" - -#define STRING_IMGUI_PLAYBACK_MENU "Playback Menu" -#define STRING_IMGUI_PLAYBACK_LOOP "Loop" - -#define STRING_IMGUI_WIZARD_MENU "Wizard Menu" -#define STRING_IMGUI_WIZARD_EXPORT_FRAMES_TO_PNG "Export Frames to PNG" - -#define STRING_IMGUI_RECORDING "Recording..." - -#define STRING_IMGUI_ANIMATIONS "Animations" -#define STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL "##Animation" -#define STRING_IMGUI_ANIMATIONS_ADD "Add" -#define STRING_IMGUI_ANIMATIONS_REMOVE "Remove" -#define STRING_IMGUI_ANIMATIONS_DUPLICATE "Duplicate" -#define STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT "Set as Default" -#define STRING_IMGUI_ANIMATIONS_DEFAULT_ANIMATION_FORMAT "(*) {}" -#define STRING_IMGUI_ANIMATIONS_DRAG_DROP "Animation Drag/Drop" - -#define STRING_IMGUI_EVENTS "Events" -#define STRING_IMGUI_EVENTS_EVENT_LABEL "##Event" -#define STRING_IMGUI_EVENTS_ADD "Add" -#define STRING_IMGUI_EVENTS_REMOVE "Remove" -#define STRING_IMGUI_EVENT_FORMAT "#{} {}" -#define STRING_IMGUI_EVENTS_DRAG_DROP "Event Drag/Drop" - -#define STRING_IMGUI_SPRITESHEETS "Spritesheets" -#define STRING_IMGUI_SPRITESHEETS_ADD "Add" -#define STRING_IMGUI_SPRITESHEETS_REMOVE "Remove" -#define STRING_IMGUI_SPRITESHEETS_RELOAD "Reload" -#define STRING_IMGUI_SPRITESHEETS_REPLACE "Replace" -#define STRING_IMGUI_SPRITESHEET_FORMAT "#{} {}" -#define STRING_IMGUI_SPRITESHEETS_DRAG_DROP "Spritesheet Drag/Drop" - -#define STRING_IMGUI_FRAME_PROPERTIES "Frame Properties" -#define STRING_IMGUI_FRAME_PROPERTIES_CROP_POSITION "Crop Position" -#define STRING_IMGUI_FRAME_PROPERTIES_CROP_SIZE "Crop Size" -#define STRING_IMGUI_FRAME_PROPERTIES_POSITION "Position" -#define STRING_IMGUI_FRAME_PROPERTIES_PIVOT "Pivot" -#define STRING_IMGUI_FRAME_PROPERTIES_SCALE "Scale" -#define STRING_IMGUI_FRAME_PROPERTIES_ROTATION "Rotation" -#define STRING_IMGUI_FRAME_PROPERTIES_VISIBLE "Visible" -#define STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED "Interpolated" -#define STRING_IMGUI_FRAME_PROPERTIES_DURATION "Duration" -#define STRING_IMGUI_FRAME_PROPERTIES_TINT "Tint" -#define STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET "Color Offset" -#define STRING_IMGUI_FRAME_PROPERTIES_EVENT "Event" -#define STRING_IMGUI_FRAME_PROPERTIES_AT_FRAME "At Frame" -#define STRING_IMGUI_FRAME_PROPERTIES_FLIP_X "Flip X" -#define STRING_IMGUI_FRAME_PROPERTIES_FLIP_Y "Flip Y" -#define STRING_IMGUI_FRAME_PROPERTIES_ROOT "-- Root --" -#define STRING_IMGUI_FRAME_PROPERTIES_LAYER "-- Layer -- " -#define STRING_IMGUI_FRAME_PROPERTIES_NULL "-- Null --" -#define STRING_IMGUI_FRAME_PROPERTIES_TRIGGER "-- Trigger --" - -#define STRING_IMGUI_ANIMATION_PREVIEW "Animation Preview" -#define STRING_IMGUI_ANIMATION_PREVIEW_LABEL "##Animation Preview" -#define STRING_IMGUI_ANIMATION_PREVIEW_SETTINGS "##Animation Preview Settings" -#define STRING_IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS "##Grid Settings" -#define STRING_IMGUI_ANIMATION_PREVIEW_GRID "Grid" -#define STRING_IMGUI_ANIMATION_PREVIEW_GRID_SIZE "Size" -#define STRING_IMGUI_ANIMATION_PREVIEW_GRID_OFFSET "Offset" -#define STRING_IMGUI_ANIMATION_PREVIEW_GRID_COLOR "Color" -#define STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS "##View Settings" -#define STRING_IMGUI_ANIMATION_PREVIEW_ZOOM "Zoom" -#define STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS "##Background Settings" -#define STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR "Background Color" -#define STRING_IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS "##Helper Settings" -#define STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW "Center View" -#define STRING_IMGUI_ANIMATION_PREVIEW_AXIS "Axes" -#define STRING_IMGUI_ANIMATION_PREVIEW_AXIS_COLOR "Color" -#define STRING_IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM "Root Transform" -#define STRING_IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT "Show Pivot" -#define STRING_IMGUI_ANIMATION_PREVIEW_POSITION "##Position" -#define STRING_IMGUI_POSITION_FORMAT "Position: {{{:5}, {:5}}}" - -#define STRING_IMGUI_SPRITESHEET_EDITOR "Spritesheet Editor" -#define STRING_IMGUI_SPRITESHEET_EDITOR_LABEL "##Animation Preview" -#define STRING_IMGUI_SPRITESHEET_EDITOR_SETTINGS "##Animation Preview Settings" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS "##Grid Settings" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID "Grid" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SNAP "Snap" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SIZE "Size" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET "Offset" -#define STRING_IMGUI_SPRITESHEET_EDITOR_GRID_COLOR "Color" -#define STRING_IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS "##View Settings" -#define STRING_IMGUI_SPRITESHEET_EDITOR_ZOOM "Zoom" -#define STRING_IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS "##Background Settings" -#define STRING_IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR "Background Color" -#define STRING_IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW "Center View" -#define STRING_IMGUI_SPRITESHEET_EDITOR_BORDER "Border" -#define STRING_IMGUI_SPRITESHEET_EDITOR_POSITION "##Position" - -#define STRING_IMGUI_TIMELINE "Timeline" -#define STRING_IMGUI_TIMELINE_HEADER "##Header" -#define STRING_IMGUI_TIMELINE_ELEMENT_UP "##Up" -#define STRING_IMGUI_TIMELINE_ELEMENT_DOWN "##Down" -#define STRING_IMGUI_TIMELINE_ELEMENT_FRAMES "Element Frames" -#define STRING_IMGUI_TIMELINE_ROOT "Root" -#define STRING_IMGUI_TIMELINE_ITEM_FORMAT "#{} {}" -#define STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL "##Timeline Element Spritesheet ID" -#define STRING_IMGUI_TIMELINE_ELEMENT_NAME_LABEL "##Timeline Element Name" -#define STRING_IMGUI_TIMELINE_TRIGGERS "Triggers" -#define STRING_IMGUI_TIMELINE_ANIMATION_LABEL "##Timeline Animation" -#define STRING_IMGUI_TIMELINE_ELEMENT_SHIFT_ARROWS_LABEL "##Timeline Shift Arrows" -#define STRING_IMGUI_TIMELINE_ANIMATIONS "Animations" -#define STRING_IMGUI_TIMELINE_PLAY "|> Play" -#define STRING_IMGUI_TIMELINE_PAUSE "|| Pause" -#define STRING_IMGUI_TIMELINE_FRAME_ADD "Add Frame" -#define STRING_IMGUI_TIMELINE_FRAMES "Frames" -#define STRING_IMGUI_TIMELINE_FRAME_LABEL "##Frame" -#define STRING_IMGUI_TIMELINE_TRIGGER_LABEL "##Trigger" -#define STRING_IMGUI_TIMELINE_FRAME_REMOVE "Remove Frame" -#define STRING_IMGUI_TIMELINE_FIT_ANIMATION_LENGTH "Fit Animation Length" -#define STRING_IMGUI_TIMELINE_ANIMATION LENGTH "Animation Length:" -#define STRING_IMGUI_TIMELINE_VISIBLE "##Visible" -#define STRING_IMGUI_TIMELINE_LAYER "Layer" -#define STRING_IMGUI_TIMELINE_NULL "Null" -#define STRING_IMGUI_TIMELINE_RECT "Rect" -#define STRING_IMGUI_TIMELINE_FRAME_INDICES "Frame Indices" -#define STRING_IMGUI_TIMELINE_ANIMATION_LENGTH "Animation Length" -#define STRING_IMGUI_TIMELINE_ELEMENT_TIMELINE "Element Timeline" -#define STRING_IMGUI_TIMELINE_ELEMENTS "Elements" -#define STRING_IMGUI_TIMELINE_ELEMENT_LIST "Element List" -#define STRING_IMGUI_TIMELINE_ELEMENT_ADD "Add Element" -#define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU "Element Add Menu" -#define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_LAYER "Add Layer..." -#define STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_NULL "Add Null..." -#define STRING_IMGUI_TIMELINE_ELEMENT_REMOVE "Remove Element" -#define STRING_IMGUI_TIMELINE_LOOP "Loop" -#define STRING_IMGUI_TIMELINE_FPS "FPS" -#define STRING_IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT "#{}" -#define STRING_IMGUI_TIMELINE_CREATED_BY "Author" -#define STRING_IMGUI_TIMELINE_CREATED_ON "Created on: %s" -#define STRING_IMGUI_TIMELINE_VERSION "Version: %i" -#define STRING_IMGUI_TIMELINE_VIEWER "Viewer" -#define STRING_IMGUI_TIMELINE_CHILD "Timeline" -#define STRING_IMGUI_TIMELINE_MAIN "Main" -#define STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP "Frame Drag/Drop" -#define STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP "Item Drag/Drop" - -#define STRING_IMGUI_TOOLS "Tools" -#define STRING_IMGUI_TOOLS_PAN "##Pan" -#define STRING_IMGUI_TOOLS_MOVE "##Move" -#define STRING_IMGUI_TOOLS_ROTATE "##Rotate" -#define STRING_IMGUI_TOOLS_SCALE "##Scale" -#define STRING_IMGUI_TOOLS_CROP "##Crop" - -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_ADD "Add a new animation." -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE "Duplicates the selected animation." -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE "Removes the selected animation." -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_SELECT "Select the animation to edit.\nYou can also click the name to edit it." -#define STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT "Sets the selected animation as the default.\nThe default animation is marked with \"(*)\"." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS "Toggles the display of the X/Y axes on the animation preview." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS_COLOR "Changes the color of the X/Y axes on the animation preview." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR "Changes the background color of the animation preview." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW "Centers the animation preview's pan." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID "Toggles grid visibility on the animation preview." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_COLOR "Changes the animation preview grid color." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_OFFSET "Changes the animation preview grid's offset, in pixels." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE "Changes the animation preview grid's size, in pixels." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM "Changes the animation preview zoom level." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM "Toggle the properties of the Root element of an animation being able to change the properties (such as position or scale) of all of the animation's elements.\nNOTE: Scale/rotation currently not implemented. Matrix math is hard." -#define STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT "Toggle the pivots of layer animation elements being visible on the animation preview." -#define STRING_IMGUI_TOOLTIP_EVENTS_ADD "Add a new event." -#define STRING_IMGUI_TOOLTIP_EVENTS_REMOVE "Removes the selected event." -#define STRING_IMGUI_TOOLTIP_EVENTS_SELECT "Set the event for the trigger, or rename it." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_AT_FRAME "Sets the frame in which the trigger will activate." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET "Change the color offset of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_POSITION "Change the X/Y position of the crop of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_SIZE "Change the crop width/height of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION "Change the duration of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_EVENT "Sets the event the trigger will use." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED "Toggles the interpolation of the frame.\nBetween keyframes, will transform the values in the in-betweens to be smooth." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_PIVOT "Change the X/Y pivot of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION "Change the X/Y position of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION "Change the rotation of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE "Change the X/Y scale of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT "Change the tint of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE "Toggles the visibility of the frame." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_X "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly)." -#define STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_Y "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly)." -#define STRING_IMGUI_TOOLTIP_SPRITESHEETS_ADD "Opens the file dialog to load in a new spritesheet." -#define STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD "Reloads the selected spritesheet." -#define STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE "Removes the selected spritesheet." -#define STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE "Replaces the selected spritesheet; opens up the file dialog." -#define STRING_IMGUI_TOOLTIP_SPRITESHEETS_SELECT "Select the spritesheet element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ANIMATION_LENGTH "Changes the current animation's length." -#define STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_BY "Change the author of the animation." -#define STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_ON_NOW "Set the date of creation to the current system time." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ADD "Add a layer or null timeline element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER "This is a Layer element.\nThese are the main graphical animation elements.\nClick to select or rename." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NAME "Click to rename the element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL "This is a Null element.\nThese are invisible elements where a game engine may have access to them for additional effects.\nClick to select or rename." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_RECT "Toggle visibility for a rectangle around the null." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE "Remove a timeline element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT "This is the Root element.\nClick to select." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_DOWN "Shift this timeline element below.\nElements with lower indices will display further in front." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SHIFT_UP "Shift this timeline element above.\nElements with higher indices will display further behind." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET "Click to change the spritesheet the layer is using." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS "This is the animation's Triggers.\nTriggers are special activations; each is bound to an Event.\nClick to select." -#define STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE "Toggle visibility for this element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_FPS "Change the FPS of the animation." -#define STRING_IMGUI_TOOLTIP_TIMELINE_FIT_ANIMATION_LENGTH "Sets the animation length to the latest frame." -#define STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD "Add a frame to the current selected element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE "Removes the selected frame from the current selected element." -#define STRING_IMGUI_TOOLTIP_TIMELINE_LOOP "Toggles the animation looping." -#define STRING_IMGUI_TOOLTIP_TIMELINE_PAUSE "Pauses the animation." -#define STRING_IMGUI_TOOLTIP_TIMELINE_PLAY "Plays the animation." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_AXIS "Toggles the display of the X/Y axes on the spritesheet editor." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_AXIS_COLOR "Changes the color of the X/Y axes on the spritesheet editor." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BACKGROUND_COLOR "Changes the background color of the spritesheet editor." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_CENTER_VIEW "Centers the spritesheet editor's pan." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID "Toggles grid visibility on the spritesheet editor." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SNAP "When using the crop tool, will snap points to the grid." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_COLOR "Changes the spritesheet editor grid color." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_OFFSET "Changes the spritesheet editor grid's offset, in pixels." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SIZE "Changes the spritesheet editor grid's size, in pixels." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_ZOOM "Changes the spritesheet editor zoom level." -#define STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BORDER "Toggles a border appearing around the confines of the spritesheet." -#define STRING_IMGUI_TOOLTIP_TOOLS_PAN "Use the pan tool.\nWill shift the view as the cursor is dragged." -#define STRING_IMGUI_TOOLTIP_TOOLS_MOVE "Use the move tool.\nWill move selected elements as the cursor is dragged.\n(Animation Preview only.)" -#define STRING_IMGUI_TOOLTIP_TOOLS_ROTATE "Use the rotate tool.\nWill rotate selected elements as the cursor is dragged.\n(Animation Preview only.)" -#define STRING_IMGUI_TOOLTIP_TOOLS_SCALE "Use the scale tool.\nWill scale the selected elements as the cursor is dragged.\n(Animation Preview only.)" -#define STRING_IMGUI_TOOLTIP_TOOLS_CROP "Use the crop tool.\nWill set the crop of the selected elements as the cursor is dragged.\n(Spritesheet Editor only.)" -#define STRING_IMGUI_TOOLTIP_FILE_NEW "Load a blank .anm2 file to edit." -#define STRING_IMGUI_TOOLTIP_FILE_OPEN "Open an existing .anm2 file to edit." -#define STRING_IMGUI_TOOLTIP_FILE_SAVE "Save the current .anm2 file." -#define STRING_IMGUI_TOOLTIP_FILE_SAVE_AS "Save the current .anm2 file to a location." -#define STRING_IMGUI_TOOLTIP_PLAYBACK_IS_LOOP "Toggles the animation playback looping when the time reaches the end." -#define STRING_IMGUI_TOOLTIP_WIZARD_EXPORT_FRAMES_TO_PNG "Records the current animation and exports the frames as .pngs.\nLook in the working directory of the program for them." -#define STRING_IMGUI_TOOLTIP_FILE_MENU "Opens the file menu, for reading/writing anm2 files." -#define STRING_IMGUI_TOOLTIP_PLAYBACK_MENU "Opens the playback menu, for managing playback settings." -#define STRING_IMGUI_TOOLTIP_WIZARD_MENU "Opens the wizard menu, for handy functions involving the anm2." - -#define STRING_OPENGL_VERSION "#version 330" - -#define PATH_SETTINGS "settings.ini" diff --git a/src/anm2.cpp b/src/anm2.cpp index 8ffe636..9ab3390 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -1,12 +1,10 @@ +// Anm2 file format; serializing/deserializing + #include "anm2.h" using namespace tinyxml2; -static void _anm2_created_on_set(Anm2* self); - -// Sets the anm2's date to the system's current date -static void -_anm2_created_on_set(Anm2* self) +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); @@ -17,9 +15,7 @@ _anm2_created_on_set(Anm2* self) self->createdOn = timeString.str(); } -// Serializes the anm2 struct into XML and exports it to the given path -bool -anm2_serialize(Anm2* self, const std::string& path) +bool anm2_serialize(Anm2* self, const std::string& path) { XMLDocument document; XMLError error; @@ -33,17 +29,11 @@ anm2_serialize(Anm2* self, const std::string& path) XMLElement* eventsElement; XMLElement* animationsElement; - if (!self || path.empty()) - return false; + if (!self || path.empty()) return false; - // Update creation date on first version - if (self->version == 0) - _anm2_created_on_set(self); + if (self->version == 0) _anm2_created_on_set(self); - // Set anm2 path to argument self->path = path; - - // Update version self->version++; // AnimatedActor @@ -137,7 +127,7 @@ anm2_serialize(Anm2* self, const std::string& path) animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]); animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->defaultAnimation.c_str()); // DefaultAnimation - for (const auto & [id, animation] : self->animations) + for (auto& [id, animation] : self->animations) { XMLElement* animationElement; XMLElement* rootAnimationElement; @@ -155,7 +145,7 @@ anm2_serialize(Anm2* self, const std::string& path) // RootAnimation rootAnimationElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ROOT_ANIMATION]); - for (const auto & frame : animation.rootAnimation.frames) + for (auto& frame : animation.rootAnimation.frames) { XMLElement* frameElement; @@ -188,8 +178,10 @@ anm2_serialize(Anm2* self, const std::string& path) // LayerAnimations layerAnimationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYER_ANIMATIONS]); - for (const auto & [layerID, layerAnimation] : animation.layerAnimations) + for (auto& [layerIndex, layerID] : self->layerMap) { + Anm2Item& layerAnimation = animation.layerAnimations[layerID]; + XMLElement* layerAnimationElement; // LayerAnimation @@ -304,30 +296,28 @@ anm2_serialize(Anm2* self, const std::string& path) if (error != XML_SUCCESS) { - std::cout << STRING_ERROR_ANM2_WRITE << path << std::endl; + log_error(std::format(ANM2_WRITE_ERROR, path)); return false; } - std::cout << STRING_INFO_ANM2_WRITE << path << std::endl; + log_info(std::format(ANM2_WRITE_INFO, path)); return true; } -// Loads the .anm2 file and deserializes it into the struct equivalent -bool -anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) +bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) { XMLDocument xmlDocument; XMLError xmlError; const XMLElement* xmlElement; const XMLElement* xmlRoot; - Anm2Animation* animation = NULL; - Anm2Layer* layer = NULL; - Anm2Null* null = NULL; - Anm2Item* item = NULL; - Anm2Event* event = NULL; - Anm2Frame* frame = NULL; - Anm2Spritesheet* spritesheet = NULL; + Anm2Animation* animation = nullptr; + Anm2Layer* layer = nullptr; + Anm2Null* null = nullptr; + Anm2Item* item = nullptr; + Anm2Event* event = nullptr; + Anm2Frame* frame = nullptr; + Anm2Spritesheet* spritesheet = nullptr; Anm2Element anm2Element = ANM2_ELEMENT_ANIMATED_ACTOR; Anm2Attribute anm2Attribute = ANM2_ATTRIBUTE_ID; Anm2Item addItem; @@ -335,9 +325,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) Anm2Null addNull; Anm2Event addEvent; Anm2Spritesheet addSpritesheet; + s32 layerMapIndex = 0; + bool isLayerMapSet = false; + bool isFirstAnimationDone = false; - if (!self || path.empty()) - return false; + if (!self || path.empty()) return false; *self = Anm2{}; @@ -345,13 +337,10 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) if (xmlError != XML_SUCCESS) { - std::cout << STRING_ERROR_ANM2_READ << path << xmlDocument.ErrorStr() << std::endl; + log_error(std::format(ANM2_READ_ERROR, xmlDocument.ErrorStr())); return false; } - // Free the loaded textures used by resources so new ones for the anm2 can be loaded - resources_textures_free(resources); - // 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(); @@ -365,8 +354,8 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) // Iterate through elements while (xmlElement) { - const XMLAttribute* xmlAttribute = NULL; - const XMLElement* xmlChild = NULL; + const XMLAttribute* xmlAttribute = nullptr; + const XMLElement* xmlChild = nullptr; s32 id = 0; anm2Element = ANM2_ELEMENT_STRING_TO_ENUM(xmlElement->Name()); @@ -389,6 +378,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) id = map_next_id_get(self->animations); self->animations[id] = Anm2Animation{}; animation = &self->animations[id]; + + if (isFirstAnimationDone) + isLayerMapSet = true; + + isFirstAnimationDone = true; break; case ANM2_ELEMENT_ROOT_ANIMATION: // RootAnimation item = &animation->rootAnimation; @@ -455,6 +449,13 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) break; case ANM2_ATTRIBUTE_LAYER_ID: // LayerId id = atoi(xmlAttribute->Value()); + + if (!isLayerMapSet) + { + self->layerMap[layerMapIndex] = id; + layerMapIndex++; + } + animation->layerAnimations[id] = addItem; item = &animation->layerAnimations[id]; break; @@ -588,9 +589,7 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) xmlAttribute = xmlAttribute->Next(); } - // Load this anm2's spritesheet textures - if (anm2Element == ANM2_ELEMENT_SPRITESHEET) - resources_texture_init(resources, spritesheet->path , id); + if (anm2Element == ANM2_ELEMENT_SPRITESHEET) resources_texture_init(resources, spritesheet->path , id); xmlChild = xmlElement->FirstChildElement(); @@ -600,7 +599,6 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) continue; } - // Iterate through siblings while (xmlElement) { const XMLElement* xmlNext; @@ -613,12 +611,11 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) break; } - // If no siblings, return to parent. If no parent, end parsing - xmlElement = xmlElement->Parent() ? xmlElement->Parent()->ToElement() : NULL; + xmlElement = xmlElement->Parent() ? xmlElement->Parent()->ToElement() : nullptr; } } - std::cout << STRING_INFO_ANM2_READ << path << std::endl; + log_info(std::format(ANM2_READ_INFO, path)); // Return to old working directory std::filesystem::current_path(workingPath); @@ -626,9 +623,7 @@ anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) return true; } -// Adds a layer to the anm2 -void -anm2_layer_add(Anm2* self) +void anm2_layer_add(Anm2* self) { s32 id = map_next_id_get(self->layers); @@ -638,9 +633,7 @@ anm2_layer_add(Anm2* self) animation.layerAnimations[id] = Anm2Item{}; } -// Removes a layer from the anm2 -void -anm2_layer_remove(Anm2* self, s32 id) +void anm2_layer_remove(Anm2* self, s32 id) { // Make sure the layer exists auto it = self->layers.find(id); @@ -653,9 +646,7 @@ anm2_layer_remove(Anm2* self, s32 id) animation.layerAnimations.erase(id); } -// Adds a null to the anm2 -void -anm2_null_add(Anm2* self) +void anm2_null_add(Anm2* self) { s32 id = map_next_id_get(self->nulls); @@ -665,9 +656,7 @@ anm2_null_add(Anm2* self) animation.nullAnimations[id] = Anm2Item{}; } -// Removes the specified null from the anm2 -void -anm2_null_remove(Anm2* self, s32 id) +void anm2_null_remove(Anm2* self, s32 id) { // Make sure the null exists auto it = self->nulls.find(id); @@ -680,26 +669,16 @@ anm2_null_remove(Anm2* self, s32 id) animation.nullAnimations.erase(id); } -// Adds an animation to the anm2 -s32 -anm2_animation_add(Anm2* self) +s32 anm2_animation_add(Anm2* self) { s32 id = map_next_id_get(self->animations); Anm2Animation animation; - // Match layers - for (auto & [layerID, layer] : self->layers) - { - animation.layerAnimations[layerID] = Anm2Item{}; - } + for (auto& [layerID, layer] : self->layers) + animation.layerAnimations[layerID] = Anm2Item{}; + for (auto & [nullID, null] : self->nulls) + animation.nullAnimations[nullID] = Anm2Item{}; - // Match nulls - for (auto & [nullID, null] : self->nulls) - { - animation.nullAnimations[nullID] = Anm2Item{}; - } - - // Add a root frame animation.rootAnimation.frames.push_back(Anm2Frame{}); self->animations[id] = animation; @@ -707,38 +686,29 @@ anm2_animation_add(Anm2* self) return id; } -// Removes an animation by id from the anm2 -void -anm2_animation_remove(Anm2* self, s32 id) +void anm2_animation_remove(Anm2* self, s32 id) { self->animations.erase(id); } -// Sets the anm2 to default -void -anm2_new(Anm2* self) +void anm2_new(Anm2* self) { *self = Anm2{}; _anm2_created_on_set(self); } -Anm2Animation* -anm2_animation_from_reference(Anm2* self, Anm2Reference* reference) +Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference) { auto it = self->animations.find(reference->animationID); - if (it == self->animations.end()) - return NULL; + if (it == self->animations.end()) return nullptr; return &it->second; } -// Returns the item from a anm2 reference. -Anm2Item* -anm2_item_from_reference(Anm2* self, Anm2Reference* reference) +Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference) { Anm2Animation* animation = anm2_animation_from_reference(self, reference); - if (!animation) - return NULL; + if (!animation) return nullptr; switch (reference->itemType) { @@ -748,78 +718,108 @@ anm2_item_from_reference(Anm2* self, Anm2Reference* reference) { auto it = animation->layerAnimations.find(reference->itemID); if (it == animation->layerAnimations.end()) - return NULL; + return nullptr; return &it->second; } case ANM2_NULL: { auto it = animation->nullAnimations.find(reference->itemID); if (it == animation->nullAnimations.end()) - return NULL; + return nullptr; return &it->second; } case ANM2_TRIGGERS: return &animation->triggers; default: - return NULL; + return nullptr; } } -// Gets the frame from the reference's properties -Anm2Frame* -anm2_frame_from_reference(Anm2* self, Anm2Reference* reference) +Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference) { Anm2Item* item = anm2_item_from_reference(self, reference); - if (!item) - return NULL; - - if (reference->frameIndex < 0 || reference->frameIndex >= (s32)item->frames.size()) - return NULL; + if (!item) return nullptr; + if (reference->frameIndex < 0 || reference->frameIndex >= (s32)item->frames.size()) return nullptr; return &item->frames[reference->frameIndex]; } -// Creates or fetches a frame from a given time. -void -anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time) +s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time) { Anm2Animation* animation = anm2_animation_from_reference(self, &reference); - if (!animation) - return; - - if (time < 0 || time > animation->frameNum) - return; + 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; + if (!item) return INDEX_NONE; - Anm2Frame* nextFrame = NULL; s32 delayCurrent = 0; s32 delayNext = 0; - for (s32 i = 0; i < (s32)item->frames.size(); i++) + for (auto [i, frame] : std::views::enumerate(item->frames)) { - *frame = item->frames[i]; - delayNext += frame->delay; + delayNext += frame.delay; - // Use the last parsed frame as the given frame. if (time >= delayCurrent && time < delayNext) - { - if (i + 1 < (s32)item->frames.size()) - nextFrame = &item->frames[i + 1]; - else - nextFrame = NULL; - break; - } + return i; - delayCurrent += frame->delay; + delayCurrent += frame.delay; } - // Interpolate, but only if there's a frame following. + return INDEX_NONE; +} + +void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time) +{ + Anm2Animation* animation = anm2_animation_from_reference(self, &reference); + + if (!animation) return; + if (time < 0 || time > animation->frameNum) return; + + Anm2Item* item = anm2_item_from_reference(self, &reference); + + if (!item) return; + + Anm2Frame* nextFrame = 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()) + nextFrame = &item->frames[i + 1]; + else + nextFrame = nullptr; + break; + } + + delayCurrent += frame->delay; + } + } + + if (reference.itemType == ANM2_TRIGGERS) + return; + if (frame->isInterpolated && nextFrame) { f32 interpolationTime = (time - delayCurrent) / (delayNext - delayCurrent); @@ -832,62 +832,54 @@ anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 } } -// Returns the animation's length, based on the present frames. -s32 -anm2_animation_length_get(Anm2* self, s32 animationID) +s32 anm2_animation_length_get(Anm2Animation* self) { - auto it = self->animations.find(animationID); - if (it == self->animations.end()) - return -1; + s32 length = 0; - Anm2Animation* animation = &it->second; - s32 delayHighest = 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); + } + }; - // Go through all items and see which one has the latest frame; that's the length - // Root - for (auto & frame : animation->rootAnimation.frames) - delayHighest = std::max(delayHighest, frame.delay); + accumulate_max_delay(self->rootAnimation.frames); - // Layer - for (auto & [id, item] : animation->layerAnimations) - for (auto & frame : item.frames) - delayHighest = std::max(delayHighest, frame.delay); + for (const auto& [_, item] : self->layerAnimations) + accumulate_max_delay(item.frames); - // Null - for (auto & [id, item] : animation->nullAnimations) - for (auto & frame : item.frames) - delayHighest = std::max(delayHighest, frame.delay); + for (const auto& [_, item] : self->nullAnimations) + accumulate_max_delay(item.frames); - // Triggers - for (auto & trigger : animation->triggers.frames) - delayHighest = std::max(delayHighest, trigger.atFrame); + for (const auto& frame : self->triggers.frames) + length = std::max(length, frame.atFrame); - return delayHighest; + return length; } -// Will try adding a frame to the anm2 given the specified reference -Anm2Frame* -anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) +void anm2_animation_length_set(Anm2Animation* self) +{ + self->frameNum = anm2_animation_length_get(self); +} + +Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) { Anm2Animation* animation = anm2_animation_from_reference(self, reference); Anm2Item* item = anm2_item_from_reference(self, reference); - if (!animation || !item) - return NULL; + if (!animation || !item) return nullptr; if (item) { Anm2Frame frame = Anm2Frame{}; - s32 index = -1; + s32 index = INDEX_NONE; if (reference->itemType == ANM2_TRIGGERS) { - // Don't add redundant triggers - for (auto & frameCheck : item->frames) - { - if (frameCheck.atFrame == time) - return NULL; - } + for (auto & frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr; frame.atFrame = time; index = item->frames.size(); @@ -896,20 +888,15 @@ anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) { s32 frameDelayCount = 0; - // Get the latest frame delay, to see where this frame might lie for (auto & frameCheck : item->frames) frameDelayCount += frameCheck.delay; - // If adding the smallest frame delay would be over the length, don't bother - if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) - return NULL; + if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) return nullptr; - // Will insert next to frame if frame exists Anm2Frame* checkFrame = anm2_frame_from_reference(self, reference); if (checkFrame) { - // Will shrink frame delay to fit if (frameDelayCount + checkFrame->delay > animation->frameNum) frame.delay = animation->frameNum - frameDelayCount; @@ -924,27 +911,22 @@ anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) return &item->frames[index]; } - return NULL; + return nullptr; } -// Clears the anm2 reference, totally -void -anm2_reference_clear(Anm2Reference* self) +void anm2_reference_clear(Anm2Reference* self) { *self = Anm2Reference{}; } -// Clears the anm2 reference's item -void -anm2_reference_item_clear(Anm2Reference* self) +void anm2_reference_item_clear(Anm2Reference* self) { self->itemType = ANM2_NONE; - self->itemID = -1; + self->itemID = ID_NONE; + self->frameIndex = INDEX_NONE; } -// Clears the anm2 reference's frame -void -anm2_reference_frame_clear(Anm2Reference* self) +void anm2_reference_frame_clear(Anm2Reference* self) { - self->frameIndex = -1; + self->frameIndex = INDEX_NONE; } \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 0b0b880..3400e20 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -13,6 +13,12 @@ #define ANM2_FRAME_DELAY_MIN 1 #define ANM2_STRING_MAX 0xFF +#define ANM2_READ_ERROR "Failed to read anm2 from file: {}" +#define ANM2_READ_INFO "Read anm2 from file: {}" +#define ANM2_WRITE_ERROR "Failed to write anm2 to file: {}" +#define ANM2_WRITE_INFO "Wrote anm2 to file: {}" +#define STRING_ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p" + /* Elements */ #define ANM2_ELEMENT_LIST \ X(ANIMATED_ACTOR, "AnimatedActor") \ @@ -120,24 +126,24 @@ enum Anm2Type struct Anm2Spritesheet { - std::string path; + std::string path{}; }; struct Anm2Layer { - std::string name = STRING_ANM2_NEW_LAYER; + std::string name = "New Layer"; s32 spritesheetID = -1; }; struct Anm2Null { - std::string name = STRING_ANM2_NEW_NULL; + std::string name = "New Null"; bool isShowRect = false; }; struct Anm2Event { - std::string name = STRING_ANM2_NEW_EVENT; + std::string name = "New Event"; }; struct Anm2Frame @@ -146,8 +152,8 @@ struct Anm2Frame bool isVisible = true; f32 rotation = 1.0f; s32 delay = ANM2_FRAME_DELAY_MIN; - s32 atFrame = -1; - s32 eventID = -1; + s32 atFrame = INDEX_NONE; + s32 eventID = ID_NONE; vec2 crop = {0.0f, 0.0f}; vec2 pivot = {0.0f, 0.0f}; vec2 position = {0.0f, 0.0f}; @@ -166,7 +172,7 @@ struct Anm2Item struct Anm2Animation { s32 frameNum = ANM2_FRAME_NUM_MIN; - std::string name = STRING_ANM2_NEW_ANIMATION; + std::string name = "New Animation"; bool isLoop = true; Anm2Item rootAnimation; std::map layerAnimations; @@ -176,29 +182,47 @@ struct Anm2Animation struct Anm2 { - std::string path; - std::string defaultAnimation; - std::string createdBy = STRING_ANM2_CREATED_BY_DEFAULT; - std::string createdOn; + std::string path{}; + std::string defaultAnimation{}; + std::string createdBy = "robot"; + std::string createdOn{}; std::map spritesheets; std::map layers; std::map nulls; - std::map events; + std::map events; std::map animations; + std::map layerMap; // id, index s32 fps = 30; s32 version = 0; }; struct Anm2Reference { - s32 animationID = -1; + s32 animationID = ID_NONE; Anm2Type itemType = ANM2_NONE; - s32 itemID = -1; - s32 frameIndex = -1; - + s32 itemID = ID_NONE; + s32 frameIndex = INDEX_NONE; auto operator<=>(const Anm2Reference&) const = default; }; +struct Anm2AnimationWithID +{ + s32 id; + Anm2Animation animation; +}; + +struct Anm2EventWithID +{ + s32 id; + Anm2Event event; +}; + +struct Anm2FrameWithReference +{ + Anm2Reference reference; + Anm2Frame frame; +}; + void anm2_layer_add(Anm2* self); void anm2_layer_remove(Anm2* self, s32 id); void anm2_null_add(Anm2* self); @@ -213,9 +237,11 @@ void anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const std:: Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference); Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference); Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference); +s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time); void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); void anm2_reference_clear(Anm2Reference* self); void anm2_reference_item_clear(Anm2Reference* self); void anm2_reference_frame_clear(Anm2Reference* self); -s32 anm2_animation_length_get(Anm2* self, s32 animationID); \ No newline at end of file +s32 anm2_animation_length_get(Anm2Animation* self); +void anm2_animation_length_set(Anm2Animation* self); \ No newline at end of file diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..6010b26 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,50 @@ +// Common rendering area + +#include "canvas.h" + +void canvas_init(Canvas* self, vec2 size) +{ + self->size = size; + + glGenFramebuffers(1, &self->fbo); + + glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); + + glGenTextures(1, &self->texture); + glBindTexture(GL_TEXTURE_2D, self->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)self->size.x, (s32)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); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->texture, 0); + + glGenRenderbuffers(1, &self->rbo); + glBindRenderbuffer(GL_RENDERBUFFER, self->rbo); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, (s32)self->size.x, (s32)self->size.y); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Texture + glGenVertexArrays(1, &self->textureVAO); + glGenBuffers(1, &self->textureVBO); + glGenBuffers(1, &self->textureEBO); + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, nullptr, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_STATIC_DRAW); + + // Position attribute + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)0); + + // UV attribute + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); + + glBindVertexArray(0); +} \ No newline at end of file diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..b012a63 --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,33 @@ +#pragma once + +#include "texture.h" +#include "shader.h" + +#define CANVAS_ZOOM_MIN 1 +#define CANVAS_ZOOM_MAX 1000 +#define CANVAS_ZOOM_STEP 25 +#define CANVAS_LINE_LENGTH 100000.0f +#define CANVAS_GRID_RANGE 100 +#define CANVAS_GRID_MIN 1 +#define CANVAS_GRID_MAX 100 + +const f32 CANVAS_AXIS_VERTICES[] = +{ + -CANVAS_LINE_LENGTH, 0.0f, + CANVAS_LINE_LENGTH, 0.0f, + 0.0f, -CANVAS_LINE_LENGTH, + 0.0f, CANVAS_LINE_LENGTH +}; + +struct Canvas +{ + GLuint fbo; + GLuint rbo; + GLuint texture; + GLuint textureEBO; + GLuint textureVAO; + GLuint textureVBO; + vec2 size; +}; + +void canvas_init(Canvas* self, vec2 size); \ No newline at end of file diff --git a/src/clipboard.cpp b/src/clipboard.cpp new file mode 100644 index 0000000..ade416c --- /dev/null +++ b/src/clipboard.cpp @@ -0,0 +1,140 @@ +#include "clipboard.h" + +static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2) +{ + switch (self->type) + { + case CLIPBOARD_FRAME: + { + Anm2FrameWithReference frameWithReference = std::get(self->data); + Anm2Animation* animation = anm2_animation_from_reference(anm2, &frameWithReference.reference); + + if (!animation) break; + + std::vector* frames = nullptr; + + switch (frameWithReference.reference.itemType) + { + case ANM2_ROOT: + frames = &animation->rootAnimation.frames; + break; + case ANM2_LAYER: + frames = &animation->layerAnimations[frameWithReference.reference.itemID].frames; + break; + case ANM2_NULL: + frames = &animation->nullAnimations[frameWithReference.reference.itemID].frames; + break; + case ANM2_TRIGGERS: + frames = &animation->triggers.frames; + break; + default: + break; + } + + if (frames) + frames->erase(frames->begin() + frameWithReference.reference.frameIndex); + + break; + } + case CLIPBOARD_ANIMATION: + { + Anm2AnimationWithID animationWithID = std::get(self->data); + + for (auto & [id, animation] : anm2->animations) + { + if (id == animationWithID.id) + anm2->animations.erase(animationWithID.id); + } + break; + } + case CLIPBOARD_EVENT: + { + Anm2EventWithID eventWithID = std::get(self->data); + + for (auto & [id, event] : anm2->events) + { + if (id == eventWithID.id) + anm2->events.erase(eventWithID.id); + } + break; + } + default: + break; + } +} + +static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* location, Anm2* anm2) +{ + switch (self->type) + { + case CLIPBOARD_FRAME: + { + Anm2FrameWithReference* frameWithReference = std::get_if(&self->data); + Anm2Reference* reference = std::get_if(location); + + if (!frameWithReference || !reference) break; + if (frameWithReference->reference.itemType != reference->itemType) break; + + Anm2Animation* animation = anm2_animation_from_reference(anm2, reference); + Anm2Item* anm2Item = anm2_item_from_reference(anm2, reference); + + if (!animation || !anm2Item) break; + + s32 insertIndex = (reference->itemType == ANM2_TRIGGERS) ? + reference->frameIndex : MAX(reference->frameIndex, (s32)anm2Item->frames.size()); + + anm2Item->frames.insert(anm2Item->frames.begin() + insertIndex, frameWithReference->frame); + + anm2_animation_length_set(animation); + break; + } + case CLIPBOARD_ANIMATION: + { + s32 index = 0; + + if (std::holds_alternative(*location)) + index = std::get(*location); + + index = CLAMP(index, 0, (s32)anm2->animations.size()); + + map_insert_shift(anm2->animations, index, std::get(self->data).animation); + break; + } + case CLIPBOARD_EVENT: + { + s32 index = 0; + + if (std::holds_alternative(*location)) + index = std::get(*location); + + index = CLAMP(index, 0, (s32)anm2->events.size()); + + map_insert_shift(anm2->events, index, std::get(self->data).event); + break; + } + default: + break; + } +} + +void clipboard_copy(Clipboard* self) +{ + self->item = self->hoveredItem; +} + +void clipboard_cut(Clipboard* self) +{ + self->item = self->hoveredItem; + _clipboard_item_remove(&self->item, self->anm2); +} + +void clipboard_paste(Clipboard* self) +{ + _clipboard_item_paste(&self->item, &self->location, self->anm2); +} + +void clipboard_init(Clipboard* self, Anm2* anm2) +{ + self->anm2 = anm2; +} + diff --git a/src/clipboard.h b/src/clipboard.h new file mode 100644 index 0000000..df4db7b --- /dev/null +++ b/src/clipboard.h @@ -0,0 +1,44 @@ +#pragma once + +#include "anm2.h" + +enum ClipboardItemType +{ + CLIPBOARD_NONE, + CLIPBOARD_FRAME, + CLIPBOARD_ANIMATION, + CLIPBOARD_EVENT +}; + +struct ClipboardItem +{ + std::variant data = std::monostate(); + ClipboardItemType type = CLIPBOARD_NONE; + + ClipboardItem() = default; + + ClipboardItem(const Anm2FrameWithReference& frame) + : data(frame), type(CLIPBOARD_FRAME) {} + + ClipboardItem(const Anm2AnimationWithID& anim) + : data(anim), type(CLIPBOARD_ANIMATION) {} + + ClipboardItem(const Anm2EventWithID& event) + : data(event), type(CLIPBOARD_EVENT) {} +}; + +using ClipboardLocation = std::variant; + +struct Clipboard +{ + Anm2* anm2 = nullptr; + ClipboardItem item; + ClipboardItem hoveredItem; + ClipboardLocation location; +}; + +void clipboard_copy(Clipboard* self); +void clipboard_cut(Clipboard* self); +void clipboard_paste(Clipboard* self); +void clipboard_init(Clipboard* self, Anm2* anm2); + diff --git a/src/dialog.cpp b/src/dialog.cpp index d39866c..5d61891 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -1,10 +1,10 @@ +// File dialog; saving/writing to files + #include "dialog.h" static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter); -// Callback that runs during the file dialog; will get the path and determine if one has been selected -static void -_dialog_callback(void* userdata, const char* const* filelist, s32 filter) +static void _dialog_callback(void* userdata, const char* const* filelist, s32 filter) { Dialog* self; @@ -19,9 +19,7 @@ _dialog_callback(void* userdata, const char* const* filelist, s32 filter) self->isSelected = false; } -// Initializes dialog -void -dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window) +void dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window) { self->anm2 = anm2; self->reference = reference; @@ -29,41 +27,31 @@ dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resou self->window = window; } -// Opens file dialog to open a new anm2 -void -dialog_anm2_open(Dialog* self) +void dialog_anm2_open(Dialog* self) { - SDL_ShowOpenFileDialog(_dialog_callback, self, NULL, DIALOG_FILE_FILTER_ANM2, 1, NULL, false); + SDL_ShowOpenFileDialog(_dialog_callback, self, nullptr, DIALOG_FILE_FILTER_ANM2, 1, nullptr, false); self->type = DIALOG_ANM2_OPEN; } -// Opens file dialog to save anm2 -void -dialog_anm2_save(Dialog* self) +void dialog_anm2_save(Dialog* self) { - SDL_ShowSaveFileDialog(_dialog_callback, self, NULL, DIALOG_FILE_FILTER_ANM2, 1, NULL); + SDL_ShowSaveFileDialog(_dialog_callback, self, nullptr, DIALOG_FILE_FILTER_ANM2, 1, nullptr); self->type = DIALOG_ANM2_SAVE; } -// Opens file dialog to open png -void -dialog_png_open(Dialog* self) +void dialog_png_open(Dialog* self) { - SDL_ShowOpenFileDialog(_dialog_callback, self, NULL, DIALOG_FILE_FILTER_PNG, 1, NULL, false); + SDL_ShowOpenFileDialog(_dialog_callback, self, nullptr, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); self->type = DIALOG_PNG_OPEN; } -// Opens file dialog to replace a given png -void -dialog_png_replace(Dialog* self) +void dialog_png_replace(Dialog* self) { - SDL_ShowOpenFileDialog(_dialog_callback, self, NULL, DIALOG_FILE_FILTER_PNG, 1, NULL, false); + SDL_ShowOpenFileDialog(_dialog_callback, self, nullptr, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); self->type = DIALOG_PNG_REPLACE; } -// Ticks dialog -void -dialog_tick(Dialog* self) +void dialog_tick(Dialog* self) { if (self->isSelected) { diff --git a/src/dialog.h b/src/dialog.h index a531f4c..f9f895b 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -26,12 +26,12 @@ enum DialogType struct Dialog { - Anm2* anm2 = NULL; - Anm2Reference* reference = NULL; - Resources* resources = NULL; - SDL_Window* window = NULL; - std::string path; - s32 replaceID = -1; + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + SDL_Window* window = nullptr; + std::string path{}; + s32 replaceID = ID_NONE; DialogType type = DIALOG_NONE; bool isSelected = false; }; diff --git a/src/editor.cpp b/src/editor.cpp index 76fffb7..f308fba 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,17 +1,15 @@ +// Handles the rendering of the spritesheet editor + #include "editor.h" -static s32 _editor_grid_set(Editor* self); - -// Sets the editor's grid -static s32 -_editor_grid_set(Editor* self) +static s32 _editor_grid_set(Editor* self) { std::vector vertices; s32 verticalLineCount = (s32)(EDITOR_SIZE.x / MIN(self->settings->editorGridSizeX, EDITOR_GRID_MIN)); s32 horizontalLineCount = (s32)(EDITOR_SIZE.y / MIN(self->settings->editorGridSizeY, EDITOR_GRID_MIN)); - /* Vertical */ + // Vertical for (s32 i = 0; i <= verticalLineCount; i++) { s32 x = i * self->settings->editorGridSizeX - self->settings->editorGridOffsetX; @@ -23,7 +21,7 @@ _editor_grid_set(Editor* self) vertices.push_back(1.0f); } - /* Horizontal */ + // Horizontal for (s32 i = 0; i <= horizontalLineCount; i++) { s32 y = i * self->settings->editorGridSizeY - self->settings->editorGridOffsetY; @@ -45,9 +43,7 @@ _editor_grid_set(Editor* self) return (s32)vertices.size(); } -// Initializes editor -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) { self->anm2 = anm2; self->reference = reference; @@ -61,7 +57,7 @@ editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resou glGenTextures(1, &self->texture); glBindTexture(GL_TEXTURE_2D, self->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)EDITOR_SIZE.x, (s32)EDITOR_SIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)EDITOR_SIZE.x, (s32)EDITOR_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); @@ -98,7 +94,7 @@ editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resou glBindVertexArray(self->textureVAO); glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_STATIC_DRAW); @@ -116,15 +112,14 @@ editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resou _editor_grid_set(self); } -// Draws the editor -void -editor_draw(Editor* self) +void editor_draw(Editor* self) { GLuint shaderLine = self->resources->shaders[SHADER_LINE]; GLuint shaderLineDotted = self->resources->shaders[SHADER_LINE_DOTTED]; GLuint shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - f32 zoomFactor = PERCENT_TO_UNIT(self->settings->editorZoom); + f32 zoomFactor = PERCENT_TO_UNIT(self->settings->editorZoom); + // Get normalized panning glm::vec2 ndcPan = glm::vec2(-self->settings->editorPanX / (EDITOR_SIZE.x / 2.0f), -self->settings->editorPanY / (EDITOR_SIZE.y / 2.0f)); @@ -294,16 +289,12 @@ editor_draw(Editor* self) glBindFramebuffer(GL_FRAMEBUFFER, 0); } -// Ticks editor -void -editor_tick(Editor* self) +void editor_tick(Editor* self) { self->settings->editorZoom = CLAMP(self->settings->editorZoom, EDITOR_ZOOM_MIN, EDITOR_ZOOM_MAX); } -// Frees editor -void -editor_free(Editor* self) +void editor_free(Editor* self) { glDeleteTextures(1, &self->texture); glDeleteFramebuffers(1, &self->fbo); diff --git a/src/editor.h b/src/editor.h index a575833..3c662e0 100644 --- a/src/editor.h +++ b/src/editor.h @@ -3,6 +3,7 @@ #include "anm2.h" #include "resources.h" #include "settings.h" +#include "canvas.h" #define EDITOR_ZOOM_MIN 1 #define EDITOR_ZOOM_MAX 1000 @@ -12,7 +13,7 @@ #define EDITOR_GRID_OFFSET_MIN 0 #define EDITOR_GRID_OFFSET_MAX 100 -const vec2 EDITOR_SIZE = {5000, 5000}; +const vec2 EDITOR_SIZE = {2000, 2000}; const vec2 EDITOR_PIVOT_SIZE = {4, 4}; const vec4 EDITOR_TEXTURE_TINT = COLOR_OPAQUE; const vec4 EDITOR_BORDER_TINT = COLOR_OPAQUE; @@ -20,10 +21,10 @@ const vec4 EDITOR_FRAME_TINT = COLOR_RED; struct Editor { - Anm2* anm2 = NULL; - Anm2Reference* reference = NULL; - Resources* resources = NULL; - Settings* settings = NULL; + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; GLuint fbo; GLuint rbo; GLuint gridVAO; diff --git a/src/imgui.cpp b/src/imgui.cpp index 88eefea..cee1e67 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,1133 +1,1140 @@ +// Handles everything imgui + #include "imgui.h" -static void _imgui_tooltip(const std::string& tooltip); -static void _imgui_timeline_item_frames(Imgui* self, Anm2Reference reference, s32* index); -static void _imgui_timeline_item(Imgui* self, Anm2Reference reference, s32* index); -static void _imgui_timeline(Imgui* self); -static void _imgui_animations(Imgui* self); -static void _imgui_events(Imgui* self); -static void _imgui_spritesheets(Imgui* self); -static void _imgui_frame_properties(Imgui* self); -static void _imgui_spritesheet_editor(Imgui* self); -static void _imgui_animation_preview(Imgui* self); -static void _imgui_taskbar(Imgui* self); -static void _imgui_undoable(Imgui* self); -static void _imgui_undo_stack_push(Imgui* self); -static void _imgui_spritesheet_editor_set(Imgui* self, s32 spritesheetID); - -// Push undo stack using imgui's members -static void _imgui_undo_stack_push(Imgui* self) +template +static void _imgui_clipboard_hovered_item_set(Imgui* self, const T& data) { - Snapshot snapshot = {*self->anm2, *self->reference, *self->time}; - snapshots_undo_stack_push(self->snapshots, &snapshot); + self->clipboard->hoveredItem = ClipboardItem(data); } -// Tooltip for the last hovered widget -static void _imgui_tooltip(const std::string& tooltip) +static void _imgui_atlas_image(Imgui* self, TextureType texture) { - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) - ImGui::SetTooltip(tooltip.c_str()); + ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[texture]), IMVEC2_ATLAS_UV_GET(texture)); } -// If the last widget is activated, pushes the undo stack -static void _imgui_undoable(Imgui* self) +static void _imgui_item_text(const ImguiItem& item) { - if (ImGui::IsItemActivated()) - _imgui_undo_stack_push(self); + ImGui::Text(item.label.c_str()); } -// Will push a new spritesheetID to the editor -static void _imgui_spritesheet_editor_set(Imgui* self, s32 spritesheetID) +static void _imgui_text_string(const std::string& string) { - // Make sure the spritesheet exists! - if (self->anm2->spritesheets.contains(spritesheetID)) - self->editor->spritesheetID = spritesheetID; + ImGui::Text(string.c_str()); } -// Drawing the frames of an Anm2Item -static void -_imgui_timeline_item_frames(Imgui* self, Anm2Reference reference, s32* index) +static bool _imgui_is_window_hovered(void) { - ImVec2 frameStartPos; - ImVec2 framePos; - ImVec2 frameFinishPos; - ImVec2 cursorPos = ImGui::GetCursorPos(); - ImVec4 frameColor; - ImVec4 hoveredColor; - ImVec4 activeColor; - - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, &reference); - Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); + return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); +} - switch (reference.itemType) +static bool _imgui_is_no_click_on_item(void) +{ + return ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && _imgui_is_window_hovered(); +} + +static void _imgui_item(Imgui* self, const ImguiItem& item, bool* isActivated) +{ + if (item.is_mnemonic()) { - case ANM2_ROOT: - frameColor = IMGUI_TIMELINE_ROOT_FRAME_COLOR; - hoveredColor = IMGUI_TIMELINE_ROOT_HIGHLIGHT_COLOR; - activeColor = IMGUI_TIMELINE_ROOT_ACTIVE_COLOR; - break; - case ANM2_LAYER: - frameColor = IMGUI_TIMELINE_LAYER_FRAME_COLOR; - hoveredColor = IMGUI_TIMELINE_LAYER_HIGHLIGHT_COLOR; - activeColor = IMGUI_TIMELINE_LAYER_ACTIVE_COLOR; - break; - case ANM2_NULL: - frameColor = IMGUI_TIMELINE_NULL_FRAME_COLOR; - hoveredColor = IMGUI_TIMELINE_NULL_HIGHLIGHT_COLOR; - activeColor = IMGUI_TIMELINE_NULL_ACTIVE_COLOR; - break; - case ANM2_TRIGGERS: - frameColor = IMGUI_TIMELINE_TRIGGERS_FRAME_COLOR; - hoveredColor = IMGUI_TIMELINE_TRIGGERS_HIGHLIGHT_COLOR; - activeColor = IMGUI_TIMELINE_TRIGGERS_ACTIVE_COLOR; - break; - default: - break; + ImVec2 pos = ImGui::GetItemRectMin(); + ImFont* font = ImGui::GetFont(); + f32 fontSize = ImGui::GetFontSize(); + const char* start = item.label.c_str(); + const char* charPointer = start + item.mnemonicIndex; + + pos.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(pos.x + offset, pos.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 (isActivated && ImGui::IsKeyChordPressed(ImGuiMod_Alt | item.mnemonicKey)) + { + *isActivated = true; + ImGui::CloseCurrentPopup(); + } } - ImGui::PushID(*index); - - // Draw only if the animation has a length above 0 - if (animation->frameNum > 0) + if (isActivated && self->isHotkeysEnabled && (item.is_chord() && ImGui::IsKeyChordPressed(item.chord))) { - ImVec2 frameListSize = {IMGUI_TIMELINE_FRAME_SIZE.x * animation->frameNum, IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE.y}; - ImVec2 mousePosRelative; + bool isFocus = !item.is_focus_window() || (imgui_nav_window_root_get() == item.focusWindow); + + if (isFocus) + *isActivated = true; + } - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + if (item.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) + ImGui::SetTooltip(item.tooltip.c_str()); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAMES, frameListSize, true); - - // Will deselect frame if hovering and click; but, if it's later clicked, this won't have any effect - if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - anm2_reference_frame_clear(self->reference); - - vec2 mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); - vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); - - f32 scrollX = ImGui::GetScrollX(); - f32 mousePosRelativeX = mousePos.x - windowPos.x - scrollX; - - frameStartPos = ImGui::GetCursorPos(); - - // Draw the frame background - for (s32 i = 0; i < animation->frameNum; i++) + if (isActivated && *isActivated) + { + if (item.isUndoable) imgui_undo_stack_push(self); + if (item.function) item.function(self); + + if (item.is_popup()) { - ImGui::PushID(i); + ImGui::OpenPopup(item.popup.c_str()); - ImVec2 frameTexturePos = ImGui::GetCursorScreenPos(); - - if (i % IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE == 0) + switch (item.popupType) { - ImVec2 bgMin = frameTexturePos; - ImVec2 bgMax = ImVec2(frameTexturePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, frameTexturePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); - ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_OVERLAY_COLOR); - ImGui::GetWindowDrawList()->AddRectFilled(bgMin, bgMax, bgColor); + case POPUP_CENTER_SCREEN: + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + break; + case POPUP_BY_ITEM: + default: + ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y)); + break; } - - ImGui::Image(self->resources->atlas.id, IMGUI_TIMELINE_FRAME_SIZE, IMVEC2_ATLAS_UV_GET(TEXTURE_FRAME_ALT)); - - ImGui::SameLine(); - ImGui::PopID(); } + } +} - // Draw each frame - for (auto [i, frame] : std::views::enumerate(item->frames)) - { - reference.frameIndex = i; +static bool _imgui_item_combo(Imgui* self, const ImguiItem& item, s32* current, const char* const items[], s32 count) +{ + bool isActivated = ImGui::Combo(item.label.c_str(), current, items, count); + _imgui_item(self, item, &isActivated); + return isActivated; +} - TextureType textureType; - f32 frameWidth = IMGUI_TIMELINE_FRAME_SIZE.x * frame.delay; - ImVec2 frameSize = ImVec2(frameWidth, IMGUI_TIMELINE_FRAME_SIZE.y); +static bool _imgui_item_selectable(Imgui* self, const ImguiItem& item) +{ + const char* label = item.label.c_str(); + s32 flags = item.flags; - if (reference.itemType == ANM2_TRIGGERS) - { - framePos.x = frameStartPos.x + (IMGUI_TIMELINE_FRAME_SIZE.x * frame.atFrame); - textureType = TEXTURE_TRIGGER; - } - else - textureType = frame.isInterpolated ? TEXTURE_CIRCLE : TEXTURE_SQUARE; + if (item.isInactive || item.color.is_normal()) + { + ImVec4 color = item.isInactive ? IMGUI_INACTIVE_COLOR : item.color.normal; + ImGui::PushStyleColor(ImGuiCol_Text, color); + flags |= ImGuiSelectableFlags_Disabled; + } - ImGui::SetCursorPos(framePos); - - ImVec4 buttonColor = *self->reference == reference ? activeColor : frameColor; + ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) :ImVec2(0, 0); + bool isActivated = ImGui::Selectable(label, item.isSelected, flags, size); + _imgui_item(self, item, &isActivated); - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, hoveredColor); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, activeColor); - ImGui::PushStyleColor(ImGuiCol_Border, IMGUI_FRAME_BORDER_COLOR); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, IMGUI_FRAME_BORDER); - - ImGui::PushID(i); + if (item.isInactive || item.color.is_normal()) ImGui::PopStyleColor(); - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize)) - { - s32 frameTime = (s32)(mousePosRelativeX / IMGUI_TIMELINE_FRAME_SIZE.x); + return isActivated; +} - *self->reference = reference; +static bool _imgui_item_inputint(Imgui* self, const ImguiItem& item, s32* value) +{ + if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - *self->time = frameTime; + bool isActivated = ImGui::InputInt(item.label.c_str(), value, item.step, item.stepFast, item.flags); + + if (item.min != 0) *value = MIN(*value, (s32)item.min); + if (item.max != 0) *value = MAX(*value, (s32)item.max); + + _imgui_item(self, item, nullptr); - // Set the preview's spritesheet ID - if (self->reference->itemType == ANM2_LAYER) - _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); - } + return isActivated; +} - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - *self->reference = reference; +static void _imgui_item_inputint2(Imgui* self, const ImguiItem& item, s32 value[2]) +{ + if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - ImGui::SetDragDropPayload(STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP, &reference, sizeof(Anm2Reference)); - ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_LABEL, frameSize); - ImGui::SetCursorPos(ImVec2(1.0f, (IMGUI_TIMELINE_FRAME_SIZE.y / 2) - (TEXTURE_SIZE_SMALL.y / 2))); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); - ImGui::EndDragDropSource(); - } + ImGui::InputInt2(item.label.c_str(), value); + + if (item.min > 0) + { + value[0] = MIN(value[0], (s32)item.min); + value[1] = MIN(value[1], (s32)item.min); + } - if (self->reference->itemID == reference.itemID) - { - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_TIMELINE_FRAME_DRAG_DROP)) - { - Anm2Reference checkReference = *(Anm2Reference*)payload->Data; - if (checkReference != reference) - { - self->isSwap = true; - self->swapReference = reference; - - _imgui_undo_stack_push(self); - } - } - ImGui::EndDragDropTarget(); - } - } + if (item.max > 0) + { + value[0] = MAX(value[0], (s32)item.max); + value[1] = MAX(value[1], (s32)item.max); + } + + _imgui_item(self, item, nullptr); +} - ImGui::PopStyleVar(); - ImGui::PopStyleColor(4); +static bool _imgui_item_inputtext(Imgui* self, const ImguiItem& item, std::string* buffer) +{ + if ((s32)buffer->size() < item.max) buffer->resize(item.max); - ImGui::SetCursorPos(ImVec2(framePos.x + 1.0f, (framePos.y + (IMGUI_TIMELINE_FRAME_SIZE.y / 2)) - TEXTURE_SIZE_SMALL.y / 2)); + if (item.is_size()) + ImGui::SetNextItemWidth(item.size.x); + else + ImGui::SetNextItemWidth(ImGui::CalcTextSize(buffer->c_str()).x); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); + bool isActivated = ImGui::InputText(item.label.c_str(), &(*buffer)[0], item.max, item.flags); + _imgui_item(self, item, nullptr); - ImGui::PopID(); + return isActivated; +} - if (reference.itemType != ANM2_TRIGGERS) - framePos.x += frameWidth; - } +static void _imgui_item_checkbox(Imgui* self, const ImguiItem& item, bool* value) +{ + ImGui::Checkbox(item.label.c_str(), value); - ImGui::EndChild(); - ImGui::PopStyleVar(2); + if (item.is_mnemonic() && ImGui::IsKeyChordPressed(ImGuiMod_Alt | item.mnemonicKey)) + { + *value = !*value; + ImGui::CloseCurrentPopup(); + } - ImGui::SetCursorPosX(cursorPos.x); - ImGui::SetCursorPosY(cursorPos.y + IMGUI_TIMELINE_FRAME_SIZE.y); + _imgui_item(self, item, nullptr); +} + +static bool _imgui_item_radio_button(Imgui* self, const ImguiItem& item, s32* value) +{ + if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); + + bool isActivated = ImGui::RadioButton(item.label.c_str(), value, item.value); + _imgui_item(self, item, &isActivated); + return isActivated; +} + +static void _imgui_item_dragfloat(Imgui* self, const ImguiItem& item, f32* value) +{ + ImGui::DragFloat(item.label.c_str(), value, item.speed, item.min, item.max, item.format.c_str()); + _imgui_item(self, item, nullptr); +} + +static void _imgui_item_coloredit3(Imgui* self, const ImguiItem& item, f32 value[3]) +{ + ImGui::ColorEdit3(item.label.c_str(), value, item.flags); + _imgui_item(self, item, nullptr); +} + +static void _imgui_item_coloredit4(Imgui* self, const ImguiItem& item, f32 value[4]) +{ + ImGui::ColorEdit4(item.label.c_str(), value, item.flags); + _imgui_item(self, item, nullptr); +} + +static void _imgui_item_dragfloat2(Imgui* self, const ImguiItem& item, f32 value[2]) +{ + ImGui::DragFloat2(item.label.c_str(), value, item.speed, item.min, item.max, item.format.c_str()); + _imgui_item(self, item, nullptr); +} + +static bool _imgui_item_button(Imgui* self, const ImguiItem& item) +{ + bool isActivated = ImGui::Button(item.label.c_str(), item.size); + _imgui_item(self, item, &isActivated); + return isActivated; +} + +static bool _imgui_item_selectable_inputtext(Imgui* self, const ImguiItem& item, std::string* string, s32 id) +{ + static s32 renameID = ID_NONE; + static s32 itemID = ID_NONE; + const char* label = item.label.c_str(); + bool isActivated = false; + static std::string buffer{}; + + ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) :ImVec2(0, 0); + + if (renameID == id && itemID == item.id) + { + ImGui::PushID(id); + + ImguiItem itemRenamable = IMGUI_RENAMABLE; + itemRenamable.size = size; + self->isRename = true; + + isActivated = _imgui_item_inputtext(self, itemRenamable, &buffer); + + if (isActivated || _imgui_is_no_click_on_item()) + { + *string = buffer; + renameID = ID_NONE; + itemID = ID_NONE; + self->isRename = false; + } + + ImGui::PopID(); + } + else + { + isActivated = ImGui::Selectable(label, item.isSelected, 0, size); + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) + { + buffer = *string; + renameID = id; + itemID = item.id; + ImGui::SetKeyboardFocusHere(-1); + } + } + + _imgui_item(self, item, &isActivated); + + return isActivated; +} + +static bool _imgui_item_selectable_inputint(Imgui* self, const ImguiItem& item, s32* value, s32 id) +{ + static s32 itemID = ID_NONE; + static s32 changeID = ID_NONE; + const char* label = item.label.c_str(); + bool isActivated = false; + ImVec2 size = item.is_size() ? item.size : item.isSizeToText ? ImGui::CalcTextSize(label) :ImVec2(0, 0); + + if (changeID == id && itemID == item.id) + { + ImGui::PushID(id); + + ImguiItem itemChangeable = IMGUI_CHANGEABLE; + itemChangeable.size = size; + self->isChangeValue = true; + + if (_imgui_item_inputint(self, itemChangeable, value)) + { + itemID = ID_NONE; + changeID = ID_NONE; + self->isChangeValue = false; + } + + if (_imgui_is_no_click_on_item()) + { + itemID = ID_NONE; + changeID = ID_NONE; + self->isChangeValue = false; + } + + ImGui::PopID(); + } + else + { + isActivated = ImGui::Selectable(label, item.isSelected, 0, size); + + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) + { + itemID = item.id; + changeID = id; + ImGui::SetKeyboardFocusHere(-1); + } + } + + _imgui_item(self, item, &isActivated); + + return isActivated; +} + +static void _imgui_item_atlas_image_text(Imgui* self, const ImguiItem& item) +{ + _imgui_atlas_image(self, item.texture); + ImGui::SameLine(); + ImGui::Text(item.label.c_str()); +} + +static bool _imgui_item_atlas_image_selectable(Imgui* self, const ImguiItem& item) +{ + _imgui_atlas_image(self, item.texture); + ImGui::SameLine(); + return _imgui_item_selectable(self, item); +} + + +static bool _imgui_item_atlas_image_selectable_inputtext(Imgui* self, ImguiItem& item, std::string* string, s32 id) +{ + _imgui_atlas_image(self, item.texture); + ImGui::SameLine(); + return _imgui_item_selectable_inputtext(self, item, string, id); +} + +static bool _imgui_item_atlas_image_selectable_inputint(Imgui* self, ImguiItem& item, s32* value, s32 id) +{ + _imgui_atlas_image(self, item.texture); + ImGui::SameLine(); + return _imgui_item_selectable_inputint(self, item, value, id); +} + +static bool _imgui_item_atlas_image_button(Imgui* self, const ImguiItem& item) +{ + bool isActivated = false; + ImVec2 imageSize = VEC2_TO_IMVEC2(ATLAS_SIZES[item.texture]); + ImVec2 buttonSize = item.is_size() ? item.size : imageSize; + + if (item.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_Button, item.color.normal); + if (item.color.is_active()) ImGui::PushStyleColor(ImGuiCol_ButtonActive, item.color.active); + if (item.color.is_hovered()) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, item.color.hovered); + if (item.isSelected) ImGui::PushStyleColor(ImGuiCol_Button, item.color.active); + if (item.is_border()) ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, item.border); + + if (item.is_size()) + { + isActivated = ImGui::Button(item.label.c_str(), buttonSize); + + ImVec2 pos = ImGui::GetItemRectMin(); + ImVec2 imageMin = pos + item.contentOffset; + ImVec2 imageMax = imageMin + imageSize; + + ImGui::GetWindowDrawList()->AddImage(self->resources->atlas.id, imageMin, imageMax, IMVEC2_ATLAS_UV_GET(item.texture), IM_COL32_WHITE); } else - ImGui::Dummy(IMGUI_DUMMY_SIZE); + isActivated = ImGui::ImageButton(item.label.c_str(), self->resources->atlas.id, buttonSize, IMVEC2_ATLAS_UV_GET(item.texture)); + _imgui_item(self, item, &isActivated); + + if (item.color.is_normal()) ImGui::PopStyleColor(); + if (item.color.is_active()) ImGui::PopStyleColor(); + if (item.color.is_hovered()) ImGui::PopStyleColor(); + if (item.isSelected) ImGui::PopStyleColor(); + if (item.is_border()) ImGui::PopStyleVar(); - (*index)++; - - ImGui::PopID(); + return isActivated; } -// Displays each item of the timeline of a selected animation -static void -_imgui_timeline_item(Imgui* self, Anm2Reference reference, s32* index) +static void _imgui_item_begin(const ImguiItem& item) { - static s32 textEntryItemIndex = -1; - static s32 textEntrySpritesheetIndex = -1; - - TextureType textureType = TEXTURE_ERROR; - s32* spritesheetID = NULL; - bool* isShowRect = NULL; - Anm2Null* null = NULL; - Anm2Layer* layer = NULL; - std::string nameVisible; - std::string* namePointer = NULL; - char nameBuffer[ANM2_STRING_MAX]; - memset(nameBuffer, '\0', sizeof(nameBuffer)); - - bool isChangeable = reference.itemType != ANM2_ROOT && reference.itemType != ANM2_TRIGGERS; - bool isSelected = self->reference->itemID == reference.itemID && self->reference->itemType == reference.itemType; - bool isTextEntry = textEntryItemIndex == *index && isChangeable; - bool isSpritesheetTextEntry = textEntrySpritesheetIndex == *index; - - f32 cursorPosY = ImGui::GetCursorPosY(); - ImVec4 color; - - Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); - - if (!item) - return; - - switch (reference.itemType) - { - case ANM2_ROOT: - textureType = TEXTURE_ROOT; - color = IMGUI_TIMELINE_ROOT_COLOR; - nameVisible = STRING_IMGUI_TIMELINE_ROOT; - break; - case ANM2_LAYER: - textureType = TEXTURE_LAYER; - color = IMGUI_TIMELINE_LAYER_COLOR; - layer = &self->anm2->layers[reference.itemID]; - spritesheetID = &layer->spritesheetID; - namePointer = &layer->name; - strncpy(nameBuffer, (*namePointer).c_str(), ANM2_STRING_MAX - 1); - nameVisible = std::format(STRING_IMGUI_TIMELINE_ITEM_FORMAT, reference.itemID, *namePointer); - break; - case ANM2_NULL: - textureType = TEXTURE_NULL; - color = IMGUI_TIMELINE_NULL_COLOR; - null = &self->anm2->nulls[reference.itemID]; - isShowRect = &null->isShowRect; - namePointer = &null->name; - strncpy(nameBuffer, (*namePointer).c_str(), ANM2_STRING_MAX - 1); - nameVisible = std::format(STRING_IMGUI_TIMELINE_ITEM_FORMAT, reference.itemID, *namePointer); - break; - case ANM2_TRIGGERS: - textureType = TEXTURE_TRIGGERS; - color = IMGUI_TIMELINE_TRIGGERS_COLOR; - nameVisible = STRING_IMGUI_TIMELINE_TRIGGERS; - break; - default: - break; - } - - ImGui::PushID(*index); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, color); - ImGui::BeginChild(nameVisible.c_str(), IMGUI_TIMELINE_ELEMENT_SIZE, true, ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar); - ImGui::PopStyleColor(); - - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); - - ImGui::SameLine(); - - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_NAME_LABEL, IMGUI_TIMELINE_ELEMENT_NAME_SIZE); - - if (isTextEntry) - { - if (ImGui::InputText(STRING_IMGUI_TIMELINE_ANIMATION_LABEL, nameBuffer, ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - { - *namePointer = nameBuffer; - textEntryItemIndex = -1; - } - _imgui_undoable(self); - - if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - textEntryItemIndex = -1; - } - else - { - if (ImGui::Selectable(nameVisible.c_str(), isSelected)) - { - *self->reference = reference; - anm2_reference_frame_clear(self->reference); - } - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - textEntryItemIndex = *index; - } - - // Drag and drop items (only layers/nulls) - if (isChangeable && ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - *self->reference = reference; - anm2_reference_frame_clear(self->reference); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, color); - ImGui::SetDragDropPayload(STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP, &reference, sizeof(Anm2Frame)); - ImGui::PopStyleColor(); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType)); - ImGui::SameLine(); - ImGui::Text(nameVisible.c_str()); - ImGui::EndDragDropSource(); - } - - if (self->reference->itemType == reference.itemType && ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_TIMELINE_ITEM_DRAG_DROP)) - { - Anm2Reference checkReference = *(Anm2Reference*)payload->Data; - if (checkReference != reference) - { - self->isSwap = true; - self->swapReference = reference; - - _imgui_undo_stack_push(self); - } - } - ImGui::EndDragDropTarget(); - } - - switch (reference.itemType) - { - case ANM2_ROOT: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ROOT); - break; - case ANM2_LAYER: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_LAYER); - break; - case ANM2_NULL: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_NULL); - break; - case ANM2_TRIGGERS: - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_TRIGGERS); - break; - default: - break; - } - - ImGui::EndChild(); - - // IsVisible - ImVec2 cursorPos; - TextureType visibleTextureType = item->isVisible ? TEXTURE_VISIBLE : TEXTURE_INVISIBLE; - - ImGui::SameLine(); - - cursorPos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(cursorPos.x + ImGui::GetContentRegionAvail().x - IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2); - - if - ( - ImGui::ImageButton - ( - STRING_IMGUI_TIMELINE_VISIBLE, - self->resources->atlas.id, - VEC2_TO_IMVEC2(ATLAS_SIZES[visibleTextureType]), - IMVEC2_ATLAS_UV_GET(visibleTextureType) - ) - ) - item->isVisible = !item->isVisible; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_VISIBLE); - - ImGui::SetCursorPos(cursorPos); - - // Spritesheet IDs - if (spritesheetID) - { - std::string spritesheetIDName; - - spritesheetIDName = std::format(STRING_IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT, *spritesheetID); - - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE); - - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); - ImGui::SameLine(); - - if (isSpritesheetTextEntry) - { - if (ImGui::InputInt(STRING_IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_LABEL, spritesheetID, 0, 0, ImGuiInputTextFlags_None)) - textEntrySpritesheetIndex = -1; - _imgui_undoable(self); - - if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - textEntrySpritesheetIndex = -1; - } - else - { - ImGui::Selectable(spritesheetIDName.c_str()); - - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - textEntrySpritesheetIndex = *index; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_SPRITESHEET); - - ImGui::EndChild(); - } - - // ShowRect - if (isShowRect) - { - TextureType rectTextureType = *isShowRect ? TEXTURE_RECT : TEXTURE_RECT_HIDE; - - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - ((IMGUI_ICON_BUTTON_SIZE.x - ImGui::GetStyle().FramePadding.x * 2) * 4)); - - if (ImGui::ImageButton(STRING_IMGUI_TIMELINE_RECT, self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[rectTextureType]), IMVEC2_ATLAS_UV_GET(rectTextureType))) - *isShowRect = !*isShowRect; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_RECT); - } - - ImGui::EndChild(); - - (*index)++; - - ImGui::PopID(); - - ImGui::SetCursorPosY(cursorPosY + IMGUI_TIMELINE_ELEMENT_SIZE.y); + if (item.is_size()) ImGui::SetNextWindowSize(item.size); + ImGui::Begin(item.label.c_str(), nullptr, item.flags); } -// Timeline window -static void -_imgui_timeline(Imgui* self) +static void _imgui_item_end(void) { - ImGui::Begin(STRING_IMGUI_TIMELINE); + ImGui::End(); +} +static void _imgui_item_begin_child(const ImguiItem& item) +{ + if (item.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_ChildBg, item.color.normal); + ImGui::BeginChild(item.label.c_str(), item.size, item.flags, item.flagsAlt); + if (item.color.is_normal()) ImGui::PopStyleColor(); +} + +static void _imgui_item_end_child(void) +{ + ImGui::EndChild(); +} + +static void _imgui_item_dockspace(const ImguiItem& item) +{ + ImGui::DockSpace(ImGui::GetID(item.label.c_str()), item.size, item.flags); +} + +static void _imgui_keyboard_navigation_set(bool value) +{ + if (value) ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + if (!value) ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; +} + +static bool _imgui_item_yes_no_popup(Imgui* self, const ImguiItem& item) +{ + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + + if (ImGui::BeginPopupModal(item.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text(item.label.c_str()); + ImGui::Separator(); + + if (_imgui_item_button(self, IMGUI_POPUP_YES_BUTTON)) + { + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + return true; + } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_POPUP_NO_BUTTON)) + ImGui::CloseCurrentPopup(); + + ImGui::EndPopup(); + } + + return false; +} + +static void _imgui_spritesheet_editor_set(Imgui* self, s32 id) +{ + if (self->anm2->spritesheets.contains(id)) self->editor->spritesheetID = id; +} + +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 textColor = ImGui::GetColorU32(ImGuiCol_Text); + static ImVec2 scroll{}; + static ImVec2 pickerPos{}; + static Anm2Reference swapItemReference; + static bool isItemSwap = false; + static ImVec2 itemMin{}; + static ImVec2 mousePos{}; + static ImVec2 localMousePos{}; + static s32 frameIndex = INDEX_NONE; + static Anm2Reference hoverReference; + + static const ImVec2& frameSize = IMGUI_TIMELINE_FRAME_SIZE; + + _imgui_item_begin(IMGUI_TIMELINE); + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - if (animation) + if (!animation) { - ImVec2 cursorPos; - ImVec2 mousePos; - ImVec2 mousePosRelative; - s32 index = 0; - ImVec2 frameSize = IMGUI_TIMELINE_FRAME_SIZE; - ImVec2 pickerPos; - ImVec2 lineStart; - ImVec2 lineEnd; - ImDrawList* drawList; - static f32 itemScrollX = 0; - static f32 itemScrollY = 0; - static bool isPickerDragging; - ImVec2 frameIndicesSize = {frameSize.x * animation->frameNum, IMGUI_TIMELINE_FRAME_INDICES_SIZE.y}; - const char* buttonText = self->preview->isPlaying ? STRING_IMGUI_TIMELINE_PAUSE : STRING_IMGUI_TIMELINE_PLAY; - const char* buttonTooltipText = self->preview->isPlaying ? STRING_IMGUI_TOOLTIP_TIMELINE_PAUSE : STRING_IMGUI_TOOLTIP_TIMELINE_PLAY; - ImVec2 region = ImGui::GetContentRegionAvail(); - ImVec2 windowSize; - s32& animationID = self->reference->animationID; + ImGui::Text(IMGUI_TIMELINE_NO_ANIMATION); + _imgui_item_end(); // IMGUI_TIMELINE + return; + } - ImVec2 timelineSize = {region.x, region.y - IMGUI_TIMELINE_OFFSET_Y}; - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_CHILD, timelineSize, true); - windowSize = ImGui::GetWindowSize(); + ImVec2 defaultItemSpacing = ImGui::GetStyle().ItemSpacing; + ImVec2 defaultWindowPadding = ImGui::GetStyle().WindowPadding; + ImVec2 defaultFramePadding = ImGui::GetStyle().FramePadding; - cursorPos = ImGui::GetCursorPos(); + ImguiItem timelineChild = IMGUI_TIMELINE_CHILD; + timelineChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_TIMELINE_FOOTER_HEIGHT; - drawList = ImGui::GetWindowDrawList(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + + _imgui_item_begin_child(timelineChild); + ImVec2 clipRectMin = ImGui::GetWindowDrawList()->GetClipRectMin(); + ImVec2 clipRectMax = ImGui::GetWindowDrawList()->GetClipRectMax(); + clipRectMin.x += IMGUI_TIMELINE_ITEM_SIZE.x; - ImGui::SetCursorPos(ImVec2(cursorPos.x + IMGUI_TIMELINE_ELEMENT_SIZE.x, cursorPos.y + IMGUI_TIMELINE_VIEWER_SIZE.y)); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_FRAMES, {0, 0}, false, ImGuiWindowFlags_HorizontalScrollbar); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - itemScrollX = ImGui::GetScrollX(); - itemScrollY = ImGui::GetScrollY(); + ImVec2 scrollDelta = {0, 0}; + + if (_imgui_is_window_hovered()) + { + ImGuiIO& io = ImGui::GetIO(); + f32 lineHeight = ImGui::GetTextLineHeight(); - // Root - _imgui_timeline_item_frames(self, Anm2Reference{animationID, ANM2_ROOT, 0, 0}, &index); + scrollDelta.x -= io.MouseWheelH * lineHeight; + scrollDelta.y -= io.MouseWheel * lineHeight * 3.0f; + } - // Layers (Reversed) - for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) + std::function timeline_header = [&]() + { + static bool isHeaderClicked = false; + _imgui_item_begin_child(IMGUI_TIMELINE_HEADER); + + ImGui::SetScrollX(scroll.x); + + itemMin = ImGui::GetItemRectMin(); + mousePos = ImGui::GetMousePos(); + localMousePos = ImVec2(mousePos.x - itemMin.x + scroll.x, mousePos.y - itemMin.y); + frameIndex = CLAMP((s32)(localMousePos.x / frameSize.x), 0, (f32)(animation->frameNum - 1)); + + if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && _imgui_is_window_hovered()) + isHeaderClicked = true; + + if (isHeaderClicked) { - s32 id = it->first; - _imgui_timeline_item_frames(self, Anm2Reference{animationID, ANM2_LAYER, id, 0}, &index); + self->preview->time = frameIndex; + + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + isHeaderClicked = false; } - // Nulls - for (auto & [id, null] : animation->nullAnimations) - _imgui_timeline_item_frames(self, Anm2Reference{animationID, ANM2_NULL, id, 0}, &index); - - // Triggers - _imgui_timeline_item_frames(self, Anm2Reference{animationID, ANM2_TRIGGERS, 0, 0}, &index); - - // Swap the two selected elements - if (self->isSwap) + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); + pickerPos = {cursorPos.x + (self->preview->time * frameSize.x), cursorPos.y}; + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + for (s32 i = 0; i < animation->frameNum; i++) { - Anm2Frame* aFrame = anm2_frame_from_reference(self->anm2, self->reference); - Anm2Frame* bFrame = anm2_frame_from_reference(self->anm2, &self->swapReference); - Anm2Frame oldFrame = *aFrame; + bool isMultiple = i % IMGUI_TIMELINE_FRAME_MULTIPLE == 0; + + ImVec2 framePos = ImGui::GetCursorScreenPos(); + ImVec2 bgMin = framePos; + ImVec2 bgMax = {framePos.x + frameSize.x, framePos.y + frameSize.y}; + ImU32 bgColor = isMultiple ? headerFrameMultipleColor : headerFrameColor; - // With triggers, just swap the event Id - if (self->reference->itemType == ANM2_TRIGGERS) + drawList->AddRectFilled(bgMin, bgMax, bgColor); + + if (i % IMGUI_TIMELINE_FRAME_MULTIPLE == 0) { - aFrame->eventID = bFrame->eventID; - bFrame->eventID = oldFrame.eventID; + std::string frameIndexString = std::to_string(i); + ImVec2 textSize = ImGui::CalcTextSize(frameIndexString.c_str()); + ImVec2 textPos = {framePos.x + (frameSize.x - textSize.x) * 0.5f, framePos.y + (frameSize.y - textSize.y) * 0.5f}; + drawList->AddText(textPos, textColor, frameIndexString.c_str()); } - else - { - *aFrame = *bFrame; - *bFrame = oldFrame; - } - - self->isSwap = false; - self->reference->frameIndex = self->swapReference.frameIndex; - self->swapReference = Anm2Reference{}; - } - - ImGui::EndChild(); - - ImGui::SetCursorPos(cursorPos); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, IMGUI_TIMELINE_HEADER_COLOR); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_HEADER, IMGUI_TIMELINE_ELEMENT_SIZE, true); - ImGui::EndChild(); - ImGui::PopStyleColor(); - - // Only draw if animation length isn't 0 - if (animation->frameNum > 0) - { - bool isMouseInElementsRegion = false; - - ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); - ImVec2 clipRectMin = {cursorScreenPos.x + IMGUI_TIMELINE_ELEMENT_SIZE.x, 0}; - ImVec2 clipRectMax = {cursorScreenPos.x + timelineSize.x + IMGUI_TIMELINE_FRAME_SIZE.x, cursorScreenPos.y + timelineSize.y}; - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - - ImGui::SameLine(); + + _imgui_atlas_image(self, TEXTURE_FRAME); - ImGui::PushClipRect(clipRectMin, clipRectMax, true); - - ImGui::BeginChild(STRING_IMGUI_TIMELINE_FRAME_INDICES, {0, IMGUI_TIMELINE_FRAME_SIZE.y}); - ImGui::SetScrollX(itemScrollX); - - ImVec2 itemsRectMin = ImGui::GetWindowPos(); - ImVec2 itemsRectMax = ImVec2(itemsRectMin.x + frameIndicesSize.x, itemsRectMin.y + frameIndicesSize.y); - - cursorPos = ImGui::GetCursorScreenPos(); - mousePos = ImGui::GetMousePos(); - mousePosRelative = ImVec2(ImGui::GetMousePos().x - cursorPos.x, ImGui::GetMousePos().y - cursorPos.y); - - isMouseInElementsRegion = - mousePos.x >= itemsRectMin.x && mousePos.x < itemsRectMax.x && - mousePos.y >= itemsRectMin.y && mousePos.y < itemsRectMax.y; - - // Dragging/undragging the frame picker - if ((isMouseInElementsRegion && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) || isPickerDragging) - { - s32 frameIndex = CLAMP((s32)(mousePosRelative.x / frameSize.x), 0, (f32)(animation->frameNum - 1)); - *self->time = frameIndex; - - isPickerDragging = true; - } - - if (isPickerDragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - isPickerDragging = false; - - // Draw the frame indices, based on the animation length - for (s32 i = 0; i < animation->frameNum; i++) - { - ImVec2 imagePos = ImGui::GetCursorScreenPos(); - - if (i % IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE == 0) - { - std::string frameIndexString; - frameIndexString = std::to_string(i); - - ImVec2 bgMin = imagePos; - ImVec2 bgMax = ImVec2(imagePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, - imagePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); - - ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_INDICES_OVERLAY_COLOR); - drawList->AddRectFilled(bgMin, bgMax, bgColor); - - ImVec2 textSize = ImGui::CalcTextSize(frameIndexString.c_str()); - - ImVec2 textPos; - textPos.x = imagePos.x + (IMGUI_TIMELINE_FRAME_SIZE.x - textSize.x) / 2.0f; - textPos.y = imagePos.y + (IMGUI_TIMELINE_FRAME_SIZE.y - textSize.y) / 2.0f; - - drawList->AddText(textPos, ImGui::GetColorU32(ImGuiCol_Text), frameIndexString.c_str()); - } - else - { - ImVec2 bgMin = imagePos; - ImVec2 bgMax = ImVec2(imagePos.x + IMGUI_TIMELINE_FRAME_SIZE.x, - imagePos.y + IMGUI_TIMELINE_FRAME_SIZE.y); - - ImU32 bgColor = ImGui::GetColorU32(IMGUI_FRAME_INDICES_COLOR); - drawList->AddRectFilled(bgMin, bgMax, bgColor); - } - - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_FRAME]), IMVEC2_ATLAS_UV_GET(TEXTURE_FRAME)); - + if (i < animation->frameNum - 1) ImGui::SameLine(); - } + } - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); + ImVec2& pos = pickerPos; + + ImDrawList* foregroundDrawList = ImGui::GetForegroundDrawList(); + + ImVec2 lineStart = {pos.x + (frameSize.x * 0.5f) - (IMGUI_TIMELINE_PICKER_LINE_WIDTH * 0.5f), pos.y + frameSize.y}; + ImVec2 lineEnd = {lineStart.x + IMGUI_TIMELINE_PICKER_LINE_WIDTH, lineStart.y + timelineChild.size.y - frameSize.y}; + + foregroundDrawList->PushClipRect(clipRectMin, clipRectMax, true); - pickerPos = ImVec2(cursorPos.x + *self->time * frameSize.x, cursorPos.y); - lineStart = ImVec2(pickerPos.x + frameSize.x / 2.0f, pickerPos.y + frameSize.y); - lineEnd = ImVec2(lineStart.x, lineStart.y + timelineSize.y - IMGUI_TIMELINE_FRAME_SIZE.y); + foregroundDrawList->AddImage(self->resources->atlas.id, pos, ImVec2(pos.x + frameSize.x, pos.y + frameSize.y), IMVEC2_ATLAS_UV_GET(TEXTURE_PICKER)); + foregroundDrawList->AddRectFilled(lineStart, lineEnd, IMGUI_PICKER_LINE_COLOR); - ImGui::GetWindowDrawList()->AddImage - ( - self->resources->atlas.id, - pickerPos, - ImVec2(pickerPos.x + frameSize.x, pickerPos.y + frameSize.y), - IMVEC2_ATLAS_UV_GET(TEXTURE_PICKER) - ); + foregroundDrawList->PopClipRect(); + + _imgui_item_end_child(); // IMGUI_TIMELINE_HEADER + }; + + std::function timeline_item_child = [&](Anm2Reference reference, s32* index) + { + Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); + + if (!item) return; + + ImVec2 buttonSize = VEC2_TO_IMVEC2(TEXTURE_SIZE) + (defaultFramePadding * ImVec2(2, 2)); + + Anm2Type& type = reference.itemType; + Anm2Layer* layer = nullptr; + Anm2Null* null = nullptr; + s32 buttonCount = type == ANM2_NULL ? 2 : 1; + f32 buttonAreaWidth = buttonCount * buttonSize.x + (buttonCount - 1) * defaultItemSpacing.x; + + ImguiItem imguiItem = *IMGUI_TIMELINE_ITEMS[type]; + ImguiItem imguiItemSelectable = *IMGUI_TIMELINE_ITEM_SELECTABLES[type]; + imguiItemSelectable.isSelected = self->reference->itemID == reference.itemID && self->reference->itemType == type; + + ImGui::PushID(reference.itemID); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, defaultItemSpacing); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, defaultWindowPadding); - drawList->AddRectFilled - ( - ImVec2(lineStart.x - IMGUI_PICKER_LINE_SIZE, lineStart.y), - ImVec2(lineStart.x + IMGUI_PICKER_LINE_SIZE, lineEnd.y), - IMGUI_PICKER_LINE_COLOR - ); + ImVec2 childPos = ImGui::GetCursorScreenPos(); + _imgui_item_begin_child(imguiItem); + ImVec2 childSize = ImGui::GetContentRegionAvail(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::EndChild(); - ImGui::PopClipRect(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - } - else + switch (type) { - ImGui::SameLine(); - ImGui::Dummy(frameIndicesSize); - } - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); - ImGui::BeginChild(STRING_IMGUI_TIMELINE_ELEMENT_LIST, IMGUI_TIMELINE_ELEMENT_LIST_SIZE, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); - ImGui::SetScrollY(itemScrollY); - - index = 0; - - // Root - _imgui_timeline_item(self, Anm2Reference{animationID, ANM2_ROOT, 0, 0}, &index); - - // Layer - for (auto it = animation->layerAnimations.rbegin(); it != animation->layerAnimations.rend(); it++) - { - s32 id = it->first; - _imgui_timeline_item(self, Anm2Reference{animationID, ANM2_LAYER, id, 0}, &index); - } - - // Null - for (auto & [id, null] : animation->nullAnimations) - _imgui_timeline_item(self, Anm2Reference{animationID, ANM2_NULL, id, 0}, &index); - - // Triggers - _imgui_timeline_item(self, Anm2Reference{animationID, ANM2_TRIGGERS, 0, 0}, &index); - - // Swap the drag/drop elements - if (self->isSwap) - { - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - switch (self->reference->itemType) - { - case ANM2_LAYER: - map_swap(self->anm2->layers, self->reference->itemID, self->swapReference.itemID); - map_swap(animation->layerAnimations, self->reference->itemID, self->swapReference.itemID); - break; - case ANM2_NULL: - map_swap(self->anm2->nulls, self->reference->itemID, self->swapReference.itemID); - map_swap(animation->nullAnimations, self->reference->itemID, self->swapReference.itemID); - break; - default: - break; - } - - self->isSwap = false; - self->reference->itemID = self->swapReference.itemID; - anm2_reference_clear(&self->swapReference); - } - - ImGui::EndChild(); - ImGui::EndChild(); - - // Add Element - if (ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_ADD)) - ImGui::OpenPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_ADD); - - if (ImGui::BeginPopup(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU)) - { - if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_LAYER)) - anm2_layer_add(self->anm2); - _imgui_undoable(self); - - if (ImGui::Selectable(STRING_IMGUI_TIMELINE_ELEMENT_ADD_MENU_NULL)) - anm2_null_add(self->anm2); - _imgui_undoable(self); - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - - // Remove Element - if (ImGui::Button(STRING_IMGUI_TIMELINE_ELEMENT_REMOVE)) - { - _imgui_undo_stack_push(self); - - switch (self->reference->itemType) - { - case ANM2_LAYER: - anm2_layer_remove(self->anm2, self->reference->itemID); - break; - case ANM2_NULL: - anm2_null_remove(self->anm2, self->reference->itemID); - break; - default: - break; - } - - anm2_reference_item_clear(self->reference); - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ELEMENT_REMOVE); - - ImGui::SameLine(); - - // Play/Pause button - if (ImGui::Button(buttonText)) - { - self->preview->isPlaying = !self->preview->isPlaying; - - if ((s32)(*self->time) >= animation->frameNum - 1) - *self->time = 0.0f; - } - _imgui_tooltip(buttonTooltipText); - - ImGui::SameLine(); - - // Add frame - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_ADD)) - anm2_frame_add(self->anm2, self->reference, (s32)*self->time); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_ADD); - - ImGui::SameLine(); - - // Remove Frame - if (ImGui::Button(STRING_IMGUI_TIMELINE_FRAME_REMOVE)) - { - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - - if (frame) - { - _imgui_undo_stack_push(self); - Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); - item->frames.erase(item->frames.begin() + index); - - anm2_reference_frame_clear(self->reference); - } - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FRAME_REMOVE); - - ImGui::SameLine(); - - // Fit Animation Length - if (ImGui::Button(STRING_IMGUI_TIMELINE_FIT_ANIMATION_LENGTH)) - { - s32 length = anm2_animation_length_get(self->anm2, self->reference->animationID); - animation->frameNum = length; - *self->time = CLAMP(*self->time, 0.0f, animation->frameNum - 1); - } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FIT_ANIMATION_LENGTH); - - ImGui::SameLine(); - - // Animation Length - ImGui::SetNextItemWidth(IMGUI_TIMELINE_ANIMATION_LENGTH_WIDTH); - ImGui::InputInt(STRING_IMGUI_TIMELINE_ANIMATION_LENGTH, &animation->frameNum); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_ANIMATION_LENGTH); - - animation->frameNum = CLAMP(animation->frameNum, ANM2_FRAME_NUM_MIN, ANM2_FRAME_NUM_MAX); - - ImGui::SameLine(); - - // FPS - ImGui::SetNextItemWidth(IMGUI_TIMELINE_FPS_WIDTH); - ImGui::SameLine(); - ImGui::InputInt(STRING_IMGUI_TIMELINE_FPS, &self->anm2->fps); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_FPS); - - self->anm2->fps = CLAMP(self->anm2->fps, ANM2_FPS_MIN, ANM2_FPS_MAX); - - ImGui::SameLine(); - - // Loop - ImGui::Checkbox(STRING_IMGUI_TIMELINE_LOOP, &animation->isLoop); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_LOOP); - ImGui::SameLine(); - - // CreatedBy - ImGui::SetNextItemWidth(IMGUI_TIMELINE_CREATED_BY_WIDTH); - ImGui::SameLine(); - ImGui::InputText(STRING_IMGUI_TIMELINE_CREATED_BY, &self->anm2->createdBy[0], ANM2_STRING_MAX); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_TIMELINE_CREATED_BY); - - ImGui::SameLine(); - - // CreatedOn - ImGui::Text(STRING_IMGUI_TIMELINE_CREATED_ON, self->anm2->createdOn.c_str()); - ImGui::SameLine(); - - // Version - ImGui::Text(STRING_IMGUI_TIMELINE_VERSION, self->anm2->version); - } - - ImGui::End(); -} - -// Taskbar -static void -_imgui_taskbar(Imgui* self) -{ - ImGuiWindowFlags taskbarWindowFlags = 0 | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_NoSavedSettings; - - ImGuiViewport* viewport = ImGui::GetMainViewport(); - - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, IMGUI_TASKBAR_HEIGHT)); - - ImGui::Begin(STRING_IMGUI_TASKBAR, NULL, taskbarWindowFlags); - - // File - if (ImGui::Selectable(STRING_IMGUI_TASKBAR_FILE, false, 0, ImGui::CalcTextSize(STRING_IMGUI_TASKBAR_FILE))) - ImGui::OpenPopup(STRING_IMGUI_FILE_MENU); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FILE_MENU); - - if (ImGui::IsItemHovered() || ImGui::IsItemActive()) - ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y)); - - if (ImGui::BeginPopup(STRING_IMGUI_FILE_MENU)) - { - if (ImGui::Selectable(STRING_IMGUI_FILE_NEW)) - { - anm2_reference_clear(self->reference); - anm2_new(self->anm2); - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FILE_NEW); - - if (ImGui::Selectable(STRING_IMGUI_FILE_OPEN)) - dialog_anm2_open(self->dialog); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FILE_OPEN); - - if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE)) - { - // Open dialog if path empty, otherwise save in-place - if (self->anm2->path.empty()) - dialog_anm2_save(self->dialog); - else - anm2_serialize(self->anm2, self->anm2->path); - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FILE_SAVE); - - if (ImGui::Selectable(STRING_IMGUI_FILE_SAVE_AS)) - dialog_anm2_save(self->dialog); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FILE_SAVE_AS); - - ImGui::EndPopup(); - } - - ImGui::SameLine(); - - // Playback - if (ImGui::Selectable(STRING_IMGUI_TASKBAR_PLAYBACK, false, 0, ImGui::CalcTextSize(STRING_IMGUI_TASKBAR_PLAYBACK))) - ImGui::OpenPopup(STRING_IMGUI_PLAYBACK_MENU); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_PLAYBACK_MENU); - - if (ImGui::IsItemHovered() || ImGui::IsItemActive()) - ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y)); - - if (ImGui::BeginPopup(STRING_IMGUI_PLAYBACK_MENU)) - { - ImGui::Checkbox(STRING_IMGUI_PLAYBACK_LOOP, &self->settings->playbackIsLoop); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_PLAYBACK_IS_LOOP); - ImGui::EndPopup(); - } - - ImGui::SameLine(); - - // Wizard - if (ImGui::Selectable(STRING_IMGUI_TASKBAR_WIZARD, false, 0, ImGui::CalcTextSize(STRING_IMGUI_TASKBAR_WIZARD))) - ImGui::OpenPopup(STRING_IMGUI_WIZARD_MENU); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_WIZARD_MENU); - - if (ImGui::IsItemHovered() || ImGui::IsItemActive()) - ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y)); - - if (ImGui::BeginPopup(STRING_IMGUI_WIZARD_MENU)) - { - if (ImGui::Selectable(STRING_IMGUI_WIZARD_EXPORT_FRAMES_TO_PNG)) - { - if (anm2_animation_from_reference(self->anm2, self->reference)) - { - self->preview->isRecording = true; - *self->time = true; - } - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_WIZARD_EXPORT_FRAMES_TO_PNG); - ImGui::EndPopup(); - } - - // Add a persistent tooltip to indicate recording - if (self->preview->isRecording) - { - ImVec2 mousePos = ImGui::GetMousePos(); - - ImGui::SetNextWindowPos(ImVec2(mousePos.x + IMGUI_RECORD_TOOLTIP_OFFSET.x, mousePos.y + IMGUI_RECORD_TOOLTIP_OFFSET.y)); - ImGui::BeginTooltip(); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(TEXTURE_SIZE), IMVEC2_ATLAS_UV_GET(TEXTURE_ANIMATION)); - ImGui::SameLine(); - ImGui::Text(STRING_IMGUI_RECORDING); - ImGui::EndTooltip(); - } - - ImGui::End(); -} - -// Tools -static void -_imgui_tools(Imgui* self) -{ - ImGui::Begin(STRING_IMGUI_TOOLS); - - ImVec2 availableSize = ImGui::GetContentRegionAvail(); - f32 availableWidth = availableSize.x; - - s32 buttonsPerRow = availableWidth / TEXTURE_SIZE.x + IMGUI_TOOLS_WIDTH_INCREMENT; - buttonsPerRow = MIN(buttonsPerRow, 1); - - for (s32 i = 0; i < TOOL_COUNT; i++) - { - const char* toolString; - const char* toolTooltip; - TextureType textureType; - - if (i > 0 && i % buttonsPerRow != 0) - ImGui::SameLine(); - - ImVec4 buttonColor = self->tool->type == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; - ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); - - switch (i) - { - case TOOL_PAN: - toolString = STRING_IMGUI_TOOLS_PAN; - toolTooltip = STRING_IMGUI_TOOLTIP_TOOLS_PAN; - textureType = TEXTURE_PAN; + case ANM2_ROOT: + case ANM2_TRIGGERS: + if (_imgui_item_atlas_image_selectable(self, imguiItemSelectable)) + *self->reference = reference; break; - case TOOL_MOVE: - toolString = STRING_IMGUI_TOOLS_MOVE; - toolTooltip = STRING_IMGUI_TOOLTIP_TOOLS_MOVE; - textureType = TEXTURE_MOVE; + case ANM2_LAYER: + layer = &self->anm2->layers[reference.itemID]; + imguiItemSelectable.label = std::format(IMGUI_TIMELINE_CHILD_ID_LABEL, reference.itemID, layer->name); + if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, &layer->name, *index)) + *self->reference = reference; break; - case TOOL_ROTATE: - toolString = STRING_IMGUI_TOOLS_ROTATE; - toolTooltip = STRING_IMGUI_TOOLTIP_TOOLS_ROTATE; - textureType = TEXTURE_ROTATE; - break; - case TOOL_SCALE: - toolString = STRING_IMGUI_TOOLS_SCALE; - toolTooltip = STRING_IMGUI_TOOLTIP_TOOLS_SCALE; - textureType = TEXTURE_SCALE; - break; - case TOOL_CROP: - toolString = STRING_IMGUI_TOOLS_CROP; - toolTooltip = STRING_IMGUI_TOOLTIP_TOOLS_CROP; - textureType = TEXTURE_CROP; + case ANM2_NULL: + null = &self->anm2->nulls[reference.itemID]; + imguiItemSelectable.label = std::format(IMGUI_TIMELINE_CHILD_ID_LABEL, reference.itemID, null->name); + if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, &null->name, *index)) + *self->reference = reference; break; default: break; } - if (ImGui::ImageButton(toolString, self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[textureType]), IMVEC2_ATLAS_UV_GET(textureType))) - self->tool->type = (ToolType)i; - - _imgui_tooltip(toolTooltip); - - ImGui::PopStyleColor(); - } - - ImGui::End(); - -} - -// Animations -static void -_imgui_animations(Imgui* self) -{ - static s32 textEntryAnimationID = -1; - - ImGui::Begin(STRING_IMGUI_ANIMATIONS); - - // Iterate through all animations, can be selected and names can be edited - for (auto & [id, animation] : self->anm2->animations) - { - std::string name; - bool isSelected = self->reference->animationID == id; - bool isTextEntry = textEntryAnimationID == id; - - // Distinguish default animation - if (animation.name == self->anm2->defaultAnimation) - name = std::format(STRING_IMGUI_ANIMATIONS_DEFAULT_ANIMATION_FORMAT, animation.name); - else - name = animation.name; - - ImGui::PushID(id); - - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_ANIMATION)); - ImGui::SameLine(); - - if (isTextEntry) + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - if (ImGui::InputText(STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL, &animation.name[0], ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - textEntryAnimationID = -1; - _imgui_undoable(self); - - if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - textEntryAnimationID = -1; + *self->reference = reference; + + ImGui::SetDragDropPayload(imguiItemSelectable.dragDrop.c_str(), &reference, sizeof(Anm2Reference)); + timeline_item_child(reference, index); + ImGui::EndDragDropSource(); } - else + + if (ImGui::BeginDragDropTarget()) { - if (ImGui::Selectable(name.c_str(), isSelected)) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(imguiItemSelectable.dragDrop.c_str())) { - self->reference->animationID = id; - anm2_reference_item_clear(self->reference); - self->preview->isPlaying = false; - *self->time = 0.0f; + Anm2Reference checkReference = *(Anm2Reference*)payload->Data; + if (checkReference != reference) + { + swapItemReference = reference; + isItemSwap = true; + } } - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - textEntryAnimationID = id; + ImGui::EndDragDropTarget(); + } - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload(STRING_IMGUI_ANIMATIONS_DRAG_DROP, &id, sizeof(s32)); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_ANIMATION)); + if (type == ANM2_LAYER) + { + ImguiItem spritesheetIDItem = IMGUI_TIMELINE_SPRITESHEET_ID; + spritesheetIDItem.label = std::format(IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT, layer->spritesheetID); + ImGui::SameLine(); + _imgui_item_atlas_image_selectable_inputint(self, spritesheetIDItem, &layer->spritesheetID, *index); + } + + ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); + + if (type == ANM2_NULL) + { + ImguiItem rectItem = null->isShowRect ? IMGUI_TIMELINE_ITEM_SHOW_RECT : IMGUI_TIMELINE_ITEM_HIDE_RECT; + if (_imgui_item_atlas_image_button(self, rectItem)) + null->isShowRect = !null->isShowRect; + + ImGui::SameLine(0.0f, defaultItemSpacing.x); + } + + ImguiItem visibleItem = item->isVisible ? IMGUI_TIMELINE_ITEM_VISIBLE : IMGUI_TIMELINE_ITEM_INVISIBLE; + if (_imgui_item_atlas_image_button(self, visibleItem)) + item->isVisible = !item->isVisible; + + ImGui::PopStyleVar(2); + + _imgui_item_end_child(); // imguiItem + + ImGui::PopID(); + + (*index)++; + }; + + std::function timeline_items_child = [&]() + { + s32 index = 0; + + s32& animationID = self->reference->animationID; + + _imgui_item_begin_child(IMGUI_TIMELINE_ITEMS_CHILD); + ImGui::SetScrollY(scroll.y); + + timeline_item_child({animationID, ANM2_ROOT, ID_NONE, INDEX_NONE}, &index); + + for (auto it = self->anm2->layerMap.rbegin(); it != self->anm2->layerMap.rend(); it++) + timeline_item_child({animationID, ANM2_LAYER, it->second, INDEX_NONE}, &index); + + for (auto & [id, null] : animation->nullAnimations) + timeline_item_child({animationID, ANM2_NULL, id, INDEX_NONE}, &index); + + timeline_item_child({animationID, ANM2_TRIGGERS, ID_NONE, INDEX_NONE}, &index); + + _imgui_item_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD + }; + + std::function timeline_item_frames = [&](Anm2Reference reference, s32* index) + { + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + Anm2Item* item = anm2_item_from_reference(self->anm2, &reference); + Anm2Type& type = reference.itemType; + + ImGui::PushID(*index); + + ImguiItem itemFramesChild = IMGUI_TIMELINE_ITEM_FRAMES_CHILD; + itemFramesChild.size.x = frameSize.x * animation->frameNum; + + _imgui_item_begin_child(itemFramesChild); + + if (_imgui_is_window_hovered()) + { + hoverReference = reference; + hoverReference.frameIndex = frameIndex; + self->clipboard->location = hoverReference; + } + + ImVec2 startPos = ImGui::GetCursorPos(); + + for (s32 i = 0; i < animation->frameNum; i++) + { + bool isMultiple = i % IMGUI_TIMELINE_FRAME_MULTIPLE == 0; + + ImVec2 framePos = ImGui::GetCursorScreenPos(); + ImVec2 bgMin = framePos; + ImVec2 bgMax = {framePos.x + frameSize.x, framePos.y + frameSize.y}; + + ImU32 bgColor = isMultiple ? frameMultipleColor : frameColor; + + drawList->AddRectFilled(bgMin, bgMax, bgColor); + + _imgui_atlas_image(self, TEXTURE_FRAME_ALT); + + if (i < animation->frameNum - 1) ImGui::SameLine(); - ImGui::Text(name.c_str()); - ImGui::EndDragDropSource(); + } + + ImGui::SetCursorPos(startPos); + + std::function timeline_item_frame = [&](s32 i, Anm2Frame& frame) + { + ImGui::PushID(i); + reference.frameIndex = i; + ImguiItem frameButton = *IMGUI_TIMELINE_FRAMES[type]; + ImVec2 framePos = ImGui::GetCursorPos(); + frameButton.texture = frame.isInterpolated ? TEXTURE_INTERPOLATED : TEXTURE_UNINTERPOLATED; + frameButton.size = {frameSize.x * frame.delay, frameSize.y}; + frameButton.isSelected = reference == *self->reference; + + if (type == ANM2_TRIGGERS) + { + framePos.x = startPos.x + (frameSize.x * frame.atFrame); + frameButton.texture = TEXTURE_TRIGGER; + } + + ImGui::SetCursorPos(framePos); + + if (_imgui_item_atlas_image_button(self, frameButton)) + { + *self->reference = reference; + + _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); + } + + if (ImGui::IsItemHovered()) + { + Anm2FrameWithReference frameWithReference = {reference, frame}; + _imgui_clipboard_hovered_item_set(self, frameWithReference); + } + + if (type == ANM2_TRIGGERS) + { + if (ImGui::IsItemActivated()) + imgui_undo_stack_push(self); + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + frame.atFrame = frameIndex; + ImGui::EndDragDropSource(); + } + } + else + { + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(frameButton.dragDrop.c_str(), &reference, sizeof(Anm2Reference)); + timeline_item_frame(i, frame); + ImGui::EndDragDropSource(); + } } if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_ANIMATIONS_DRAG_DROP)) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(frameButton.dragDrop.c_str())) { - s32 sourceID = *(s32*)payload->Data; - if (sourceID != id) + Anm2Reference swapReference = *(Anm2Reference*)payload->Data; + if (swapReference != reference) { - _imgui_undo_stack_push(self); - map_swap(self->anm2->animations, sourceID, id); + imgui_undo_stack_push(self); + + Anm2Frame* swapFrame = anm2_frame_from_reference(self->anm2, &reference); + Anm2Frame* dragFrame = anm2_frame_from_reference(self->anm2, &swapReference); + + if (swapFrame && dragFrame) + { + Anm2Frame oldFrame = *swapFrame; + + *swapFrame = *dragFrame; + *dragFrame = oldFrame; + + *self->reference = swapReference; + } } } ImGui::EndDragDropTarget(); } - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SELECT); + + if (i < (s32)item->frames.size() - 1) + ImGui::SameLine(); + if (_imgui_is_no_click_on_item()) + anm2_reference_frame_clear(self->reference); + + ImGui::PopID(); + }; + + for (auto [i, frame] : std::views::enumerate(item->frames)) + timeline_item_frame(i, frame); + + _imgui_item_end_child(); // itemFramesChild + ImGui::PopID(); + + (*index)++; + }; + + std::function timeline_frames_child = [&]() + { + s32& animationID = self->reference->animationID; + s32 index = 0; + + _imgui_item_begin_child(IMGUI_TIMELINE_FRAMES_CHILD); + scroll.x = ImGui::GetScrollX() + scrollDelta.x; + scroll.y = ImGui::GetScrollY() + scrollDelta.y; + ImGui::SetScrollX(scroll.x); + ImGui::SetScrollY(scroll.y); + + timeline_item_frames({animationID, ANM2_ROOT, ID_NONE, INDEX_NONE}, &index); + + for (auto it = self->anm2->layerMap.rbegin(); it != self->anm2->layerMap.rend(); it++) + timeline_item_frames({animationID, ANM2_LAYER, it->second, INDEX_NONE}, &index); + + for (auto & [id, null] : animation->nullAnimations) + timeline_item_frames({animationID, ANM2_NULL, id, INDEX_NONE}, &index); + + timeline_item_frames({animationID, ANM2_TRIGGERS, ID_NONE, INDEX_NONE}, &index); + + _imgui_item_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(0, 0)); + + _imgui_item_begin_child(IMGUI_TIMELINE_ITEM); + _imgui_item_end_child(); // IMGUI_TIMELINE_ITEM + ImGui::SameLine(); + + timeline_header(); + timeline_items_child(); + + ImGui::PopStyleVar(2); + + _imgui_item_end_child(); // IMGUI_TIMELINE_CHILD + + if (isItemSwap) + { + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + + switch (swapItemReference.itemType) + { + case ANM2_LAYER: + map_swap(self->anm2->layers, self->reference->itemID, swapItemReference.itemID); + map_swap(animation->layerAnimations, self->reference->itemID, swapItemReference.itemID); + break; + case ANM2_NULL: + map_swap(self->anm2->nulls, self->reference->itemID, swapItemReference.itemID); + map_swap(animation->nullAnimations, self->reference->itemID, swapItemReference.itemID); + break; + default: + break; + } + + self->reference->itemID = swapItemReference.itemID; + anm2_reference_clear(&swapItemReference); + isItemSwap = false; + } + + if(_imgui_item_button(self, IMGUI_TIMELINE_ADD_ELEMENT)) + ImGui::OpenPopup(IMGUI_TIMELINE_ADD_ELEMENT_POPUP); + + if (ImGui::BeginPopup(IMGUI_TIMELINE_ADD_ELEMENT_POPUP)) + { + if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ELEMENT_LAYER)) + anm2_layer_add(self->anm2); + + if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ELEMENT_NULL)) + anm2_null_add(self->anm2); + + ImGui::EndPopup(); + } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_ELEMENT)) + { + switch (self->reference->itemType) + { + case ANM2_LAYER: + anm2_layer_remove(self->anm2, self->reference->itemID); + break; + case ANM2_NULL: + anm2_null_remove(self->anm2, self->reference->itemID); + break; + default: + break; + } + + anm2_reference_item_clear(self->reference); } - // Add - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_ADD)) + ImGui::SameLine(); + + ImguiItem playPauseItem = self->preview->isPlaying ? IMGUI_TIMELINE_PAUSE : IMGUI_TIMELINE_PLAY; + if (_imgui_item_button(self, playPauseItem)) + self->preview->isPlaying = !self->preview->isPlaying; + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_TIMELINE_ADD_FRAME)) + anm2_frame_add(self->anm2, self->reference, (s32)self->preview->time); + + ImGui::SameLine(); + + if(_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_FRAME)) { - bool isDefault = (s32)self->anm2->animations.size() == 0; // First animation is default automatically + imgui_undo_stack_push(self); + if (anm2_frame_from_reference(self->anm2, self->reference)) + { + Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); + item->frames.erase(item->frames.begin() + self->reference->frameIndex); + anm2_reference_frame_clear(self->reference); + } + } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_TIMELINE_FIT_ANIMATION_LENGTH)) + anm2_animation_length_set(animation); + + ImGui::SameLine(); + _imgui_item_inputint(self, IMGUI_TIMELINE_ANIMATION_LENGTH, &animation->frameNum); + ImGui::SameLine(); + _imgui_item_inputint(self, IMGUI_TIMELINE_FPS, &self->anm2->fps); + ImGui::SameLine(); + _imgui_item_checkbox(self, IMGUI_TIMELINE_LOOP, &animation->isLoop); + ImGui::SameLine(); + _imgui_item_inputtext(self, IMGUI_TIMELINE_CREATED_BY, &self->anm2->createdBy); + ImGui::SameLine(); + _imgui_text_string(IMGUI_TIMELINE_CREATED_ON.label + self->anm2->createdOn); + ImGui::SameLine(); + _imgui_text_string(IMGUI_TIMELINE_VERSION.label + std::to_string(self->anm2->version)); + + if (_imgui_is_no_click_on_item()) + anm2_reference_item_clear(self->reference); + + _imgui_item_end(); // IMGUI_TIMELINE + + self->preview->time = CLAMP(self->preview->time, 0.0f, animation->frameNum - 1); +} + +static void _imgui_taskbar(Imgui* self) +{ + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImguiItem taskbarItem = IMGUI_TASKBAR; + taskbarItem.size = {viewport->Size.x, IMGUI_TASKBAR.size.y}; + ImGui::SetNextWindowPos(viewport->Pos); + _imgui_item_begin(taskbarItem); + + _imgui_item_selectable(self, IMGUI_TASKBAR_FILE); + + if (ImGui::BeginPopup(IMGUI_FILE_POPUP)) + { + _imgui_item_selectable(self, IMGUI_FILE_NEW); + _imgui_item_selectable(self, IMGUI_FILE_OPEN); + _imgui_item_selectable(self, IMGUI_FILE_SAVE); + _imgui_item_selectable(self, IMGUI_FILE_SAVE_AS); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + + _imgui_item_selectable(self, IMGUI_TASKBAR_PLAYBACK); + + if (ImGui::BeginPopup(IMGUI_PLAYBACK_POPUP)) + { + _imgui_item_checkbox(self, IMGUI_PLAYBACK_ALWAYS_LOOP, &self->settings->playbackIsLoop); + ImGui::EndPopup(); + } + + ImGui::SameLine(); + + _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD); + + if (ImGui::BeginPopup(IMGUI_WIZARD_POPUP)) + { + _imgui_item_selectable(self, IMGUI_WIZARD_RECORD_GIF_ANIMATION); + ImGui::EndPopup(); + } + + _imgui_item_end(); +} + +static void _imgui_tools(Imgui* self) +{ + _imgui_item_begin(IMGUI_TOOLS); + + f32 availableWidth = ImGui::GetContentRegionAvail().x; + f32 usedWidth = ImGui::GetStyle().FramePadding.x; + + for (s32 i = 0; i < TOOL_COUNT; i++) + { + const ImguiItem item = *IMGUI_TOOL_ITEMS[i]; + + if (i > 0 && usedWidth < availableWidth) + ImGui::SameLine(); + else + usedWidth = 0; + + if (i != TOOL_COLOR) + { + ImVec4 buttonColor = self->tool == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; + ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); + + _imgui_item_atlas_image_button(self, item); + + ImGui::PopStyleColor(); + } + else + _imgui_item_coloredit4(self, IMGUI_TOOL_COLOR, &self->settings->toolColorR); + + usedWidth += ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x; + } + + _imgui_item_end(); // IMGUI_TOOLS +} + +static void _imgui_animations(Imgui* self) +{ + _imgui_item_begin(IMGUI_ANIMATIONS); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); + + std::function animation_item = [&](s32 id, Anm2Animation& animation) + { + ImGui::PushID(id); + + ImguiItem animationItem = IMGUI_ANIMATION; + animationItem.isSelected = self->reference->animationID == id; + animationItem.size.x = windowSize.x; + + if (animation.name == self->anm2->defaultAnimation) + animationItem.label = std::format(IMGUI_ANIMATION_DEFAULT_FORMAT, animation.name); + else + animationItem.label = animation.name; + + if (_imgui_item_atlas_image_selectable_inputtext(self, animationItem, &animation.name, id)) + { + self->reference->animationID = id; + anm2_reference_item_clear(self->reference); + self->preview->isPlaying = false; + self->preview->time = 0.0f; + } + + if (ImGui::IsItemHovered()) + { + Anm2AnimationWithID animationWithID = {id, animation}; + _imgui_clipboard_hovered_item_set(self, animationWithID); + self->clipboard->location = (s32)id; + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(animationItem.dragDrop.c_str(), &id, sizeof(s32)); + animation_item(id, animation); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(animationItem.dragDrop.c_str())) + { + s32 sourceID = *(s32*)payload->Data; + if (sourceID != id) + { + imgui_undo_stack_push(self); + map_swap(self->anm2->animations, sourceID, id); + } + } + + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + }; + + for (auto & [id, animation] : self->anm2->animations) + animation_item(id, animation); + + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + + if (_imgui_item_button(self, IMGUI_ANIMATION_ADD)) + { + bool isDefault = (s32)self->anm2->animations.size() == 0; s32 id = anm2_animation_add(self->anm2); self->reference->animationID = id; @@ -1135,192 +1142,195 @@ _imgui_animations(Imgui* self) if (isDefault) self->anm2->defaultAnimation = self->anm2->animations[id].name; } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_ADD); ImGui::SameLine(); - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - - if (animation) + if (_imgui_item_button(self, IMGUI_ANIMATION_DUPLICATE) && animation) { - // Remove - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_REMOVE)) - { - anm2_animation_remove(self->anm2, self->reference->animationID); - anm2_reference_clear(self->reference); - } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_REMOVE); - - ImGui::SameLine(); - - // Duplicate - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_DUPLICATE)) - { - s32 id = map_next_id_get(self->anm2->animations); - self->anm2->animations.insert({id, *animation}); - self->reference->animationID = id; - } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_DUPLICATE); - - ImGui::SameLine(); - - // Set as default - if (ImGui::Button(STRING_IMGUI_ANIMATIONS_SET_AS_DEFAULT)) - self->anm2->defaultAnimation = animation->name; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATIONS_SET_AS_DEFAULT); + s32 id = map_next_id_get(self->anm2->animations); + self->anm2->animations.insert({id, *animation}); + self->reference->animationID = id; } - if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - anm2_reference_clear(self->reference); + ImGui::SameLine(); - ImGui::End(); -} + _imgui_item_button(self, IMGUI_ANIMATION_MERGE); -// Events -static void -_imgui_events(Imgui* self) -{ - static s32 selectedEventID = -1; - static s32 textEntryEventID = -1; - - ImGui::Begin(STRING_IMGUI_EVENTS); - - // Iterate through all events - for (auto & [id, event] : self->anm2->events) + if (ImGui::BeginPopupModal(IMGUI_MERGE_POPUP, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - std::string eventString; - bool isSelected = selectedEventID == id; - bool isTextEntry = textEntryEventID == id; - - eventString = std::format(STRING_IMGUI_EVENT_FORMAT, id, event.name); + static s32 selectedRadioButton = ID_NONE; + static bool isDeleteAnimationsAfter = false; - ImGui::PushID(id); + _imgui_item_begin_child(IMGUI_MERGE_ANIMATIONS_CHILD); + + for (auto& [id, animation] : self->anm2->animations) + animation_item(id, animation); + + _imgui_item_end_child(); //IMGUI_MERGE_ANIMATIONS_CHILD + + _imgui_item_begin_child(IMGUI_MERGE_ON_CONFLICT_CHILD); + + _imgui_item_text(IMGUI_MERGE_ON_CONFLICT); + + _imgui_item_radio_button(self, IMGUI_MERGE_APPEND_FRAMES, &selectedRadioButton); + ImGui::SameLine(); + _imgui_item_radio_button(self, IMGUI_MERGE_REPLACE_FRAMES, &selectedRadioButton); + _imgui_item_radio_button(self, IMGUI_MERGE_PREPEND_FRAMES, &selectedRadioButton); + ImGui::SameLine(); + _imgui_item_radio_button(self, IMGUI_MERGE_IGNORE, &selectedRadioButton); + + _imgui_item_end_child(); //IMGUI_MERGE_ON_CONFLICT_CHILD + + _imgui_item_begin_child(IMGUI_MERGE_OPTIONS_CHILD); + + _imgui_item_checkbox(self, IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, &isDeleteAnimationsAfter); + + _imgui_item_end_child(); //IMGUI_MERGE_OPTIONS_CHILD + + if (_imgui_item_button(self, IMGUI_MERGE_CONFIRM)) + ImGui::CloseCurrentPopup(); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_EVENT]), IMVEC2_ATLAS_UV_GET(TEXTURE_EVENT)); ImGui::SameLine(); - if (isTextEntry) - { - if (ImGui::InputText(STRING_IMGUI_ANIMATIONS_ANIMATION_LABEL, &event.name[0], ANM2_STRING_MAX, ImGuiInputTextFlags_EnterReturnsTrue)) - { - selectedEventID = -1; - textEntryEventID = -1; - } - _imgui_undoable(self); + if (_imgui_item_button(self, IMGUI_MERGE_CANCEL)) + ImGui::CloseCurrentPopup(); - if (!ImGui::IsItemHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) - textEntryEventID = -1; - } - else - { - if (ImGui::Selectable(eventString.c_str(), isSelected)) - selectedEventID = id; + ImGui::EndPopup(); + } - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) - textEntryEventID = id; - - if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) - { - ImGui::SetDragDropPayload(STRING_IMGUI_EVENTS_DRAG_DROP, &id, sizeof(s32)); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_ANIMATION]), IMVEC2_ATLAS_UV_GET(TEXTURE_EVENT)); - ImGui::SameLine(); - ImGui::Text(eventString.c_str()); - ImGui::EndDragDropSource(); - } - - if (ImGui::BeginDragDropTarget()) - { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_EVENTS_DRAG_DROP)) - { - s32 sourceID = *(s32*)payload->Data; - if (sourceID != id) - { - _imgui_undo_stack_push(self); - map_swap(self->anm2->events, sourceID, id); - } - } - ImGui::EndDragDropTarget(); - } - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_SELECT); - - ImGui::PopID(); + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_ANIMATION_REMOVE) && animation) + { + anm2_animation_remove(self->anm2, self->reference->animationID); + anm2_reference_clear(self->reference); } - if (ImGui::Button(STRING_IMGUI_EVENTS_ADD)) + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_ANIMATION_DEFAULT) && animation) + self->anm2->defaultAnimation = animation->name; + + if (_imgui_is_no_click_on_item()) + anm2_reference_clear(self->reference); + + _imgui_item_end(); +} + +static void _imgui_events(Imgui* self) +{ + static s32 selectedEventID = ID_NONE; + + _imgui_item_begin(IMGUI_EVENTS); + ImVec2 windowSize = ImGui::GetContentRegionAvail(); + + std::function event_item = [&](s32 id, Anm2Event& event) + { + ImGui::PushID(id); + + ImguiItem eventItem = IMGUI_EVENT; + eventItem.label = std::format(IMGUI_EVENT_FORMAT, id, event.name); + eventItem.isSelected = selectedEventID == id; + eventItem.size.x = windowSize.x; + + if (_imgui_item_atlas_image_selectable_inputtext(self, eventItem, &event.name, id)) + selectedEventID = id; + + if (ImGui::IsItemHovered()) + { + Anm2EventWithID eventWithID = {id, event}; + _imgui_clipboard_hovered_item_set(self, eventWithID); + self->clipboard->location = (s32)id; + } + + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) + { + ImGui::SetDragDropPayload(eventItem.dragDrop.c_str(), &id, sizeof(s32)); + event_item(id, event); + ImGui::EndDragDropSource(); + } + + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(eventItem.dragDrop.c_str())) + { + s32 sourceID = *(s32*)payload->Data; + if (sourceID != id) + { + imgui_undo_stack_push(self); + map_swap(self->anm2->events, sourceID, id); + } + } + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + }; + + for (auto& [id, event] : self->anm2->events) + event_item(id, event); + + if (_imgui_item_button(self, IMGUI_EVENT_ADD)) { s32 id = map_next_id_get(self->anm2->events); self->anm2->events[id] = Anm2Event{}; selectedEventID = id; } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_ADD); - + ImGui::SameLine(); - if (selectedEventID != -1) + if (selectedEventID == ID_NONE) { - if (ImGui::Button(STRING_IMGUI_EVENTS_REMOVE)) - { - self->anm2->events.erase(selectedEventID); - selectedEventID = -1; - } - _imgui_undoable(self); + _imgui_item_end(); + return; + } + + if (_imgui_item_button(self, IMGUI_EVENT_REMOVE)) + { + self->anm2->events.erase(selectedEventID); + selectedEventID = ID_NONE; } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_EVENTS_REMOVE); - if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - selectedEventID = -1; + if (_imgui_is_no_click_on_item()) + selectedEventID = ID_NONE; - ImGui::End(); + _imgui_item_end(); } -// Spritesheets -static void -_imgui_spritesheets(Imgui* self) +static void _imgui_spritesheets(Imgui* self) { - static s32 selectedSpritesheetID = -1; + static s32 selectedSpritesheetID = ID_NONE; - ImGui::Begin(STRING_IMGUI_SPRITESHEETS); + _imgui_item_begin(IMGUI_SPRITESHEETS); - for (auto [id, spritesheet] : self->anm2->spritesheets) + std::function spritesheet_item = [&](s32 id, Anm2Spritesheet& spritesheet) { - ImVec2 spritesheetPreviewSize = IMGUI_SPRITESHEET_PREVIEW_SIZE; - bool isSelected = selectedSpritesheetID == id; - Texture* texture = &self->resources->textures[id]; - - std::string spritesheetString = std::format(STRING_IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path); - - ImGui::BeginChild(spritesheetString.c_str(), IMGUI_SPRITESHEET_SIZE, true, ImGuiWindowFlags_None); - ImGui::PushID(id); + + Texture* texture = &self->resources->textures[id]; + ImguiItem spritesheetItem = IMGUI_SPRITESHEET_CHILD; - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); - ImGui::SameLine(); - - if (ImGui::Selectable(spritesheetString.c_str(), isSelected)) + ImguiItem spritesheetItemSelectable = IMGUI_SPRITESHEET_SELECTABLE; + spritesheetItemSelectable.label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path); + spritesheetItemSelectable.isSelected = selectedSpritesheetID == id; + + _imgui_item_begin_child(spritesheetItem); + if (_imgui_item_atlas_image_selectable(self, spritesheetItemSelectable)) { selectedSpritesheetID = id; _imgui_spritesheet_editor_set(self, selectedSpritesheetID); } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_SELECT); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { - ImGui::SetDragDropPayload(STRING_IMGUI_SPRITESHEETS_DRAG_DROP, &id, sizeof(s32)); - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_SPRITESHEET]), IMVEC2_ATLAS_UV_GET(TEXTURE_SPRITESHEET)); - ImGui::SameLine(); - ImGui::Text(spritesheetString.c_str()); + ImGui::SetDragDropPayload(spritesheetItem.dragDrop.c_str(), &id, sizeof(s32)); + spritesheet_item(id, spritesheet); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { - if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(STRING_IMGUI_SPRITESHEETS_DRAG_DROP)) + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(spritesheetItem.dragDrop.c_str())) { s32 sourceID = *(s32*)payload->Data; if (sourceID != id) @@ -1332,6 +1342,7 @@ _imgui_spritesheets(Imgui* self) ImGui::EndDragDropTarget(); } + ImVec2 spritesheetPreviewSize = IMGUI_SPRITESHEET_PREVIEW_SIZE; f32 spritesheetAspect = (f32)self->resources->textures[id].size.x / self->resources->textures[id].size.y; if ((IMGUI_SPRITESHEET_PREVIEW_SIZE.x / IMGUI_SPRITESHEET_PREVIEW_SIZE.y) > spritesheetAspect) @@ -1340,802 +1351,472 @@ _imgui_spritesheets(Imgui* self) spritesheetPreviewSize.y = IMGUI_SPRITESHEET_PREVIEW_SIZE.x / spritesheetAspect; if (texture->isInvalid) - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[TEXTURE_ERROR]), IMVEC2_ATLAS_UV_GET(TEXTURE_ERROR)); + _imgui_atlas_image(self, TEXTURE_NONE); else ImGui::Image(texture->id, spritesheetPreviewSize); - ImGui::PopID(); - - ImGui::EndChild(); - } + _imgui_item_end_child(); // spritesheetItem - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_ADD)) - dialog_png_open(self->dialog); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_ADD); + ImGui::PopID(); + }; + + for (auto [id, spritesheet] : self->anm2->spritesheets) + spritesheet_item(id, spritesheet); + + // TODO: match Nicalis + + _imgui_item_button(self, IMGUI_SPRITESHEET_ADD); ImGui::SameLine(); - // If spritesheet selected... - if (selectedSpritesheetID > -1) + if (selectedSpritesheetID == ID_NONE) { - // Remove - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REMOVE)) - { - texture_free(&self->resources->textures[selectedSpritesheetID]); - self->resources->textures.erase(selectedSpritesheetID); - self->anm2->spritesheets.erase(selectedSpritesheetID); - selectedSpritesheetID = -1; - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REMOVE); - - ImGui::SameLine(); - - // Reload - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_RELOAD)) - { - // Save the working path, set path, to the anm2's path, load spritesheet, return to old path - std::filesystem::path workingPath = std::filesystem::current_path(); - working_directory_from_file_set(self->anm2->path); - - resources_texture_init(self->resources, self->anm2->spritesheets[selectedSpritesheetID].path, selectedSpritesheetID); - - std::filesystem::current_path(workingPath); - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_RELOAD); - ImGui::SameLine(); - - // Replace - if (ImGui::Button(STRING_IMGUI_SPRITESHEETS_REPLACE)) - { - self->dialog->replaceID = selectedSpritesheetID; - dialog_png_replace(self->dialog); - } - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEETS_REPLACE); + _imgui_item_end(); + return; } - if (ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) - selectedSpritesheetID = -1; + if (_imgui_item_button(self, IMGUI_SPRITESHEET_REMOVE)) + { + texture_free(&self->resources->textures[selectedSpritesheetID]); + self->resources->textures.erase(selectedSpritesheetID); + self->anm2->spritesheets.erase(selectedSpritesheetID); + selectedSpritesheetID = ID_NONE; + } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_SPRITESHEET_RELOAD)) + { + std::filesystem::path workingPath = std::filesystem::current_path(); + working_directory_from_file_set(self->anm2->path); + + resources_texture_init(self->resources, self->anm2->spritesheets[selectedSpritesheetID].path, selectedSpritesheetID); + + std::filesystem::current_path(workingPath); + } + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_SPRITESHEET_REPLACE)) + { + self->dialog->replaceID = selectedSpritesheetID; + dialog_png_replace(self->dialog); + } + + if (_imgui_is_no_click_on_item()) + selectedSpritesheetID = ID_NONE; - ImGui::End(); + _imgui_item_end(); } -// Animation Preview -static void -_imgui_animation_preview(Imgui* self) +static void _imgui_animation_preview(Imgui* self) { static bool isPreviewHover = false; - static bool isPreviewCenter = false; - static vec2 mousePos = {0, 0}; - - ImGui::Begin(STRING_IMGUI_ANIMATION_PREVIEW, NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + static vec2 mousePos{}; + static vec2 previewPos{}; + static ImVec2 previewScreenPos{}; + _imgui_item_begin(IMGUI_ANIMATION_PREVIEW); ImVec2 windowSize = ImGui::GetWindowSize(); - - // Grid settings - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - // Grid toggle - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_GRID, &self->settings->previewIsGrid); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID); - + ImVec2 previewWindowRectSize = ImGui::GetCurrentWindow()->ClipRect.GetSize(); + + _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS); + _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_GRID, &self->settings->previewIsGrid); ImGui::SameLine(); - - // Grid Color - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_GRID_COLOR, (f32*)&self->settings->previewGridColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_COLOR); - - // Grid Size - ImGui::InputInt2(STRING_IMGUI_ANIMATION_PREVIEW_GRID_SIZE, (s32*)&self->settings->previewGridSizeX); - self->settings->previewGridSizeX = CLAMP(self->settings->previewGridSizeX, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - self->settings->previewGridSizeY = CLAMP(self->settings->previewGridSizeY, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_SIZE); - - // Grid Offset - ImGui::InputInt2(STRING_IMGUI_ANIMATION_PREVIEW_GRID_OFFSET, (s32*)&self->settings->previewGridOffsetX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_GRID_OFFSET); - ImGui::EndChild(); + _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_GRID_COLOR, (f32*)&self->settings->previewGridColorR); + _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_SIZE, (s32*)&self->settings->previewGridSizeX); + _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_OFFSET, (s32*)&self->settings->previewGridOffsetX); + _imgui_item_end_child(); ImGui::SameLine(); - ImGui::SameLine(); - - // View settings - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - // Zoom - ImGui::DragFloat(STRING_IMGUI_ANIMATION_PREVIEW_ZOOM, &self->settings->previewZoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ZOOM); - - // Center view - if (ImGui::Button(STRING_IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) - isPreviewCenter = true; - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_CENTER_VIEW); - - // Mouse position; note that mousePos is relative to the animation preview and set later - std::string mousePositionString = std::format(STRING_IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); + _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS); + _imgui_item_dragfloat(self, IMGUI_ANIMATION_PREVIEW_ZOOM, &self->settings->previewZoom); + if (_imgui_item_button(self, IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) + { + self->settings->previewPanX = -(previewWindowRectSize.x - PREVIEW_SIZE.x) * 0.5f; + self->settings->previewPanY = -((previewWindowRectSize.y - PREVIEW_SIZE.y) * 0.5f) + (previewPos.y * 0.5f); + } + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); ImGui::Text(mousePositionString.c_str()); - - ImGui::EndChild(); + _imgui_item_end_child(); ImGui::SameLine(); - // Background settings - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - // Background color - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->previewBackgroundColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_BACKGROUND_COLOR); - - ImGui::EndChild(); + _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS); + _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->previewBackgroundColorR); + _imgui_item_end_child(); ImGui::SameLine(); - // Helper settings - ImGui::BeginChild(STRING_IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS, IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE, true); - - // Axis toggle - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_AXIS, &self->settings->previewIsAxis); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS); - + _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS); + _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_AXIS, &self->settings->previewIsAxis); ImGui::SameLine(); + _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, (f32*)&self->settings->previewAxisColorR); + _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM, &self->settings->previewIsRootTransform); + _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT, &self->settings->previewIsShowPivot); + _imgui_item_end_child(); - // Axis colors - ImGui::ColorEdit4(STRING_IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, (f32*)&self->settings->previewAxisColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_AXIS_COLOR); - - // Root transform - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM, &self->settings->previewIsRootTransform); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_ROOT_TRANSFORM); - - // Show pivot - ImGui::Checkbox(STRING_IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT, &self->settings->previewIsShowPivot); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_ANIMATION_PREVIEW_SHOW_PIVOT); - - ImGui::EndChild(); - - // Animation preview texture - vec2 previewPos = IMVEC2_TO_VEC2(ImGui::GetCursorPos()); - + previewPos = IMVEC2_TO_VEC2(ImGui::GetCursorPos()); + previewScreenPos = ImGui::GetCursorScreenPos(); ImGui::Image(self->preview->texture, VEC2_TO_IMVEC2(PREVIEW_SIZE)); - - self->preview->recordSize = vec2(windowSize.x, windowSize.y - IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE.y); - - // Using tools when hovered + self->preview->recordSize = vec2(windowSize.x, windowSize.y - IMGUI_CANVAS_CHILD_SIZE.y); + if (ImGui::IsItemHovered()) { - vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); + if (!isPreviewHover) + SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)); - // Setting mouse pos relative to preview - mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); + isPreviewHover = true; + } + else + isPreviewHover = false; - mousePos -= (windowPos + previewPos); - mousePos -= (PREVIEW_SIZE / 2.0f); - mousePos.x += self->settings->previewPanX; - mousePos.y += self->settings->previewPanY; - mousePos.x /= PERCENT_TO_UNIT(self->settings->previewZoom); - mousePos.y /= PERCENT_TO_UNIT(self->settings->previewZoom); + Anm2Frame trigger{}; + anm2_frame_from_time(self->anm2, &trigger, Anm2Reference{self->reference->animationID, ANM2_TRIGGERS}, self->preview->time); - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); + if (trigger.eventID != ID_NONE) + { + static const ImU32 textColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_COLOR); + ImGui::GetWindowDrawList()->AddText(previewScreenPos, textColor, self->anm2->events[trigger.eventID].name.c_str()); + } - if (self->reference->itemType == ANM2_TRIGGERS) - frame = NULL; + if (!isPreviewHover) + { + _imgui_keyboard_navigation_set(true); + _imgui_item_end(); + return; + } - // Allow use of keybinds for tools - self->tool->isEnabled = true; + _imgui_keyboard_navigation_set(false); + + vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); + mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); - switch (self->tool->type) + mousePos -= (windowPos + previewPos); + mousePos -= (PREVIEW_SIZE * 0.5f); + mousePos += vec2(self->settings->previewPanX, self->settings->previewPanY); + mousePos /= PERCENT_TO_UNIT(self->settings->previewZoom); + + ToolType tool = self->tool; + bool isLeft = ImGui::IsKeyDown(IMGUI_INPUT_LEFT); + bool isRight = ImGui::IsKeyDown(IMGUI_INPUT_RIGHT); + bool isUp = ImGui::IsKeyDown(IMGUI_INPUT_UP); + bool isDown = ImGui::IsKeyDown(IMGUI_INPUT_DOWN); + bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_MOD); + bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; + f32 mouseWheel = ImGui::GetIO().MouseWheel; + + SDL_SetCursor(SDL_CreateSystemCursor(IMGUI_TOOL_MOUSE_CURSORS[tool])); + + if (self->tool == TOOL_MOVE || self->tool == TOOL_SCALE || self->tool == TOOL_ROTATE) + if (isMouseClick || isLeft || isRight || isUp || isDown) + imgui_undo_stack_push(self); + + if ((self->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + { + self->settings->previewPanX -= mouseDelta.x; + self->settings->previewPanY -= mouseDelta.y; + } + + Anm2Frame* frame = nullptr; + if (self->reference->itemType != ANM2_TRIGGERS) + frame = anm2_frame_from_reference(self->anm2, self->reference); + + if (frame) + { + f32 step = isMod ? IMGUI_TOOL_STEP_MOD : IMGUI_TOOL_STEP; + + switch (tool) { - case TOOL_PAN: - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)); - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - { - self->settings->previewPanX -= self->input->mouse.delta.x; - self->settings->previewPanY -= self->input->mouse.delta.y; - } - break; case TOOL_MOVE: - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE)); - - if (frame) + if (isMouseDown) + frame->position = IMVEC2_TO_VEC2(mousePos); + else { - f32 step = input_held(self->input, INPUT_MOD) ? PREVIEW_MOVE_STEP_MOD : PREVIEW_MOVE_STEP; - - if - ( - mouse_press(&self->input->mouse, MOUSE_LEFT) || - input_press(self->input, INPUT_LEFT) || - input_press(self->input, INPUT_RIGHT) || - input_press(self->input, INPUT_UP) || - input_press(self->input, INPUT_DOWN) - ) - _imgui_undo_stack_push(self); - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - frame->position = IMVEC2_TO_VEC2(mousePos); - - if (input_held(self->input, INPUT_LEFT)) - frame->position.x -= step; - - if (input_held(self->input, INPUT_RIGHT)) - frame->position.x += step; - - if (input_held(self->input, INPUT_UP)) - frame->position.y -= step; - - if (input_held(self->input, INPUT_DOWN)) - frame->position.y += step; + 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: - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); - if (frame) + if (isMouseDown) + frame->rotation += mouseDelta.x; + else { - f32 step = input_held(self->input, INPUT_MOD) ? PREVIEW_ROTATE_STEP_MOD : PREVIEW_ROTATE_STEP; - - if - ( - mouse_press(&self->input->mouse, MOUSE_LEFT) || - input_press(self->input, INPUT_LEFT) || - input_press(self->input, INPUT_RIGHT) || - input_press(self->input, INPUT_UP) || - input_press(self->input, INPUT_DOWN) - ) - _imgui_undo_stack_push(self); - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - frame->rotation += (s32)self->input->mouse.delta.x; - - if - ( - input_held(self->input, INPUT_LEFT) || - input_held(self->input, INPUT_UP) - ) - frame->rotation -= step; - - if - ( - input_held(self->input, INPUT_RIGHT) || - input_held(self->input, INPUT_DOWN) - ) - frame->rotation += step; + if (isLeft || isUp) frame->rotation -= step; + if (isRight || isDown) frame->rotation += step; } break; case TOOL_SCALE: - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NE_RESIZE)); - if (frame) + if (isMouseDown) + frame->scale += IMVEC2_TO_VEC2(ImGui::GetIO().MouseDelta); + else { - f32 step = input_held(self->input, INPUT_MOD) ? PREVIEW_SCALE_STEP_MOD : PREVIEW_SCALE_STEP; - - if - ( - mouse_press(&self->input->mouse, MOUSE_LEFT) || - input_press(self->input, INPUT_LEFT) || - input_press(self->input, INPUT_RIGHT) || - input_press(self->input, INPUT_UP) || - input_press(self->input, INPUT_DOWN) - ) - _imgui_undo_stack_push(self); - - if (input_held(self->input, INPUT_LEFT)) - frame->scale.x -= step; - - if (input_held(self->input, INPUT_RIGHT)) - frame->scale.x += step; - - if (input_held(self->input, INPUT_UP)) - frame->scale.y -= step; - - if (input_held(self->input, INPUT_DOWN)) - frame->scale.y += step; - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - { - frame->scale.x += (s32)self->input->mouse.delta.x; - frame->scale.y += (s32)self->input->mouse.delta.y; - } + 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; - case TOOL_CROP: - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); - break; default: break; - }; - - isPreviewHover = true; - - // Used to not be annoying when at lowest zoom - self->settings->previewZoom = self->settings->previewZoom == EDITOR_ZOOM_MIN ? 0 : self->settings->previewZoom; - - // Zoom in - if (self->input->mouse.wheelDeltaY > 0 || input_release(self->input, INPUT_ZOOM_IN)) - self->settings->previewZoom += PREVIEW_ZOOM_STEP; - - // Zoom out - if (self->input->mouse.wheelDeltaY < 0 || input_release(self->input, INPUT_ZOOM_OUT)) - self->settings->previewZoom -= PREVIEW_ZOOM_STEP; - - self->settings->previewZoom = CLAMP(self->settings->previewZoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); - } - else - { - if (isPreviewHover) - { - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)); - isPreviewHover = false; } } - if (isPreviewCenter) + if (mouseWheel != 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN) || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT)) { - ImVec2 previewWindowRectSize = ImGui::GetCurrentWindow()->ClipRect.GetSize(); - - // Based on the preview's crop in its window, adjust the pan - self->settings->previewPanX = -(previewWindowRectSize.x - PREVIEW_SIZE.x) / 2.0f; - self->settings->previewPanY = -((previewWindowRectSize.y - PREVIEW_SIZE.y) / 2.0f) + (IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE.y / 2.0f); - - isPreviewCenter = false; + const f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + self->settings->previewZoom = ROUND_NEAREST_FLOAT(self->settings->previewZoom + delta, CANVAS_ZOOM_STEP); + self->settings->previewZoom = CLAMP(self->settings->previewZoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } - ImGui::End(); + _imgui_item_end(); } -// Spritesheet Editor -static void -_imgui_spritesheet_editor(Imgui* self) +static void _imgui_spritesheet_editor(Imgui* self) { static bool isEditorHover = false; - static bool isEditorCenter = false; static vec2 mousePos = {0, 0}; + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); - ImGui::Begin(STRING_IMGUI_SPRITESHEET_EDITOR, NULL, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + _imgui_item_begin(IMGUI_SPRITESHEET_EDITOR); - // Grid settings - ImGui::BeginChild(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS, IMGUI_SPRITESHEET_EDITOR_SETTINGS_CHILD_SIZE, true); - - // Grid toggle - ImGui::Checkbox(STRING_IMGUI_SPRITESHEET_EDITOR_GRID, &self->settings->editorIsGrid); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID); - + _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS); + _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID, &self->settings->editorIsGrid); ImGui::SameLine(); - - // Grid snap - ImGui::Checkbox(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, &self->settings->editorIsGridSnap); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SNAP); - + _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, &self->settings->editorIsGridSnap); ImGui::SameLine(); - - // Grid color - ImGui::ColorEdit4(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_COLOR, (f32*)&self->settings->editorGridColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_COLOR); - - // Grid size - ImGui::InputInt2(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_SIZE, (s32*)&self->settings->editorGridSizeX); - self->settings->editorGridSizeX = CLAMP(self->settings->editorGridSizeX, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - self->settings->editorGridSizeY = CLAMP(self->settings->editorGridSizeY, PREVIEW_GRID_MIN, PREVIEW_GRID_MAX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_SIZE); - - // Grid offset - ImGui::InputInt2(STRING_IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET, (s32*)&self->settings->editorGridOffsetX); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_GRID_OFFSET); - - ImGui::EndChild(); + _imgui_item_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_GRID_COLOR, (f32*)&self->settings->editorGridColorR); + _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_SIZE, (s32*)&self->settings->editorGridSizeX); + _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET, (s32*)&self->settings->editorGridOffsetX); + _imgui_item_end_child(); ImGui::SameLine(); - // View settings - ImGui::BeginChild(STRING_IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS, IMGUI_SPRITESHEET_EDITOR_SETTINGS_CHILD_SIZE, true); - - // Zoom - ImGui::DragFloat(STRING_IMGUI_SPRITESHEET_EDITOR_ZOOM, &self->settings->editorZoom, 1, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX, "%.0f"); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_ZOOM); - - // Center view - if (ImGui::Button(STRING_IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW)) - isEditorCenter = true; - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_CENTER_VIEW); - - // Info position - std::string mousePositionString = std::format(STRING_IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); + _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS); + _imgui_item_dragfloat(self, IMGUI_SPRITESHEET_EDITOR_ZOOM, &self->settings->editorZoom); + if (_imgui_item_button(self, IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW)) + { + self->settings->editorPanX = EDITOR_SIZE.x / 2.0f; + self->settings->editorPanY = EDITOR_SIZE.y / 2.0f; + } ImGui::Text(mousePositionString.c_str()); - - ImGui::EndChild(); - + _imgui_item_end_child(); + ImGui::SameLine(); - // Background settings - ImGui::BeginChild(STRING_IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS, IMGUI_SPRITESHEET_EDITOR_SETTINGS_CHILD_SIZE, true); - - // Background color - ImGui::ColorEdit4(STRING_IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR, (f32*)&self->settings->editorBackgroundColorR, ImGuiColorEditFlags_NoInputs); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BACKGROUND_COLOR); - - // Border - ImGui::Checkbox(STRING_IMGUI_SPRITESHEET_EDITOR_BORDER, &self->settings->editorIsBorder); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_SPRITESHEET_EDITOR_BORDER); - - ImGui::EndChild(); + _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS); + _imgui_item_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR, (f32*)&self->settings->editorBackgroundColorR); + _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_BORDER, &self->settings->editorIsBorder); + _imgui_item_end_child(); vec2 editorPos = IMVEC2_TO_VEC2(ImGui::GetCursorPos()); ImGui::Image(self->editor->texture, VEC2_TO_IMVEC2(EDITOR_SIZE)); - // Panning and zoom - if (ImGui::IsItemHovered()) - { - vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); - mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); - - mousePos -= (windowPos + editorPos); - mousePos -= (EDITOR_SIZE / 2.0f); - mousePos.x += self->settings->editorPanX; - mousePos.y += self->settings->editorPanY; - mousePos.x /= PERCENT_TO_UNIT(self->settings->editorZoom); - mousePos.y /= PERCENT_TO_UNIT(self->settings->editorZoom); - - isEditorHover = true; - - Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - - // Allow use of keybinds for tools - self->tool->isEnabled = true; - - // Only changing layer frames - if (self->reference->itemType != ANM2_LAYER) - frame = NULL; - - if (self->tool->type == TOOL_CROP && frame) - { - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); - - if (mouse_press(&self->input->mouse, MOUSE_LEFT)) - { - _imgui_undo_stack_push(self); - - vec2 cropPosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; - - if (self->settings->editorIsGridSnap) - { - cropPosition.x = (s32)(cropPosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; - cropPosition.y = (s32)(cropPosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; - } - - frame->crop = cropPosition; - frame->size = {0, 0}; - - } - else if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - { - vec2 sizePosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; - - if (self->settings->editorIsGridSnap) - { - sizePosition.x = (s32)(sizePosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; - sizePosition.y = (s32)(sizePosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; - } - - frame->size = sizePosition - frame->crop; - } - } - else - { - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER)); - - if (mouse_held(&self->input->mouse, MOUSE_LEFT)) - { - self->settings->editorPanX -= self->input->mouse.delta.x; - self->settings->editorPanY -= self->input->mouse.delta.y; - } - } - - // Used to not be annoying when at lowest zoom - self->settings->editorZoom = self->settings->editorZoom == EDITOR_ZOOM_MIN ? 0 : self->settings->editorZoom; - - // Zoom in - if (self->input->mouse.wheelDeltaY > 0 || input_press(self->input, INPUT_ZOOM_IN)) - self->settings->editorZoom += PREVIEW_ZOOM_STEP; - - // Zoom out - if (self->input->mouse.wheelDeltaY < 0 || input_press(self->input, INPUT_ZOOM_OUT)) - self->settings->editorZoom -= PREVIEW_ZOOM_STEP; - - self->settings->editorZoom = CLAMP(self->settings->editorZoom, EDITOR_ZOOM_MIN, EDITOR_ZOOM_MAX); - } - else + if (!ImGui::IsItemHovered()) { if (isEditorHover) { SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)); isEditorHover = false; } + _imgui_keyboard_navigation_set(true); + _imgui_item_end(); + return; } - if (isEditorCenter) - { - self->settings->editorPanX = EDITOR_SIZE.x / 2.0f; - self->settings->editorPanY = EDITOR_SIZE.y / 2.0f; - - isEditorCenter = false; - } + isEditorHover = true; + _imgui_keyboard_navigation_set(false); - ImGui::End(); + vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); + mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); + + mousePos -= (windowPos + editorPos); + mousePos -= (EDITOR_SIZE * 0.5f); + mousePos += vec2(self->settings->editorPanX, self->settings->editorPanY); + mousePos /= PERCENT_TO_UNIT(self->settings->editorZoom); + + bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); + bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); + bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); + f32 mouseWheel = ImGui::GetIO().MouseWheel; + ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; + + SDL_SetCursor(SDL_CreateSystemCursor(IMGUI_TOOL_MOUSE_CURSORS[self->tool])); + + if ((self->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + { + self->settings->editorPanX -= mouseDelta.x; + self->settings->editorPanY -= mouseDelta.y; + } + + Anm2Frame* frame = nullptr; + if (self->reference->itemType == ANM2_LAYER) + frame = anm2_frame_from_reference(self->anm2, self->reference); + + if (frame && self->tool == TOOL_CROP) + { + if (isMouseClick) + { + imgui_undo_stack_push(self); + + vec2 cropPosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; + + if (self->settings->editorIsGridSnap) + { + cropPosition.x = (s32)(cropPosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; + cropPosition.y = (s32)(cropPosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; + } + + frame->crop = cropPosition; + frame->size = {0, 0}; + } + else if (isMouseDown) + { + vec2 sizePosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; + + if (self->settings->editorIsGridSnap) + { + sizePosition.x = (s32)(sizePosition.x / self->settings->editorGridSizeX) * self->settings->editorGridSizeX; + sizePosition.y = (s32)(sizePosition.y / self->settings->editorGridSizeX) * self->settings->editorGridSizeY; + } + + frame->size = sizePosition - frame->crop; + } + } + + if (mouseWheel != 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN) || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT)) + { + const f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + self->settings->editorZoom = ROUND_NEAREST_FLOAT(self->settings->editorZoom + delta, CANVAS_ZOOM_STEP); + self->settings->editorZoom = CLAMP(self->settings->editorZoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + } + + _imgui_item_end(); } -// Frame properties -static void -_imgui_frame_properties(Imgui* self) +static void _imgui_frame_properties(Imgui* self) { - ImGui::Begin(STRING_IMGUI_FRAME_PROPERTIES); - + _imgui_item_begin(IMGUI_FRAME_PROPERTIES); + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - if (frame) + if (!frame) { - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + ImGui::Text(IMGUI_FRAME_PROPERTIES_NO_FRAME); + _imgui_item_end(); + return; + } + + Anm2Type type = self->reference->itemType; + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - std::vector eventNames; + ImGui::Text(IMGUI_FRAME_PROPERTIES_TITLE[type].c_str()); + + if (type == ANM2_ROOT || type == ANM2_NULL || type == ANM2_LAYER) + { + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position)); + + if (type == ANM2_LAYER) + { + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_CROP, value_ptr(frame->crop)); + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SIZE, value_ptr(frame->size)); + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_PIVOT, value_ptr(frame->pivot)); + } + + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(frame->scale)); + _imgui_item_dragfloat(self, IMGUI_FRAME_PROPERTIES_ROTATION, &frame->rotation); + _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); + _imgui_item_coloredit4(self, IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); + _imgui_item_coloredit3(self, IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(frame->offsetRGB)); + + if (_imgui_item_button(self, IMGUI_FRAME_PROPERTIES_FLIP_X)) + frame->scale.x = -frame->scale.x; + ImGui::SameLine(); + if (_imgui_item_button(self, IMGUI_FRAME_PROPERTIES_FLIP_Y)) + frame->scale.y = -frame->scale.y; + + _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); + ImGui::SameLine(); + _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); + + } + else if (type == ANM2_TRIGGERS) + { + std::vector eventStrings; + std::vector eventLabels; std::vector eventIDs; - static s32 selectedEventIndex = -1; + + eventStrings.reserve(self->anm2->events.size() + 1); + eventIDs.reserve(self->anm2->events.size() + 1); + eventLabels.reserve(self->anm2->events.size() + 1); - switch (self->reference->itemType) + eventIDs.push_back(ID_NONE); + eventStrings.push_back(IMGUI_EVENT_NONE); + eventLabels.push_back(eventStrings.back().c_str()); + + for (auto & [id, event] : self->anm2->events) { - case ANM2_ROOT: - ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_ROOT); - break; - case ANM2_LAYER: - ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_LAYER); - break; - case ANM2_NULL: - ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_NULL); - break; - case ANM2_TRIGGERS: - ImGui::Text(STRING_IMGUI_FRAME_PROPERTIES_TRIGGER); - break; - default: - break; + eventIDs.push_back(id); + eventStrings.push_back(std::format(IMGUI_EVENT_FORMAT, id, event.name)); + eventLabels.push_back(eventStrings.back().c_str()); } - switch (self->reference->itemType) - { - case ANM2_ROOT: - case ANM2_NULL: - // Position - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); + s32 selectedEventIndex = std::find(eventIDs.begin(), eventIDs.end(), frame->eventID) - eventIDs.begin(); - // Scale - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(frame->scale), 1.0, 0, 0, "%.1f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE); + if (_imgui_item_combo(self, IMGUI_FRAME_PROPERTIES_EVENT, &selectedEventIndex, eventLabels.data(), (s32)eventLabels.size())) + frame->eventID = eventIDs[selectedEventIndex]; - // Rotation - ImGui::DragFloat(STRING_IMGUI_FRAME_PROPERTIES_ROTATION, &frame->rotation, 1, 0, 0, "%.1f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION); - - // Duration - ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); - frame->delay = CLAMP(frame->delay, ANM2_FRAME_DELAY_MIN, animation->frameNum + 1); - - // Tint - ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT); - - // Color Offset - ImGui::ColorEdit3(STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(frame->offsetRGB)); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET); - - // Visible - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); - - ImGui::SameLine(); - - // Interpolation - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); - - // Flip X - if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_X)) - frame->scale.x = -frame->scale.x; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_X); - - ImGui::SameLine(); - - // Flip Y - if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_Y)) - frame->scale.y = -frame->scale.y; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_Y); - break; - case ANM2_LAYER: - // Position - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position), 1, 0, 0, "%.0f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_POSITION); - - // Crop Position - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_POSITION, value_ptr(frame->crop), 1, 0, 0, "%.0f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_POSITION); - - // Crop - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_CROP_SIZE, value_ptr(frame->size), 1, 0, 0, "%.0f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_CROP_SIZE); - - // Pivot - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_PIVOT, value_ptr(frame->pivot), 1, 0, 0, "%.0f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_PIVOT); - - // Scale - ImGui::DragFloat2(STRING_IMGUI_FRAME_PROPERTIES_SCALE, value_ptr(frame->scale), 1.0, 0, 0, "%.1f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_SCALE); - - // Rotation - ImGui::DragFloat(STRING_IMGUI_FRAME_PROPERTIES_ROTATION, &frame->rotation, 1, 0, 0, "%.1f"); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_ROTATION); - - // Duration - ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_DURATION, &frame->delay); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_DURATION); - // clamp delay - frame->delay = CLAMP(frame->delay, ANM2_FRAME_DELAY_MIN, animation->frameNum + 1); - - // Tint - ImGui::ColorEdit4(STRING_IMGUI_FRAME_PROPERTIES_TINT, value_ptr(frame->tintRGBA)); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_TINT); - - // Color Offset - ImGui::ColorEdit3(STRING_IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, value_ptr(frame->offsetRGB)); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_COLOR_OFFSET); - - // Flip X - if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_X)) - frame->scale.x = -frame->scale.x; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_X); - - ImGui::SameLine(); - - // Flip Y - if (ImGui::Button(STRING_IMGUI_FRAME_PROPERTIES_FLIP_Y)) - frame->scale.y = -frame->scale.y; - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_FLIP_Y); - - // Visible - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_VISIBLE, &frame->isVisible); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_VISIBLE); - - ImGui::SameLine(); - - // Interpolation - ImGui::Checkbox(STRING_IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_INTERPOLATED); - break; - case ANM2_TRIGGERS: - // Events drop down; pick one! - for (auto & [id, event] : self->anm2->events) - { - eventIDs.push_back(id); - eventNames.push_back(event.name.c_str()); - if (id == frame->eventID) - selectedEventIndex = eventIDs.size() - 1; - } - - if (ImGui::Combo(STRING_IMGUI_FRAME_PROPERTIES_EVENT, &selectedEventIndex, eventNames.data(), eventNames.size())) - { - frame->eventID = eventIDs[selectedEventIndex]; - selectedEventIndex = -1; - } - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_EVENT); - - // At Frame - ImGui::InputInt(STRING_IMGUI_FRAME_PROPERTIES_AT_FRAME, &frame->atFrame); - _imgui_undoable(self); - _imgui_tooltip(STRING_IMGUI_TOOLTIP_FRAME_PROPERTIES_AT_FRAME); - frame->atFrame = CLAMP(frame->atFrame, 0, animation->frameNum- 1); - break; - default: - break; - } - + _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_AT_FRAME, &frame->atFrame); + frame->atFrame = CLAMP(frame->atFrame, 0, animation->frameNum - 1); } - ImGui::End(); + _imgui_item_end(); } -// Initializes imgui -void -imgui_init -( - Imgui* self, - Dialog* dialog, - Resources* resources, - Input* input, - Anm2* anm2, - Anm2Reference* reference, - f32* time, - Editor* editor, - Preview* preview, - Settings* settings, - Tool* tool, - Snapshots* snapshots, - SDL_Window* window, - SDL_GLContext* glContext -) +static void _imgui_persistent(Imgui* self) { - IMGUI_CHECKVERSION(); + if (self->preview->isRecording) + { + ImVec2 mousePos = ImGui::GetMousePos(); - self->dialog = dialog; - self->resources = resources; - self->input = input; - self->anm2 = anm2; - self->reference = reference; - self->time = time; - self->editor = editor; - self->preview = preview; - self->settings = settings; - self->tool = tool; - self->snapshots = snapshots; - self->window = window; - self->glContext = glContext; + ImGui::SetNextWindowPos(ImVec2(mousePos.x + IMGUI_TOOLTIP_OFFSET.x, mousePos.y + IMGUI_TOOLTIP_OFFSET.y)); + ImGui::BeginTooltip(); + _imgui_item_atlas_image_text(self, IMGUI_RECORDING); + ImGui::EndTooltip(); + } - ImGui::CreateContext(); - ImGui::StyleColorsDark(); + if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) + ImGui::OpenPopup(IMGUI_CONTEXT_MENU); - ImGui_ImplSDL3_InitForOpenGL(self->window, *self->glContext); - ImGui_ImplOpenGL3_Init(STRING_OPENGL_VERSION); + if (ImGui::BeginPopup(IMGUI_CONTEXT_MENU)) + { + ImguiItem pasteItem = IMGUI_PASTE; + pasteItem.isInactive = self->clipboard->item.type == CLIPBOARD_NONE; - ImGuiIO& io = ImGui::GetIO(); - io.IniFilename = NULL; - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - io.ConfigWindowsMoveFromTitleBarOnly = true; + _imgui_item_selectable(self, IMGUI_CUT); + _imgui_item_selectable(self, IMGUI_COPY); + _imgui_item_selectable(self, pasteItem); - ImGui::LoadIniSettingsFromDisk(PATH_SETTINGS); - - std::cout << STRING_INFO_IMGUI_INIT << std::endl; + ImGui::EndPopup(); + } } -// Main dockspace -static void -_imgui_dock(Imgui* self) +static void _imgui_dock(Imgui* self) { ImGuiViewport* viewport = ImGui::GetMainViewport(); - - ImGuiWindowFlags dockspaceWindowFlags = 0 | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoBringToFrontOnFocus | - ImGuiWindowFlags_NoNavFocus; - ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + IMGUI_TASKBAR_HEIGHT)); - ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, viewport->Size.y - IMGUI_TASKBAR_HEIGHT)); + 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(STRING_IMGUI_WINDOW, NULL, dockspaceWindowFlags); + _imgui_item_begin(IMGUI_WINDOW); - ImGui::DockSpace(ImGui::GetID(STRING_IMGUI_DOCKSPACE), ImVec2(0.0f, 0.0f), ImGuiDockNodeFlags_PassthruCentralNode); + _imgui_item_dockspace(IMGUI_DOCKSPACE); _imgui_tools(self); _imgui_animations(self); @@ -2149,38 +1830,113 @@ _imgui_dock(Imgui* self) ImGui::End(); } -// Ticks imgui -void -imgui_tick(Imgui* self) +void imgui_init +( + Imgui* self, + Dialog* dialog, + Resources* resources, + Anm2* anm2, + Anm2Reference* reference, + Editor* editor, + Preview* preview, + 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->settings = settings; + self->snapshots = snapshots; + self->clipboard = clipboard; + self->window = window; + self->glContext = glContext; + + 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.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigWindowsMoveFromTitleBarOnly = true; + + ImGui::LoadIniSettingsFromDisk(SETTINGS_PATH); +} + +void imgui_tick(Imgui* self) { ImGui_ImplSDL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame(); ImGui::NewFrame(); - self->tool->isEnabled = false; - _imgui_taskbar(self); _imgui_dock(self); + _imgui_persistent(self); + + self->isHotkeysEnabled = !self->isRename && !self->isChangeValue; + + if (self->isHotkeysEnabled) + { + for (const auto& hotkey : imgui_hotkey_registry()) + { + if (ImGui::IsKeyChordPressed(hotkey.chord)) + { + if (hotkey.is_focus_window() && (imgui_nav_window_root_get() != hotkey.focusWindow)) + continue; + + hotkey.function(self); + } + } + } + + SDL_Event event; + + while(SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + + switch (event.type) + { + case SDL_EVENT_QUIT: + if (ImGui::IsPopupOpen(IMGUI_EXIT_CONFIRMATION_POPUP)) + { + self->isQuit = true; + break; + } + ImGui::OpenPopup(IMGUI_EXIT_CONFIRMATION_POPUP); + break; + default: + break; + } + } + + if (_imgui_item_yes_no_popup(self, IMGUI_EXIT_CONFIRMATION)) + self->isQuit = true; } -// Draws imgui -void -imgui_draw(void) +void imgui_draw(void) { ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } -// Frees imgui -void -imgui_free(void) +void imgui_free(void) { ImGui_ImplSDL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown(); - // Save Ini manually - ImGui::SaveIniSettingsToDisk(PATH_SETTINGS); + ImGui::SaveIniSettingsToDisk(SETTINGS_PATH); ImGui::DestroyContext(); - - std::cout << STRING_INFO_IMGUI_FREE << std::endl; -} +} \ No newline at end of file diff --git a/src/imgui.h b/src/imgui.h index a14f01a..06d36ca 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -1,129 +1,1580 @@ #pragma once -#include "dialog.h" -#include "resources.h" -#include "editor.h" -#include "preview.h" -#include "window.h" -#include "input.h" -#include "settings.h" -#include "snapshots.h" -#include "tool.h" - #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); } \ + +#define IM_VEC3_CLASS_EXTRA \ + inline bool operator==(const ImVec3& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z; } \ + inline bool operator!=(const ImVec3& rhs) const { return !(*this == rhs); } \ + inline ImVec3 operator+(const ImVec3& rhs) const { return ImVec3(x + rhs.x, y + rhs.y, z + rhs.z); } \ + inline ImVec3 operator-(const ImVec3& rhs) const { return ImVec3(x - rhs.x, y - rhs.y, z - rhs.z); } \ + inline ImVec3 operator*(const ImVec3& rhs) const { return ImVec3(x * rhs.x, y * rhs.y, z * rhs.z); } + +#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); } + #include #include #include #include -#define IMGUI_TIMELINE_ELEMENT_WIDTH 300 +#include "clipboard.h" +#include "dialog.h" +#include "editor.h" +#include "preview.h" +#include "resources.h" +#include "settings.h" +#include "snapshots.h" +#include "window.h" -#define IMGUI_DRAG_SPEED 1.0 -#define IMGUI_TASKBAR_HEIGHT 32 -#define IMGUI_TIMELINE_OFFSET_Y 24 -#define IMGUI_TIMELINE_ANIMATION_LENGTH_WIDTH 200 -#define IMGUI_TIMELINE_FPS_WIDTH 100 -#define IMGUI_TIMELINE_CREATED_BY_WIDTH 150 -#define IMGUI_TIMELINE_FRAME_INDICES_MULTIPLE 5 -#define IMGUI_TIMELINE_FRAME_INDICES_STRING_MAX 16 -#define IMGUI_PICKER_LINE_SIZE 1.0f +#define IMGUI_ANIMATIONS_LABEL "Animations" +#define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" +#define IMGUI_CHORD_NONE (ImGuiMod_None) +#define IMGUI_CONTEXT_MENU "## Context Menu" +#define IMGUI_EVENTS_LABEL "Events" +#define IMGUI_EVENT_FORMAT "#{} {}" +#define IMGUI_EVENT_NONE "None" +#define IMGUI_FILE_POPUP "## File Popup" #define IMGUI_FRAME_BORDER 2.0f +#define IMGUI_FRAME_PROPERTIES_NO_FRAME "Select a frame to show properties..." +#define IMGUI_ITEM_SELECTABLE_EDITABLE_LABEL "## Editing" +#define IMGUI_OPENGL_VERSION "#version 330" #define IMGUI_PICKER_LINE_COLOR IM_COL32(255, 255, 255, 255) -#define IMGUI_TOOLS_WIDTH_INCREMENT -2 +#define IMGUI_PLAYBACK_POPUP "## Playback Popup" +#define IMGUI_EXIT_CONFIRMATION_POPUP "Exit Confirmation" +#define IMGUI_POSITION_FORMAT "Position: {{{:5}, {:5}}}" +#define IMGUI_SPRITESHEET_FORMAT "#{} {}" +#define IMGUI_TIMELINE_ADD_ELEMENT_POPUP "## Add Element Popup" +#define IMGUI_TIMELINE_CHILD_ID_LABEL "#{} {}" +#define IMGUI_TIMELINE_FOOTER_HEIGHT 20 +#define IMGUI_TIMELINE_FRAME_BORDER 2 +#define IMGUI_TIMELINE_FRAME_LABEL_FORMAT "## {}" +#define IMGUI_TIMELINE_FRAME_MULTIPLE 5 +#define IMGUI_TIMELINE_LABEL "Timeline" +#define IMGUI_MERGE_POPUP "Merge Animations" +#define IMGUI_TIMELINE_NO_ANIMATION "Select an animation to show timeline..." +#define IMGUI_TIMELINE_PICKER_LINE_WIDTH 2.0f +#define IMGUI_TIMELINE_SPRITESHEET_ID_FORMAT "#{}" +#define IMGUI_TOOL_STEP 1 +#define IMGUI_TOOL_STEP_MOD 10 +#define IMGUI_WIZARD_POPUP "## Wizard Popup" #define VEC2_TO_IMVEC2(value) ImVec2(value.x, value.y) #define IMVEC2_TO_VEC2(value) glm::vec2(value.x, value.y) #define IMVEC2_ATLAS_UV_GET(type) VEC2_TO_IMVEC2(ATLAS_UVS[type][0]), VEC2_TO_IMVEC2(ATLAS_UVS[type][1]) -const vec2 IMGUI_TASKBAR_MARGINS = {8, 4}; -const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; - -const ImVec2 IMGUI_RECORD_TOOLTIP_OFFSET = {16, 16}; -const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_SIZE = {1280, 105}; -const ImVec2 IMGUI_ANIMATION_PREVIEW_SETTINGS_CHILD_SIZE = {200, 85}; -const ImVec2 IMGUI_ANIMATION_PREVIEW_POSITION = {8, 135}; - -const ImVec2 IMGUI_SPRITESHEET_EDITOR_SETTINGS_CHILD_SIZE = {200, 85}; -const ImVec2 IMGUI_SPRITESHEET_EDITOR_SETTINGS_SIZE = {1280, 105}; - -const ImVec2 IMGUI_TIMELINE_ELEMENT_LIST_SIZE = {300, 0}; -const ImVec2 IMGUI_TIMELINE_FRAMES_SIZE = {0, 0}; -const ImVec2 IMGUI_TIMELINE_ELEMENT_FRAMES_SIZE = {0, 0}; const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {16, 40}; -const ImVec2 IMGUI_TIMELINE_VIEWER_SIZE = {0, 40}; -const ImVec2 IMGUI_TIMELINE_ELEMENTS_TIMELINE_SIZE = {0, 40}; -const ImVec2 IMGUI_TIMELINE_FRAME_INDICES_SIZE = {0, 40}; -const ImVec2 IMGUI_TIMELINE_ELEMENT_SIZE = {300, 40}; -const ImVec2 IMGUI_TIMELINE_ELEMENT_NAME_SIZE = {150, 20}; -const ImVec2 IMGUI_TIMELINE_ELEMENT_SPRITESHEET_ID_SIZE = {60, 20}; +const ImVec2 IMGUI_TIMELINE_FRAME_CONTENT_OFFSET = {TEXTURE_SIZE_SMALL.x * 0.25f, (IMGUI_TIMELINE_FRAME_SIZE.y * 0.5f) - (TEXTURE_SIZE_SMALL.y * 0.5f)}; +const ImVec2 IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE = {150, 0}; +const ImVec2 IMGUI_TIMELINE_ITEM_SIZE = {300, 40}; -const ImVec2 IMGUI_SPRITESHEET_SIZE = {0, 150}; -const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {100, 100}; -const ImVec2 IMGUI_IMAGE_TARGET_SIZE = {125, 125}; -const ImVec2 IMGUI_ICON_BUTTON_SIZE = {24, 24}; -const ImVec2 IMGUI_DUMMY_SIZE = {1, 1}; +const ImVec4 IMGUI_TIMELINE_FRAME_COLOR = {0.0f, 0.0f, 0.0f, 0.125}; +const ImVec4 IMGUI_TIMELINE_FRAME_MULTIPLE_COLOR = {0.113, 0.184, 0.286, 0.125}; +const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_COLOR = {0.113, 0.184, 0.286, 0.5}; +const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_COLOR = {0.113, 0.184, 0.286, 1.0}; +const ImVec4 IMGUI_INACTIVE_COLOR = {0.5, 0.5, 0.5, 1.0}; +const ImVec4 IMGUI_ACTIVE_COLOR = {1.0, 1.0, 1.0, 1.0}; -const ImVec4 IMGUI_TIMELINE_HEADER_COLOR = {0.04, 0.04, 0.04, 1.0f}; -const ImVec4 IMGUI_FRAME_BORDER_COLOR = {1.0f, 1.0f, 1.0f, 0.5f}; -const ImVec4 IMGUI_FRAME_OVERLAY_COLOR = {0.0f, 0.0f, 0.0f, 0.25f}; -const ImVec4 IMGUI_FRAME_INDICES_OVERLAY_COLOR = {0.113, 0.184, 0.286, 1.0f}; -const ImVec4 IMGUI_FRAME_INDICES_COLOR = {0.113, 0.184, 0.286, 0.5f}; +const ImVec2 IMGUI_SPRITESHEET_PREVIEW_SIZE = {125.0, 125.0}; +const ImVec2 IMGUI_TOOLTIP_OFFSET = {16, 8}; +const vec2 IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS = {1, 1}; +const ImVec2 IMGUI_CANVAS_CHILD_SIZE = {200, 85}; -const ImVec4 IMGUI_TIMELINE_ROOT_COLOR = {0.010, 0.049, 0.078, 1.0f}; -const ImVec4 IMGUI_TIMELINE_LAYER_COLOR = {0.098, 0.039, 0.020, 1.0f}; -const ImVec4 IMGUI_TIMELINE_NULL_COLOR = {0.020, 0.049, 0.000, 1.0f}; -const ImVec4 IMGUI_TIMELINE_TRIGGERS_COLOR = {0.078, 0.020, 0.029, 1.0f}; +const ImGuiKey IMGUI_INPUT_DELETE = ImGuiKey_Delete; +const ImGuiKey IMGUI_INPUT_LEFT = ImGuiKey_LeftArrow; +const ImGuiKey IMGUI_INPUT_RIGHT = ImGuiKey_RightArrow; +const ImGuiKey IMGUI_INPUT_UP = ImGuiKey_UpArrow; +const ImGuiKey IMGUI_INPUT_DOWN = ImGuiKey_DownArrow; +const ImGuiKey IMGUI_INPUT_MOD = ImGuiMod_Shift; +const ImGuiKey IMGUI_INPUT_ZOOM_IN = ImGuiKey_1; +const ImGuiKey IMGUI_INPUT_ZOOM_OUT = ImGuiKey_2; +const ImGuiKey IMGUI_INPUT_PLAY = ImGuiKey_Space; -const ImVec4 IMGUI_TIMELINE_ROOT_FRAME_COLOR = {0.020, 0.294, 0.569, 0.5}; -const ImVec4 IMGUI_TIMELINE_LAYER_FRAME_COLOR = {0.529, 0.157, 0.000, 0.5}; -const ImVec4 IMGUI_TIMELINE_NULL_FRAME_COLOR = {0.137, 0.353, 0.000, 0.5}; -const ImVec4 IMGUI_TIMELINE_TRIGGERS_FRAME_COLOR = {0.529, 0.118, 0.196, 0.5}; +inline const std::string IMGUI_FRAME_PROPERTIES_TITLE[ANM2_COUNT] = +{ + "", + "-- Root --", + "-- Layer --", + "-- Null --", + "-- Trigger --", +}; -const ImVec4 IMGUI_TIMELINE_ROOT_HIGHLIGHT_COLOR = {0.314, 0.588, 0.843, 0.75}; -const ImVec4 IMGUI_TIMELINE_LAYER_HIGHLIGHT_COLOR = {0.882, 0.412, 0.216, 0.75}; -const ImVec4 IMGUI_TIMELINE_NULL_HIGHLIGHT_COLOR = {0.431, 0.647, 0.294, 0.75}; -const ImVec4 IMGUI_TIMELINE_TRIGGERS_HIGHLIGHT_COLOR = {0.804, 0.412, 0.490, 0.75}; +static inline ImGuiKey imgui_key_get(char c) +{ + if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; + if (c >= 'A' && c <= 'Z') return static_cast(ImGuiKey_A + (c - 'A')); + return ImGuiKey_None; +} +enum ToolType +{ + TOOL_PAN, + TOOL_MOVE, + TOOL_ROTATE, + TOOL_SCALE, + TOOL_CROP, + TOOL_DRAW, + TOOL_ERASE, + TOOL_COLOR_PICKER, + TOOL_UNDO, + TOOL_REDO, + TOOL_COLOR, + TOOL_COUNT, +}; -const ImVec4 IMGUI_TIMELINE_ROOT_ACTIVE_COLOR = {0.471, 0.882, 1.000, 0.75}; -const ImVec4 IMGUI_TIMELINE_LAYER_ACTIVE_COLOR = {1.000, 0.618, 0.324, 0.75}; -const ImVec4 IMGUI_TIMELINE_NULL_ACTIVE_COLOR = {0.646, 0.971, 0.441, 0.75}; -const ImVec4 IMGUI_TIMELINE_TRIGGERS_ACTIVE_COLOR = {1.000, 0.618, 0.735, 0.75}; +#define SDL_MOUSE_CURSOR_NULL (-1) +const SDL_SystemCursor IMGUI_TOOL_MOUSE_CURSORS[TOOL_COUNT] = +{ + SDL_SYSTEM_CURSOR_POINTER, + SDL_SYSTEM_CURSOR_MOVE, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_NE_RESIZE, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_CROSSHAIR, + SDL_SYSTEM_CURSOR_DEFAULT, + SDL_SYSTEM_CURSOR_DEFAULT +}; struct Imgui { - Dialog* dialog = NULL; - Resources* resources = NULL; - Input* input = NULL; - Anm2* anm2 = NULL; - Anm2Reference* reference = NULL; - f32* time = NULL; - Editor* editor = NULL; - Preview* preview = NULL; - Settings* settings = NULL; - Tool* tool = NULL; - Snapshots* snapshots = NULL; - SDL_Window* window = NULL; - SDL_GLContext* glContext = NULL; - bool isSwap = false; - Anm2Reference swapReference; + Dialog* dialog = nullptr; + Resources* resources = nullptr; + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Editor* editor = nullptr; + Preview* preview = nullptr; + Settings* settings = nullptr; + Snapshots* snapshots = nullptr; + Clipboard* clipboard = nullptr; + SDL_Window* window = nullptr; + SDL_GLContext* glContext = nullptr; + ToolType tool = TOOL_PAN; + bool isHotkeysEnabled = true; + bool isRename = false; + bool isChangeValue = false; + bool isQuit = false; }; -void -imgui_init +typedef void(*ImguiFunction)(Imgui*); + +struct ImguiHotkey +{ + ImGuiKeyChord chord; + ImguiFunction function; + std::string focusWindow{}; + + bool is_focus_window() const { return !focusWindow.empty(); } +}; + +static std::vector& imgui_hotkey_registry() +{ + static std::vector registry; + return registry; +} + +enum PopupType +{ + POPUP_BY_ITEM, + POPUP_CENTER_SCREEN +}; + +struct ImguiColorSet +{ + ImVec4 normal{}; + ImVec4 active{}; + ImVec4 hovered{}; + + bool is_normal() const { return normal != ImVec4(); } + bool is_active() const { return active != ImVec4(); } + bool is_hovered() const { return hovered != ImVec4(); } +}; + +struct ImguiItemBuilder +{ + std::string label{}; + std::string tooltip{}; + std::string popup{}; + std::string dragDrop{}; + std::string format{}; + std::string focusWindow{}; + ImguiFunction function = nullptr; + ImGuiKeyChord chord = IMGUI_CHORD_NONE; + TextureType texture = TEXTURE_NONE; + PopupType popupType = POPUP_BY_ITEM; + bool isUndoable = false; + bool isSizeToText = true; + ImguiColorSet color{}; + ImVec2 size{}; + ImVec2 contentOffset{}; + f64 speed{}; + f64 min{}; + f64 max{}; + s32 border{}; + s32 step = 1; + s32 stepFast = 1; + s32 value = ID_NONE; + s32 id = ID_NONE; + s32 flags{}; + s32 flagsAlt{}; +}; + +struct ImguiItem +{ + std::string label{}; + std::string tooltip{}; + std::string popup{}; + std::string dragDrop{}; + std::string format{}; + std::string focusWindow{}; + ImguiFunction function = nullptr; + ImGuiKeyChord chord = IMGUI_CHORD_NONE; + TextureType texture = TEXTURE_NONE; + PopupType popupType = POPUP_BY_ITEM; + bool isUndoable = false; + bool isInactive = false; + bool isSizeToText = true; + bool isSelected = false; + f64 speed{}; + f64 min{}; + f64 max{}; + s32 border{}; + s32 step = 1; + s32 stepFast = 1; + s32 value = ID_NONE; + s32 id{}; + s32 flags{}; + s32 flagsAlt{}; + ImguiColorSet color{}; + ImVec2 size{}; + ImVec2 contentOffset{}; + ImGuiKey mnemonicKey = ImGuiKey_None; + s32 mnemonicIndex = -1; + + bool is_popup() const { return !popup.empty(); } + bool is_tooltip() const { return !tooltip.empty(); } + bool is_size() const { return size != ImVec2(); } + bool is_border() const { return border != 0; } + bool is_mnemonic() const { return mnemonicIndex != -1 && mnemonicKey != ImGuiKey_None; } + bool is_chord() const { return chord != IMGUI_CHORD_NONE;} + bool is_focus_window() const { return !focusWindow.empty(); } + + ImguiItem(const ImguiItemBuilder& builder) + { + static s32 newID = 0; + + tooltip = builder.tooltip; + popup = builder.popup; + dragDrop = builder.dragDrop; + format = builder.format; + focusWindow = builder.focusWindow; + function = builder.function; + chord = builder.chord; + popupType = builder.popupType; + texture = builder.texture; + isUndoable = builder.isUndoable; + isSizeToText = builder.isSizeToText; + color = builder.color; + size = builder.size; + contentOffset = builder.contentOffset; + speed = builder.speed; + min = builder.min; + max = builder.max; + border = builder.border; + step = builder.step; + stepFast = builder.stepFast; + value = builder.value; + flags = builder.flags; + flagsAlt = builder.flagsAlt; + + id = newID; + newID++; + + if (is_chord() && function) + imgui_hotkey_registry().push_back({chord, function, focusWindow}); + + mnemonicIndex = -1; + + for (s32 i = 0; i < (s32)builder.label.size(); i++) + { + if (builder.label[i] == '&') + { + if (builder.label[i + 1] == '&') + { + label += '&'; + i++; + } + else if (builder.label[i + 1] != '\0') + { + mnemonicKey = imgui_key_get(builder.label[i + 1]); + mnemonicIndex = (s32)label.size(); + label += builder.label[i + 1]; + i++; + } + } + else + label += builder.label[i]; + } + } +}; + +static inline std::string_view imgui_nav_window_root_get() +{ + ImGuiWindow* navWindow = ImGui::GetCurrentContext()->NavWindow; + if (!navWindow) + return {}; + + std::string_view name(navWindow->Name); + size_t slash = name.find('/'); + return (slash == std::string_view::npos) ? name : name.substr(0, slash); +} + +static inline void imgui_file_new(Imgui* self) +{ + anm2_reference_clear(self->reference); + anm2_new(self->anm2); +} + +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); +} + +static inline void imgui_file_save_as(Imgui* self) +{ + dialog_anm2_save(self->dialog); +} + +static inline void imgui_png_open(Imgui* self) +{ + dialog_png_open(self->dialog); +} + +static inline void imgui_generate_gif_animation(Imgui* self) +{ + if (anm2_animation_from_reference(self->anm2, self->reference)) + { + self->preview->isRecording = true; + self->preview->time = 0; + } +} + +static inline void imgui_undo_stack_push(Imgui* self) +{ + Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time}; + snapshots_undo_stack_push(self->snapshots, &snapshot); +} + +static inline void imgui_tool_pan_set(Imgui* self) +{ + self->tool = TOOL_PAN; +} + +static inline void imgui_tool_move_set(Imgui* self) +{ + self->tool = TOOL_MOVE; +} + +static inline void imgui_tool_rotate_set(Imgui* self) +{ + self->tool = TOOL_ROTATE; +} + +static inline void imgui_tool_scale_set(Imgui* self) +{ + self->tool = TOOL_SCALE; +} + +static inline void imgui_tool_crop_set(Imgui* self) +{ + self->tool = TOOL_CROP; +} + +static inline void imgui_tool_draw_set(Imgui* self) +{ + self->tool = TOOL_DRAW; +} + +static inline void imgui_tool_erase_set(Imgui* self) +{ + self->tool = TOOL_ERASE; +} + +static inline void imgui_tool_color_picker_set(Imgui* self) +{ + self->tool = TOOL_COLOR_PICKER; +} + +static inline void imgui_undo(Imgui* self) +{ + snapshots_undo(self->snapshots); +} + +static inline void imgui_redo(Imgui* self) +{ + snapshots_redo(self->snapshots); +} + +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) +{ + clipboard_paste(self->clipboard); +} + +const inline ImguiItem IMGUI_WINDOW = ImguiItem +({ + .label = "## Window", + .flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBringToFrontOnFocus | + ImGuiWindowFlags_NoNavFocus +}); + +const inline ImguiItem IMGUI_DOCKSPACE = ImguiItem +({ + .label = "## Dockspace", + .flags = ImGuiDockNodeFlags_PassthruCentralNode +}); + +const inline ImguiItem IMGUI_TASKBAR = ImguiItem +({ + .label = "## Taskbar", + .size = {0, 32}, + .flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoSavedSettings +}); + +const inline ImguiItem IMGUI_TASKBAR_FILE = ImguiItem +({ + .label = "&File", + .tooltip = "Opens the file menu, for reading/writing anm2 files.", + .popup = IMGUI_FILE_POPUP, + .isSizeToText = true +}); + +const inline ImguiItem IMGUI_FILE_NEW = ImguiItem +({ + .label = "&New", + .tooltip = "Load a blank .anm2 file to edit.", + .function = imgui_file_new, + .chord = ImGuiMod_Ctrl | ImGuiKey_N +}); + +const inline ImguiItem IMGUI_FILE_OPEN = ImguiItem +({ + .label = "&Open", + .tooltip = "Open an existing .anm2 file to edit.", + .function = imgui_file_open, + .chord = ImGuiMod_Ctrl | ImGuiKey_O +}); + +const inline ImguiItem IMGUI_FILE_SAVE = ImguiItem +({ + .label = "&Save", + .tooltip = "Saves the current .anm2 file to its path.\nIf no path exists, one can be chosen.", + .function = imgui_file_save, + .chord = ImGuiMod_Ctrl | ImGuiKey_S +}); + +const inline ImguiItem IMGUI_FILE_SAVE_AS = ImguiItem +({ + .label = "S&ave As", + .tooltip = "Saves the current .anm2 file to a chosen path.", + .function = imgui_file_save_as, + .chord = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S +}); + +const inline ImguiItem IMGUI_TASKBAR_PLAYBACK = ImguiItem +({ + .label = "&Playback", + .tooltip = "Opens the playback menu, for configuring playback settings.", + .popup = IMGUI_PLAYBACK_POPUP, + .isSizeToText = true +}); + +const inline ImguiItem IMGUI_PLAYBACK_ALWAYS_LOOP = ImguiItem +({ + .label = "&Always Loop", + .tooltip = "Sets the animation playback to always loop, regardless of the animation's loop setting." +}); + +const inline ImguiItem IMGUI_TASKBAR_WIZARD = ImguiItem +({ + .label = "&Wizard", + .tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", + .popup = IMGUI_WIZARD_POPUP, + .isSizeToText = true +}); + +const inline ImguiItem IMGUI_WIZARD_RECORD_GIF_ANIMATION = ImguiItem +({ + .label = "G&enerate GIF Animation", + .tooltip = "Generates a GIF animation from the current animation.", + .function = imgui_generate_gif_animation +}); + +const inline ImguiItem IMGUI_ANIMATIONS = ImguiItem({IMGUI_ANIMATIONS_LABEL}); + +const inline ImguiItem IMGUI_ANIMATION = ImguiItem +({ + .label = "## Animation Item", + .dragDrop = "## Animation Drag Drop", + .texture = TEXTURE_ANIMATION, + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_ANIMATION_ADD = ImguiItem +({ + .label = "Add", + .tooltip = "Adds a new animation.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_ANIMATION_DUPLICATE = ImguiItem +({ + .label = "Duplicate", + .tooltip = "Duplicates the selected animation, placing it after.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_ANIMATION_MERGE = ImguiItem +({ + .label = "Merge", + .tooltip = "Open the animation merge popup, to merge animations together.", + .popup = IMGUI_MERGE_POPUP, + .popupType = POPUP_CENTER_SCREEN, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_MERGE_ANIMATIONS_CHILD = ImguiItem +({ + .label = "## Merge Animations", + .size = {300, 250}, + .flags = true +}); + +const inline ImguiItem IMGUI_MERGE_ON_CONFLICT_CHILD = ImguiItem +({ + .label = "## Merge On Conflict", + .size = {300, 75}, + .flags = true +}); + +const inline ImguiItem IMGUI_MERGE_ON_CONFLICT = ImguiItem({"On Conflict"}); + +const inline ImguiItem IMGUI_MERGE_APPEND_FRAMES = ImguiItem +({ + .label = "Append Frames ", + .tooltip = "On frame conflict, the merged animation will have the selected animations' frames appended, in top-to-bottom order.", + .value = 0 +}); + +const inline ImguiItem IMGUI_MERGE_REPLACE_FRAMES = ImguiItem +({ + .label = "Replace Frames", + .tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", + .value = 1 +}); + +const inline ImguiItem IMGUI_MERGE_PREPEND_FRAMES = ImguiItem +({ + .label = "Prepend Frames", + .tooltip = "On frame conflict, the merged animation will have the selected animations' frames prepend, in top-to-bottom order.", + .value = 2 +}); + +const inline ImguiItem IMGUI_MERGE_IGNORE = ImguiItem +({ + .label = "Ignore ", + .tooltip = "On frame conflict, the merged animation will ignore the other selected animations' frames.", + .value = 3 +}); + +const inline ImguiItem IMGUI_MERGE_OPTIONS_CHILD = ImguiItem +({ + .label = "## Merge Options", + .size = {300, 35}, + .flags = true +}); + +const inline ImguiItem IMGUI_MERGE_DELETE_ANIMATIONS_AFTER = ImguiItem +({ + .label = "Delete Animations After Merging", + .tooltip = "After merging, the selected animations (besides the original) will be deleted." +}); + +const inline ImguiItem IMGUI_MERGE_CONFIRM = ImguiItem +({ + .label = "Merge", + .tooltip = "Merge the selected animations with the options set.", + .size = {150, 25}, +}); + +const inline ImguiItem IMGUI_MERGE_CANCEL = ImguiItem +({ + .label = "Cancel", + .tooltip = "Cancel merging.", + .size = {150, 25} +}); + +const inline ImguiItem IMGUI_ANIMATION_DEFAULT = ImguiItem +({ + .label = "Default", + .tooltip = "Sets the selected animation to be the default.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_ANIMATION_REMOVE = ImguiItem +({ + .label = "Remove", + .tooltip = "Removes the selected animation.", + .focusWindow = IMGUI_ANIMATIONS_LABEL, + .chord = ImGuiKey_Delete, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_EVENTS = ImguiItem({IMGUI_EVENTS_LABEL}); + +const inline ImguiItem IMGUI_EVENT = ImguiItem +({ + .label = "## Event Item", + .dragDrop = "## Event Drag Drop", + .texture = TEXTURE_EVENT, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_EVENT_ADD = ImguiItem +({ + .label = "Add", + .tooltip = "Adds a new event.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_EVENT_REMOVE = ImguiItem +({ + .label = "Remove", + .tooltip = "Remove the selected event.", + .focusWindow = IMGUI_EVENTS_LABEL, + .chord = ImGuiKey_Delete, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_SPRITESHEETS = ImguiItem({"Spritesheets"}); + +const inline ImguiItem IMGUI_SPRITESHEET_CHILD = ImguiItem +({ + .label = "## Spritesheet", + .size = {0, 175}, + .flags = true +}); + +const inline ImguiItem IMGUI_SPRITESHEET_SELECTABLE = ImguiItem +({ + .label = "## Spritesheet Selectable", + .dragDrop = "## Spritesheet Drag Drop", + .texture = TEXTURE_SPRITESHEET, + .flags = true +}); + +const inline ImguiItem IMGUI_SPRITESHEET_ADD = ImguiItem +({ + .label = "Add", + .tooltip = "Select an image to add as a spritesheet.", + .function = imgui_png_open +}); + +const inline ImguiItem IMGUI_SPRITESHEET_REMOVE = ImguiItem +({ + .label = "Remove", + .tooltip = "Remove the selected spritesheet." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_RELOAD = ImguiItem +({ + .label = "Reload", + .tooltip = "Reload the selected spritesheet." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_REPLACE = ImguiItem +({ + .label = "Replace", + .tooltip = "Replace the selected spritesheet with another." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW = ImguiItem +({ + .label = "Animation Preview", + .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS = ImguiItem +({ + .label = "## Grid Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID = ImguiItem +({ + .label = "Grid", + .tooltip = "Toggles the visiblity of a grid over the animation preview." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_COLOR = ImguiItem +({ + .label = "Color", + .tooltip = "Change the color of the animation preview grid.", + .flags = ImGuiColorEditFlags_NoInputs +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_SIZE = ImguiItem +({ + .label = "Size", + .tooltip = "Change the size of the animation preview grid, in pixels.", + .min = CANVAS_GRID_MIN, + .max = CANVAS_GRID_MAX +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_OFFSET = ImguiItem +({ + .label = "Offset", + .tooltip = "Change the offset of the animation preview grid, in pixels." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_VIEW_SETTINGS = ImguiItem +({ + .label = "## View Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_ZOOM = ImguiItem +({ + .label = "Zoom", + .tooltip = "Change the animation preview's zoom.", + .format = "%.0f", + .min = CANVAS_ZOOM_MIN, + .max = CANVAS_ZOOM_MAX +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_CENTER_VIEW = ImguiItem +({ + .label = "Center View", + .tooltip = "Centers the current view.", + .size = {-FLT_MIN, 0} +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS = ImguiItem +({ + .label = "## Background Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR = ImguiItem +({ + .label = "Background Color", + .tooltip = "Change the background color of the animation preview.", + .flags = ImGuiColorEditFlags_NoInputs +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS = ImguiItem +({ + .label = "## Helper Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_AXIS = ImguiItem +({ + .label = "Axis", + .tooltip = "Toggle the display of the X/Y axes on the animation preview." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_AXIS_COLOR = ImguiItem +({ + .label = "Color", + .tooltip = "Change the color of the animation preview's axes.", + .flags = ImGuiColorEditFlags_NoInputs +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_ROOT_TRANSFORM = ImguiItem +({ + .label = "Root Transform", + .tooltip = "Toggles the root frames's attributes transforming the other items in an animation." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT = ImguiItem +({ + .label = "Show Pivot", + .tooltip = "Toggles the appearance of an icon for each animation item's pivot." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR = ImguiItem +({ + .label = "Spritesheet Editor", + .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS = ImguiItem +({ + .label = "## Grid Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID = ImguiItem +({ + .label = "Grid", + .tooltip = "Toggles the visiblity of a grid over the spritesheet editor." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SNAP = ImguiItem +({ + .label = "Snap", + .tooltip = "Using the crop tool will snap the points to the nearest grid point." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_COLOR = ImguiItem +({ + .label = "Color", + .tooltip = "Change the color of the spritesheet editor grid.", + .flags = ImGuiColorEditFlags_NoInputs +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_SIZE = ImguiItem +({ + .label = "Size", + .tooltip = "Change the size of the spritesheet editor grid, in pixels.", + .min = EDITOR_GRID_MIN, + .max = EDITOR_GRID_MAX +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET = ImguiItem +({ + .label = "Offset", + .tooltip = "Change the offset of the spritesheet editor grid, in pixels." +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_VIEW_SETTINGS = ImguiItem +({ + .label = "## View Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_ZOOM = ImguiItem +({ + .label = "Zoom", + .tooltip = "Change the spritesheet editor's zoom.", + .format = "%.0f", + .min = CANVAS_ZOOM_MIN, + .max = CANVAS_ZOOM_MAX +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW = ImguiItem +({ + .label = "Center View", + .tooltip = "Centers the current view.", + .size = {-FLT_MIN, 0}, +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BACKGROUND_SETTINGS = ImguiItem +({ + .label = "## Background Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR = ImguiItem +({ + .label = "Background Color", + .tooltip = "Change the background color of the spritesheet editor.", + .flags = ImGuiColorEditFlags_NoInputs +}); + +const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_BORDER = ImguiItem +({ + .label = "Border", + .tooltip = "Toggle a border appearing around the selected spritesheet." +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES = ImguiItem({"Frame Properties"}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_POSITION = ImguiItem +({ + .label = "Position", + .tooltip = "Change the position of the selected frame.", + .format = "%.0f", + .isUndoable = true, + .speed = 0.25f, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_CROP = ImguiItem +({ + .label = "Crop", + .tooltip = "Change the crop position of the selected frame.", + .format = "%.0f", + .isUndoable = true, + .speed = 0.25f, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_SIZE = ImguiItem +({ + .label = "Size", + .tooltip = "Change the size of the crop of the selected frame.", + .format = "%.0f", + .isUndoable = true, + .speed = 0.25f +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_PIVOT = ImguiItem +({ + .label = "Pivot", + .tooltip = "Change the pivot of the selected frame.", + .format = "%.0f", + .isUndoable = true, + .speed = 0.25f +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_SCALE = ImguiItem +({ + .label = "Scale", + .tooltip = "Change the scale of the selected frame.", + .format = "%.1f", + .isUndoable = true, + .speed = 0.25f +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_ROTATION = ImguiItem +({ + .label = "Rotation", + .tooltip = "Change the rotation of the selected frame.", + .format = "%.1f", + .isUndoable = true, + .speed = 0.25f +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_DURATION = ImguiItem +({ + .label = "Duration", + .tooltip = "Change the duration of the selected frame.", + .isUndoable = true, + .min = 1, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_TINT = ImguiItem +({ + .label = "Tint", + .tooltip = "Change the tint of the selected frame.", + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_COLOR_OFFSET = ImguiItem +({ + .label = "Color Offset", + .tooltip = "Change the color offset of the selected frame.", + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_VISIBLE = ImguiItem +({ + .label = "Visible", + .tooltip = "Toggles the visibility of the selected frame.", + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_INTERPOLATED = ImguiItem +({ + .label = "Interpolation", + .tooltip = "Toggles the interpolation of the selected frame.", + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_FLIP_X = ImguiItem +({ + .label = "Flip X", + .tooltip = "Change the sign of the X scale, to cheat flipping the layer horizontally.\n(Anm2 doesn't support flipping directly)", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_FLIP_Y = ImguiItem +({ + .label = "Flip Y", + .tooltip = "Change the sign of the Y scale, to cheat flipping the layer vertically.\n(Anm2 doesn't support flipping directly)", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_EVENT = ImguiItem +({ + .label = "Event", + .tooltip = "Change the event the trigger uses.\nNOTE: This sets the event ID, not the event. If the events change IDs, then this will need to be changed.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_FRAME_PROPERTIES_AT_FRAME = ImguiItem +({ + .label = "At Frame", + .tooltip = "Change the frame where the trigger occurs.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TOOLS = ImguiItem({"Tools"}); + +const inline ImguiItem IMGUI_TOOL_PAN = ImguiItem +({ + .label = "## Pan", + .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.", + .function = imgui_tool_pan_set, + .chord = ImGuiKey_P, + .texture = TEXTURE_PAN, +}); + +const inline ImguiItem IMGUI_TOOL_MOVE = ImguiItem +({ + .label = "## Move", + .tooltip = "Use the move tool.\nWill move the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + .function = imgui_tool_move_set, + .chord = ImGuiKey_M, + .texture = TEXTURE_MOVE, +}); + +const inline ImguiItem IMGUI_TOOL_ROTATE = ImguiItem +({ + .label = "## Rotate", + .tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + .function = imgui_tool_rotate_set, + .chord = ImGuiKey_R, + .texture = TEXTURE_ROTATE, +}); + +const inline ImguiItem IMGUI_TOOL_SCALE = ImguiItem +({ + .label = "## Scale", + .tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", + .function = imgui_tool_scale_set, + .chord = ImGuiKey_S, + .texture = TEXTURE_SCALE, +}); + +const inline ImguiItem IMGUI_TOOL_CROP = ImguiItem +({ + .label = "## Crop", + .tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\n(Spritesheet Editor only.)", + .function = imgui_tool_crop_set, + .chord = ImGuiKey_C, + .texture = TEXTURE_CROP, +}); + +const inline ImguiItem IMGUI_TOOL_DRAW = ImguiItem +({ + .label = "## Draw", + .tooltip = "Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)", + .function = imgui_tool_draw_set, + .chord = ImGuiKey_B, + .texture = TEXTURE_DRAW, +}); + +const inline ImguiItem IMGUI_TOOL_ERASE = ImguiItem +({ + .label = "## Erase", + .tooltip = "Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)", + .function = imgui_tool_erase_set, + .chord = ImGuiKey_E, + .texture = TEXTURE_ERASE, +}); + +const inline ImguiItem IMGUI_TOOL_COLOR_PICKER = ImguiItem +({ + .label = "## Color Picker", + .tooltip = "Selects a color from anywhere on the screen, to be used for drawing.", + .function = imgui_tool_color_picker_set, + .chord = ImGuiKey_W, + .texture = TEXTURE_COLOR_PICKER, +}); + +const inline ImguiItem IMGUI_TOOL_UNDO = ImguiItem +({ + .label = "## Undo", + .tooltip = "Undoes the last action.", + .function = imgui_undo, + .chord = ImGuiKey_Z, + .texture = TEXTURE_UNDO +}); + +const inline ImguiItem IMGUI_TOOL_REDO = ImguiItem +({ + .label = "## Redo", + .tooltip = "Redoes the last action.", + .function = imgui_redo, + .chord = ImGuiMod_Shift + ImGuiKey_Z, + .texture = TEXTURE_REDO +}); + +const inline ImguiItem IMGUI_TOOL_COLOR = ImguiItem +({ + .label = "## Color", + .tooltip = "Set the color, to be used by the draw tool.", + .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_TIMELINE = ImguiItem +({ + .label = IMGUI_TIMELINE_LABEL, + .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_TIMELINE_CHILD = ImguiItem +({ + .label = "## Timeline Child", + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_HEADER = ImguiItem +({ + .label = "## Timeline Header", + .size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, + .flagsAlt = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEMS_CHILD = ImguiItem +({ + .label = "## Timeline Items", + .size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, + .flagsAlt = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_FRAMES_CHILD = ImguiItem +({ + .label = "## Timeline Item Frames", + .size = {0, IMGUI_TIMELINE_FRAME_SIZE.y}, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_FRAMES_CHILD = ImguiItem +({ + .label = "## Timeline Frames", + .size = {-1, -1}, + .flagsAlt = ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoScrollWithMouse +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM = ImguiItem +({ + .label = "## Timeline Item", + .size = IMGUI_TIMELINE_ITEM_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_ROOT = ImguiItem +({ + .label = "## Root Item", + .color = {{0.010, 0.049, 0.078, 1.0f}}, + .size = IMGUI_TIMELINE_ITEM_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_LAYER = ImguiItem +({ + .label = "## Layer Item", + .color = {{0.098, 0.039, 0.020, 1.0f}}, + .size = IMGUI_TIMELINE_ITEM_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_NULL = ImguiItem +({ + .label = "## Null Item", + .color = {{0.020, 0.049, 0.000, 1.0f}}, + .size = IMGUI_TIMELINE_ITEM_SIZE, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_TRIGGERS = ImguiItem +({ + .label = "## Triggers Item", + .color = {{0.078, 0.020, 0.029, 1.0f}}, + .size = IMGUI_TIMELINE_ITEM_SIZE, + .flags = true +}); + +const inline ImguiItem* IMGUI_TIMELINE_ITEMS[ANM2_COUNT] +{ + &IMGUI_TIMELINE_ITEM, + &IMGUI_TIMELINE_ITEM_ROOT, + &IMGUI_TIMELINE_ITEM_LAYER, + &IMGUI_TIMELINE_ITEM_NULL, + &IMGUI_TIMELINE_ITEM_TRIGGERS +}; + +const inline ImguiItem IMGUI_TIMELINE_ITEM_SELECTABLE = ImguiItem +({ + .label = "## Selectable", + .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_ROOT_SELECTABLE = ImguiItem +({ + .label = "Root", + .tooltip = "The root item of an animation.\nChanging its properties will transform the rest of the animation.", + .texture = TEXTURE_ROOT, + .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE = ImguiItem +({ + .label = "## Layer Selectable", + .tooltip = "A layer item.\nA graphical item within the animation.", + .dragDrop = "## Layer Drag Drop", + .texture = TEXTURE_LAYER, + .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_NULL_SELECTABLE = ImguiItem +({ + .label = "## Null Selectable", + .tooltip = "A null item.\nAn invisible item within the animation that is accessible via a game engine.", + .dragDrop = "## Null Drag Drop", + .texture = TEXTURE_NULL, + .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE = ImguiItem +({ + .label = "Triggers", + .tooltip = "The animation's triggers.\nWill fire based on an event.", + .texture = TEXTURE_TRIGGERS, + .size = IMGUI_TIMELINE_ITEM_SELECTABLE_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_VISIBLE = ImguiItem +({ + .label = "## Visible", + .tooltip = "The item is visible.\nPress to set to invisible.", + .texture = TEXTURE_VISIBLE, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_INVISIBLE = ImguiItem +({ + .label = "## Invisible", + .tooltip = "The item is invisible.\nPress to set to visible.", + .texture = TEXTURE_INVISIBLE, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_SHOW_RECT = ImguiItem +({ + .label = "## Show Rect", + .tooltip = "The rect is shown.\nPress to hide rect.", + .texture = TEXTURE_SHOW_RECT, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ITEM_HIDE_RECT = ImguiItem +({ + .label = "## Invisible", + .tooltip = "The rect is hidden.\nPress to show rect.", + .texture = TEXTURE_HIDE_RECT, + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_SPRITESHEET_ID = ImguiItem +({ + .label = "## Spritesheet ID", + .tooltip = "Change the spritesheet ID this item uses.", + .texture = TEXTURE_SPRITESHEET, + .isUndoable = true, + .size = {32, 0}, +}); + +const inline ImguiItem IMGUI_TIMELINE_FRAME = ImguiItem +({ + .label = "## Frame", + .size = IMGUI_TIMELINE_FRAME_SIZE, + .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, + .border = IMGUI_TIMELINE_FRAME_BORDER, +}); + +const inline ImguiItem IMGUI_TIMELINE_ROOT_FRAME = ImguiItem +({ + .label = "## Root Frame", + .color = {{0.020, 0.294, 0.569, 0.5}, {0.471, 0.882, 1.000, 0.75}, {0.314, 0.588, 0.843, 0.75}}, + .size = IMGUI_TIMELINE_FRAME_SIZE, + .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, + .border = IMGUI_TIMELINE_FRAME_BORDER, +}); + +const inline ImguiItem IMGUI_TIMELINE_LAYER_FRAME = ImguiItem +({ + .label = "## Layer Frame", + .dragDrop = "## Layer Frame Drag Drop", + .color = {{0.529, 0.157, 0.000, 0.5}, {1.000, 0.618, 0.324, 0.75}, {0.882, 0.412, 0.216, 0.75}}, + .size = IMGUI_TIMELINE_FRAME_SIZE, + .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, + .border = IMGUI_TIMELINE_FRAME_BORDER, +}); + +const inline ImguiItem IMGUI_TIMELINE_NULL_FRAME = ImguiItem +({ + .label = "## Null Frame", + .dragDrop = "## Null Frame Drag Drop", + .color = {{0.137, 0.353, 0.000, 0.5}, {0.646, 0.971, 0.441, 0.75}, {0.431, 0.647, 0.294, 0.75}}, + .size = IMGUI_TIMELINE_FRAME_SIZE, + .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, + .border = IMGUI_TIMELINE_FRAME_BORDER, +}); + +const inline ImguiItem IMGUI_TIMELINE_TRIGGERS_FRAME = ImguiItem +({ + .label = "## Triggers Frame", + .color = {{0.529, 0.118, 0.196, 0.5}, {1.000, 0.618, 0.735, 0.75}, {0.804, 0.412, 0.490, 0.75}}, + .size = IMGUI_TIMELINE_FRAME_SIZE, + .contentOffset = IMGUI_TIMELINE_FRAME_CONTENT_OFFSET, + .border = IMGUI_TIMELINE_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_ADD_ELEMENT = ImguiItem +({ + .label = "Add Element", + .tooltip = "Adds an element (layer or null) to the animation.", + .popup = IMGUI_TIMELINE_ADD_ELEMENT_POPUP +}); + +const inline ImguiItem IMGUI_TIMELINE_ADD_ELEMENT_LAYER = ImguiItem +({ + .label = "Layer", + .tooltip = "Adds a layer element.\nA layer element is a primary graphical element, using a spritesheet.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ADD_ELEMENT_NULL = ImguiItem +({ + .label = "Null", + .tooltip = "Adds a null element.\nA null element is an invisible element, often accessed by the game engine.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_REMOVE_ELEMENT = ImguiItem +({ + .label = "Remove Element", + .tooltip = "Removes an element (layer or null) from the animation." +}); + +const inline ImguiItem IMGUI_TIMELINE_PLAY = ImguiItem +({ + .label = "|> Play", + .tooltip = "Play the current animation, if paused." +}); + +const inline ImguiItem IMGUI_TIMELINE_PAUSE = ImguiItem +({ + .label = "|| Pause", + .tooltip = "Pause the current animation, if playing." +}); + +const inline ImguiItem IMGUI_TIMELINE_ADD_FRAME = ImguiItem +({ + .label = "Add Frame", + .tooltip = "Adds a frame to the selected animation item.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_REMOVE_FRAME = ImguiItem +({ + .label = "Remove Frame", + .tooltip = "Removes the selected frame from the selected animation item.", + .focusWindow = IMGUI_TIMELINE_LABEL, + .chord = ImGuiKey_Delete, + .isUndoable = true, +}); + +const inline ImguiItem IMGUI_TIMELINE_FIT_ANIMATION_LENGTH = ImguiItem +({ + .label = "Fit Animation Length", + .tooltip = "Sets the animation's length to the latest frame.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ANIMATION_LENGTH = ImguiItem +({ + .label = "Animation Length", + .tooltip = "Sets the animation length.\n(Will not change frames.)", + .isUndoable = true, + .size = {100, 0}, + .min = ANM2_FRAME_NUM_MIN, + .max = ANM2_FRAME_NUM_MAX +}); + +const inline ImguiItem IMGUI_TIMELINE_FPS = ImguiItem +({ + .label = "FPS", + .tooltip = "Sets the animation's frames per second (its speed).", + .isUndoable = true, + .size = {100, 0}, + .min = ANM2_FPS_MIN, + .max = ANM2_FPS_MAX +}); + +const inline ImguiItem IMGUI_TIMELINE_LOOP = ImguiItem +({ + .label = "Loop", + .tooltip = "Toggles the animation looping.", + .isUndoable = true +}); + +const inline ImguiItem IMGUI_TIMELINE_CREATED_BY = ImguiItem +({ + .label = "Author", + .tooltip = "Sets the author of the animation.", + .isUndoable = true, + .size = {150, 0}, + .max = ANM2_STRING_MAX +}); + +const inline ImguiItem IMGUI_TIMELINE_CREATED_ON = ImguiItem({"Created on: "}); +const inline ImguiItem IMGUI_TIMELINE_VERSION = ImguiItem({"Version: "}); + +const inline ImguiItem IMGUI_RECORDING = ImguiItem +({ + .label = "Recording...", + .texture = TEXTURE_RECORD +}); + +const inline ImguiItem IMGUI_CUT = ImguiItem +({ + .label = "Cut", + .tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", + .function = imgui_cut, + .chord = ImGuiMod_Ctrl | ImGuiKey_X +}); + +const inline ImguiItem IMGUI_COPY = ImguiItem +({ + .label = "Copy", + .tooltip = "Copies the currently selected contextual element to the clipboard.", + .function = imgui_copy, + .chord = ImGuiMod_Ctrl | ImGuiKey_C +}); + +const inline ImguiItem IMGUI_PASTE = ImguiItem +({ + .label = "Paste", + .tooltip = "Pastes the currently selection contextual element from the clipboard.", + .function = imgui_paste, + .chord = ImGuiMod_Ctrl | ImGuiKey_V +}); + +const inline ImguiItem IMGUI_RENAMABLE = ImguiItem +({ + .label = "## Renaming", + .tooltip = "Rename the selected item.", + .max = 255, + .flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue +}); + +const inline ImguiItem IMGUI_CHANGEABLE = ImguiItem +({ + .label = "## Changing", + .tooltip = "Change the selected item's value.", + .step = 0 +}); + +const inline ImguiItem IMGUI_EXIT_CONFIRMATION = ImguiItem +({ + .label = "Unsaved changes will be lost!\nAre you sure you want to exit?", + .popup = IMGUI_EXIT_CONFIRMATION_POPUP +}); + +const inline ImguiItem IMGUI_POPUP_YES_BUTTON = ImguiItem +({ + .label = "Yes", + .size = {120, 0} +}); + +const inline ImguiItem IMGUI_POPUP_NO_BUTTON = ImguiItem +({ + .label = "No", + .size = {120, 0} +}); + +void imgui_init ( Imgui* self, Dialog* dialog, Resources* resources, - Input* input, Anm2* anm2, Anm2Reference* reference, - f32* time, Editor* editor, Preview* preview, Settings* settings, - Tool* tool, Snapshots* snapshots, + Clipboard* clipboard, SDL_Window* window, SDL_GLContext* glContext ); diff --git a/src/input.cpp b/src/input.cpp deleted file mode 100644 index 906143b..0000000 --- a/src/input.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "input.h" - -static void _mouse_tick(Mouse* self); - -// Ticks mouse -static void -_mouse_tick(Mouse* self) -{ - s32 state; - - std::memcpy(&self->previous, &self->current, sizeof(self->current)); - std::memset(&self->current, '\0', sizeof(self->current)); - - state = SDL_GetMouseState(NULL, NULL); - - if ((state & SDL_BUTTON_LMASK) != 0) - { - self->current[MOUSE_LEFT] = true; - } - - if ((state & SDL_BUTTON_RMASK) != 0) - { - self->current[MOUSE_RIGHT] = true; - } - - SDL_GetMouseState(&self->position.x, &self->position.y); - - self->delta = self->position - self->oldPosition; - self->oldPosition = self->position; -} - -// Ticks keyboard -static void -_keyboard_tick(Keyboard* self) -{ - const bool* state; - - std::memcpy(&self->previous, &self->current, sizeof(self->previous)); - std::memset(&self->current, '\0', sizeof(self->current)); - - state = SDL_GetKeyboardState(NULL); - - std::memcpy(&self->current, state, KEY_COUNT); -} - -// Checks to see if the given mouse button has been pressed -bool -mouse_press(Mouse* self, MouseType type) -{ - return (self->current[type] && !self->previous[type]); -} - -// Checks to see if the given mouse button is held -bool -mouse_held(Mouse* self, MouseType type) -{ - return (self->current[type] && self->previous[type]); -} - -// Checks to see if the given mouse button is released -bool -mouse_release(Mouse* self, MouseType type) -{ - return (!self->current[type] && self->previous[type]); -} - -// Checks to see if the given key is pressed -bool -key_press(Keyboard* self, KeyType type) -{ - return (self->current[type] && !self->previous[type]); -} - -// Checks to see if the given key is held -bool -key_held(Keyboard* self, KeyType type) -{ - return (self->current[type] && self->previous[type]); -} - -// Checks to see if the given key is released -bool -key_release(Keyboard* self, KeyType type) -{ - return (!self->current[type] && self->previous[type]); -} - -// Checks to see if the given input is pressed -bool -input_press(Input* self, InputType type) -{ - for (KeyType key : INPUT_KEYS[type]) - if (!key_press(&self->keyboard, (key))) - return false; - return true; -} - -// Checks to see if the given input is held -bool -input_held(Input* self, InputType type) -{ - for (KeyType key : INPUT_KEYS[type]) - if (!key_held(&self->keyboard, (key))) - return false; - return true; -} - -// Checks to see if the given input is held -bool -input_release(Input* self, InputType type) -{ - for (size_t i = 0; i < INPUT_KEYS[type].size(); i++) { - auto& key = INPUT_KEYS[type][i]; - //below is some hackery to ensure that multikey shortcuts work... - //ideally would split this into a separate function or rename this? - if (i != INPUT_KEYS[type].size()) { - if (!key_held(&self->keyboard, key)) return false; - } - // last key in the shortcut chain, so in a mod1-mod2-...-key it won't be a mod key - // todo: introduce a list of modifer keys as a more reliable solution - else if (!key_release(&self->keyboard, (key))) return false; - }; - return true; -} - -// Ticks input -void -input_tick(Input* self) -{ - _mouse_tick(&self->mouse); - _keyboard_tick(&self->keyboard); -} diff --git a/src/input.h b/src/input.h deleted file mode 100644 index 6107424..0000000 --- a/src/input.h +++ /dev/null @@ -1,310 +0,0 @@ -#pragma once - -#include "COMMON.h" - -enum MouseType -{ - MOUSE_LEFT, - MOUSE_RIGHT, - MOUSE_COUNT -}; - -enum KeyType -{ - KEY_UNKNOWN = 0, - KEY_UNKNOWN_TWO = 1, - KEY_UNKNOWN_THREE = 2, - KEY_UNKNOWN_FOUR = 3, - KEY_A = 4, - KEY_B = 5, - KEY_C = 6, - KEY_D = 7, - KEY_E = 8, - KEY_F = 9, - KEY_G = 10, - KEY_H = 11, - KEY_I = 12, - KEY_J = 13, - KEY_K = 14, - KEY_L = 15, - KEY_M = 16, - KEY_N = 17, - KEY_O = 18, - KEY_P = 19, - KEY_Q = 20, - KEY_R = 21, - KEY_S = 22, - KEY_T = 23, - KEY_U = 24, - KEY_V = 25, - KEY_W = 26, - KEY_X = 27, - KEY_Y = 28, - KEY_Z = 29, - KEY_1 = 30, - KEY_2 = 31, - KEY_3 = 32, - KEY_4 = 33, - KEY_5 = 34, - KEY_6 = 35, - KEY_7 = 36, - KEY_8 = 37, - KEY_9 = 38, - KEY_0 = 39, - KEY_RETURN = 40, - KEY_ESCAPE = 41, - KEY_BACKSPACE = 42, - KEY_TAB = 43, - KEY_SPACE = 44, - KEY_MINUS = 45, - KEY_EQUALS = 46, - KEY_LEFTBRACKET = 47, - KEY_RIGHTBRACKET = 48, - KEY_BACKSLASH = 49, - KEY_NONUSHASH = 50, - KEY_SEMICOLON = 51, - KEY_APOSTROPHE = 52, - KEY_GRAVE = 53, - KEY_COMMA = 54, - KEY_PERIOD = 55, - KEY_SLASH = 56, - KEY_CAPSLOCK = 57, - KEY_F1 = 58, - KEY_F2 = 59, - KEY_F3 = 60, - KEY_F4 = 61, - KEY_F5 = 62, - KEY_F6 = 63, - KEY_F7 = 64, - KEY_F8 = 65, - KEY_F9 = 66, - KEY_F10 = 67, - KEY_F11 = 68, - KEY_F12 = 69, - KEY_PRINTSCREEN = 70, - KEY_SCROLLLOCK = 71, - KEY_PAUSE = 72, - KEY_INSERT = 73, - KEY_HOME = 74, - KEY_PAGEUP = 75, - KEY_DELETE = 76, - KEY_END = 77, - KEY_PAGEDOWN = 78, - KEY_RIGHT = 79, - KEY_LEFT = 80, - KEY_DOWN = 81, - KEY_UP = 82, - KEY_NUMLOCKCLEAR = 83, - KEY_KP_DIVIDE = 84, - KEY_KP_MULTIPLY = 85, - KEY_KP_MINUS = 86, - KEY_KP_PLUS = 87, - KEY_KP_ENTER = 88, - KEY_KP_1 = 89, - KEY_KP_2 = 90, - KEY_KP_3 = 91, - KEY_KP_4 = 92, - KEY_KP_5 = 93, - KEY_KP_6 = 94, - KEY_KP_7 = 95, - KEY_KP_8 = 96, - KEY_KP_9 = 97, - KEY_KP_0 = 98, - KEY_KP_PERIOD = 99, - KEY_NONUSBACKSLASH = 100, - KEY_APPLICATION = 101, - KEY_POWER = 102, - KEY_KP_EQUALS = 103, - KEY_F13 = 104, - KEY_F14 = 105, - KEY_F15 = 106, - KEY_F16 = 107, - KEY_F17 = 108, - KEY_F18 = 109, - KEY_F19 = 110, - KEY_F20 = 111, - KEY_F21 = 112, - KEY_F22 = 113, - KEY_F23 = 114, - KEY_F24 = 115, - KEY_EXECUTE = 116, - KEY_HELP = 117, - KEY_MENU = 118, - KEY_SELECT = 119, - KEY_STOP = 120, - KEY_AGAIN = 121, - KEY_UNDO = 122, - KEY_CUT = 123, - KEY_COPY = 124, - KEY_PASTE = 125, - KEY_FIND = 126, - KEY_MUTE = 127, - KEY_VOLUMEUP = 128, - KEY_VOLUMEDOWN = 129, - KEY_LOCKINGCAPSLOCK = 130, - KEY_LOCKINGNUMLOCK = 131, - KEY_LOCKINGSCROLLLOCK = 132, - KEY_KP_COMMA = 133, - KEY_KP_EQUALSAS400 = 134, - KEY_INTERNATIONAL1 = 135, - KEY_INTERNATIONAL2 = 136, - KEY_INTERNATIONAL3 = 137, - KEY_INTERNATIONAL4 = 138, - KEY_INTERNATIONAL5 = 139, - KEY_INTERNATIONAL6 = 140, - KEY_INTERNATIONAL7 = 141, - KEY_INTERNATIONAL8 = 142, - KEY_INTERNATIONAL9 = 143, - KEY_LANG1 = 144, - KEY_LANG2 = 145, - KEY_LANG3 = 146, - KEY_LANG4 = 147, - KEY_LANG5 = 148, - KEY_LANG6 = 149, - KEY_LANG7 = 150, - KEY_LANG8 = 151, - KEY_LANG9 = 152, - KEY_ALTERASE = 153, - KEY_SYSREQ = 154, - KEY_CANCEL = 155, - KEY_CLEAR = 156, - KEY_PRIOR = 157, - KEY_RETURN2 = 158, - KEY_SEPARATOR = 159, - KEY_OUT = 160, - KEY_OPER = 161, - KEY_CLEARAGAIN = 162, - KEY_CRSEL = 163, - KEY_EXSEL = 164, - KEY_KP_00 = 176, - KEY_KP_000 = 177, - KEY_THOUSANDSSEPARATOR = 178, - KEY_DECIMALSEPARATOR = 179, - KEY_CURRENCYUNIT = 180, - KEY_CURRENCYSUBUNIT = 181, - KEY_KP_LEFTPAREN = 182, - KEY_KP_RIGHTPAREN = 183, - KEY_KP_LEFTBRACE = 184, - KEY_KP_RIGHTBRACE = 185, - KEY_KP_TAB = 186, - KEY_KP_BACKSPACE = 187, - KEY_KP_A = 188, - KEY_KP_B = 189, - KEY_KP_C = 190, - KEY_KP_D = 191, - KEY_KP_E = 192, - KEY_KP_F = 193, - KEY_KP_XOR = 194, - KEY_KP_POWER = 195, - KEY_KP_PERCENT = 196, - KEY_KP_LESS = 197, - KEY_KP_GREATER = 198, - KEY_KP_AMPERSAND = 199, - KEY_KP_DBLAMPERSAND = 200, - KEY_KP_VERTICALBAR = 201, - KEY_KP_DBLVERTICALBAR = 202, - KEY_KP_COLON = 203, - KEY_KP_HASH = 204, - KEY_KP_SPACE = 205, - KEY_KP_AT = 206, - KEY_KP_EXCLAM = 207, - KEY_KP_MEMSTORE = 208, - KEY_KP_MEMRECALL = 209, - KEY_KP_MEMCLEAR = 210, - KEY_KP_MEMADD = 211, - KEY_KP_MEMSUBTRACT = 212, - KEY_KP_MEMMULTIPLY = 213, - KEY_KP_MEMDIVIDE = 214, - KEY_KP_PLUSMINUS = 215, - KEY_KP_CLEAR = 216, - KEY_KP_CLEARENTRY = 217, - KEY_KP_BINARY = 218, - KEY_KP_OCTAL = 219, - KEY_KP_DECIMAL = 220, - KEY_KP_HEXADECIMAL = 221, - KEY_LCTRL = 224, - KEY_LSHIFT = 225, - KEY_LALT = 226, - KEY_LGUI = 227, - KEY_RCTRL = 228, - KEY_RSHIFT = 229, - KEY_RALT = 230, - KEY_RGUI = 231, - KEY_COUNT = 255 -}; - -enum InputType -{ - INPUT_MOD, - INPUT_DELETE, - INPUT_PLAY, - INPUT_PAN, - INPUT_MOVE, - INPUT_ROTATE, - INPUT_SCALE, - INPUT_CROP, - INPUT_LEFT, - INPUT_RIGHT, - INPUT_UP, - INPUT_DOWN, - INPUT_ZOOM_IN, - INPUT_ZOOM_OUT, - INPUT_UNDO, - INPUT_REDO, - INPUT_SAVE, - INPUT_COUNT -}; - -const std::vector INPUT_KEYS[INPUT_COUNT] = -{ - { KEY_LSHIFT }, - { KEY_DELETE }, - { KEY_SPACE }, - { KEY_P }, - { KEY_M }, - { KEY_R }, - { KEY_S }, - { KEY_C }, - { KEY_LEFT }, - { KEY_RIGHT }, - { KEY_UP }, - { KEY_DOWN }, - { KEY_1 }, - { KEY_2 }, - { KEY_Z }, - { KEY_Y }, - { KEY_LCTRL , KEY_S} -}; - -struct Keyboard -{ - bool current[KEY_COUNT] = { 0 }; - bool previous[KEY_COUNT] = { 0 }; -}; - -struct Mouse -{ - bool current[MOUSE_COUNT]; - bool previous[MOUSE_COUNT]; - vec2 position = {-1, -1}; - vec2 oldPosition = {-1, -1}; - vec2 delta = {-1, -1}; - s32 wheelDeltaY = 0; -}; - -struct Input -{ - Mouse mouse; - Keyboard keyboard; -}; - -bool mouse_press(Mouse* self, MouseType type); -bool mouse_held(Mouse* self, MouseType type); -bool mouse_release(Mouse* self, MouseType type); -bool key_press(Keyboard* self, KeyType type); -bool key_held(Keyboard* self, KeyType type); -bool key_release(Keyboard* self, KeyType type); -bool input_press(Input* self, InputType type); -bool input_held(Input* self, InputType type); -bool input_release(Input* self, InputType type); -void input_tick(Input* self); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 70699bf..0ca3856 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,5 @@ +// Main function + #include "main.h" s32 @@ -5,12 +7,8 @@ main(s32 argc, char* argv[]) { State state; - // If anm2 given on command line, set state argument to that (will be loaded) if (argc > 0 && argv[1]) - { state.argument = argv[1]; - state.isArgument = true; - } init(&state); diff --git a/src/preview.cpp b/src/preview.cpp index 49a3cd8..f3b3a37 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -1,15 +1,12 @@ +// Handles the rendering of the animation preview + #include "preview.h" -static void _preview_axis_set(Preview* self); -static s32 _preview_grid_set(Preview* self); - -// Sets the preview's axis (lines across x/y) -static void -_preview_axis_set(Preview* self) +static void _preview_axis_set(Preview* self) { glBindVertexArray(self->axisVAO); glBindBuffer(GL_ARRAY_BUFFER, self->axisVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(PREVIEW_AXIS_VERTICES), PREVIEW_AXIS_VERTICES, GL_STATIC_DRAW); + 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); @@ -17,16 +14,14 @@ _preview_axis_set(Preview* self) glBindVertexArray(0); } -// Sets and returns the grid's vertices -static s32 -_preview_grid_set(Preview* self) +static s32 _preview_grid_set(Preview* self) { std::vector vertices; s32 verticalLineCount = (s32)(PREVIEW_SIZE.x / MIN(self->settings->previewGridSizeX, PREVIEW_GRID_MIN)); s32 horizontalLineCount = (s32)(PREVIEW_SIZE.y / MIN(self->settings->previewGridSizeY, PREVIEW_GRID_MIN)); - /* Vertical */ + // Vertical for (s32 i = 0; i <= verticalLineCount; i++) { s32 x = i * self->settings->previewGridSizeX - self->settings->previewGridOffsetX; @@ -38,7 +33,7 @@ _preview_grid_set(Preview* self) vertices.push_back(1.0f); } - /* Horizontal */ + // Horizontal for (s32 i = 0; i <= horizontalLineCount; i++) { s32 y = i * self->settings->previewGridSizeY - self->settings->previewGridOffsetY; @@ -60,13 +55,10 @@ _preview_grid_set(Preview* self) return (s32)vertices.size(); } -// Initializes preview -void -preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, f32* time, Resources* resources, Settings* settings) +void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { self->anm2 = anm2; self->reference = reference; - self->time = time; self->resources = resources; self->settings = settings; @@ -77,7 +69,7 @@ preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, f32* time, Res glGenTextures(1, &self->texture); glBindTexture(GL_TEXTURE_2D, self->texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)PREVIEW_SIZE.x, (s32)PREVIEW_SIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)PREVIEW_SIZE.x, (s32)PREVIEW_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); @@ -118,7 +110,7 @@ preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, f32* time, Res glBindVertexArray(self->textureVAO); glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, NULL, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(f32) * 4 * 4, nullptr, GL_DYNAMIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self->textureEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_STATIC_DRAW); @@ -137,9 +129,7 @@ preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, f32* time, Res _preview_grid_set(self); } -// Ticks preview -void -preview_tick(Preview* self) +void preview_tick(Preview* self) { self->settings->previewZoom = CLAMP(self->settings->previewZoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); @@ -150,13 +140,13 @@ preview_tick(Preview* self) { if (self->isPlaying) { - *self->time += (f32)self->anm2->fps / TICK_RATE; + self->time += (f32)self->anm2->fps / TICK_RATE; // If looping, return back to 0; if not, stop at length - if (*self->time >= (f32)animation->frameNum - 1) + if (self->time >= (f32)animation->frameNum - 1) { if (self->settings->playbackIsLoop && !self->isRecording) - *self->time = 0.0f; + self->time = 0.0f; else self->isPlaying = false; } @@ -164,13 +154,11 @@ preview_tick(Preview* self) // Make sure to clamp time within appropriate range if (!self->isPlaying) - *self->time = CLAMP(*self->time, 0.0f, (f32)animation->frameNum - 1); + self->time = CLAMP(self->time, 0.0f, (f32)animation->frameNum - 1); } } -// Draws preview -void -preview_draw(Preview* self) +void preview_draw(Preview* self) { GLuint shaderLine = self->resources->shaders[SHADER_LINE]; GLuint shaderTexture = self->resources->shaders[SHADER_TEXTURE]; @@ -263,15 +251,17 @@ preview_draw(Preview* self) { Anm2Frame rootFrame; Anm2Frame frame; - anm2_frame_from_time(self->anm2, &rootFrame, Anm2Reference{animationID, ANM2_ROOT, 0, 0}, *self->time); + anm2_frame_from_time(self->anm2, &rootFrame, Anm2Reference{animationID, ANM2_ROOT, 0, 0}, self->time); // Layers - for (auto & [id, layerAnimation] : animation->layerAnimations) + for (auto [i, id] : self->anm2->layerMap) { + Anm2Item& layerAnimation = animation->layerAnimations[id]; + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) continue; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id, 0}, *self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id, 0}, self->time); if (!frame.isVisible) continue; @@ -372,7 +362,7 @@ preview_draw(Preview* self) if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) continue; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id, 0}, *self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id, 0}, self->time); if (!frame.isVisible) continue; @@ -423,7 +413,7 @@ preview_draw(Preview* self) if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) continue; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id, 0}, *self->time); + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id, 0}, self->time); if (!frame.isVisible) continue; @@ -432,7 +422,7 @@ preview_draw(Preview* self) glm::mat4 nullTransform = previewTransform; - TextureType textureType = null->isShowRect ? TEXTURE_SQUARE : TEXTURE_TARGET; + TextureType textureType = null->isShowRect ? TEXTURE_UNINTERPOLATED : TEXTURE_TARGET; glm::vec2 size = null->isShowRect ? PREVIEW_POINT_SIZE : PREVIEW_TARGET_SIZE; glm::vec2 pos = self->settings->previewIsRootTransform ? frame.position + (rootFrame.position) - (size / 2.0f) : frame.position - (size / 2.0f); @@ -494,12 +484,11 @@ preview_draw(Preview* self) } } - // Manage recording + /* if (self->isRecording && animation) { if (recordFrameIndex == 0) { - // Create frames directory, if it exists if ( std::filesystem::exists(STRING_PREVIEW_FRAMES_DIRECTORY) && @@ -548,27 +537,27 @@ preview_draw(Preview* self) } else { - if (*self->time >= (f32)animation->frameNum - 1) + if (self->time >= (f32)animation->frameNum - 1) { self->isRecording = false; self->isPlaying = false; recordFrameIndex = 0; recordFrameTimeNext = 0; - *self->time = 0.0f; + self->time = 0.0f; } - else if (*self->time >= recordFrameTimeNext) + else if (self->time >= recordFrameTimeNext) { isRecordThisFrame = true; - recordFrameTimeNext = *self->time + (f32)self->anm2->fps / TICK_RATE; + recordFrameTimeNext = self->time + (f32)self->anm2->fps / TICK_RATE; } } } + */ glBindFramebuffer(GL_FRAMEBUFFER, 0); } -void -preview_free(Preview* self) +void preview_free(Preview* self) { glDeleteTextures(1, &self->texture); glDeleteFramebuffers(1, &self->fbo); diff --git a/src/preview.h b/src/preview.h index 717e37d..8156b28 100644 --- a/src/preview.h +++ b/src/preview.h @@ -2,13 +2,12 @@ #include "anm2.h" #include "resources.h" -#include "input.h" #include "settings.h" +#include "canvas.h" -const vec2 PREVIEW_SIZE = {5000, 5000}; +const vec2 PREVIEW_SIZE = {2000, 2000}; const vec2 PREVIEW_CENTER = {0, 0}; - #define PREVIEW_ZOOM_MIN 1 #define PREVIEW_ZOOM_MAX 1000 #define PREVIEW_ZOOM_STEP 25 @@ -16,20 +15,6 @@ const vec2 PREVIEW_CENTER = {0, 0}; #define PREVIEW_GRID_MAX 1000 #define PREVIEW_GRID_OFFSET_MIN 0 #define PREVIEW_GRID_OFFSET_MAX 100 -#define PREVIEW_MOVE_STEP 1 -#define PREVIEW_MOVE_STEP_MOD 10 -#define PREVIEW_ROTATE_STEP 1 -#define PREVIEW_ROTATE_STEP_MOD 10 -#define PREVIEW_SCALE_STEP 1 -#define PREVIEW_SCALE_STEP_MOD 10 - -const f32 PREVIEW_AXIS_VERTICES[] = -{ - -1.0f, 0.0f, - 1.0f, 0.0f, - 0.0f, -1.0f, - 0.0f, 1.0f -}; const vec2 PREVIEW_NULL_RECT_SIZE = {100, 100}; const vec2 PREVIEW_POINT_SIZE = {2, 2}; @@ -41,30 +26,29 @@ const vec2 PREVIEW_TARGET_SIZE = {16, 16}; struct Preview { - Anm2* anm2 = NULL; - Anm2Reference* reference = NULL; - f32* time = NULL; - Input* input = NULL; - Resources* resources = NULL; - Settings* settings = NULL; - GLuint axisVAO; - GLuint axisVBO; - GLuint fbo; - GLuint gridVAO; - GLuint gridVBO; - GLuint rbo; - GLuint texture; - GLuint rectVAO; - GLuint rectVBO; - GLuint textureEBO; - GLuint textureVAO; - GLuint textureVBO; + Anm2* anm2 = nullptr; + Anm2Reference* reference = nullptr; + Resources* resources = nullptr; + Settings* settings = nullptr; + GLuint axisVAO = ID_NONE; + GLuint axisVBO = ID_NONE; + GLuint fbo = ID_NONE; + GLuint gridVAO = ID_NONE; + GLuint gridVBO = ID_NONE; + GLuint rbo = ID_NONE; + GLuint texture = ID_NONE; + GLuint rectVAO = ID_NONE; + GLuint rectVBO = ID_NONE; + GLuint textureEBO = ID_NONE; + GLuint textureVAO = ID_NONE; + GLuint textureVBO = ID_NONE; bool isPlaying = false; bool isRecording = false; - vec2 recordSize = {0.0f, 0.0f}; + vec2 recordSize{}; + f32 time{}; }; -void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, f32* time, Resources* resources, Settings* settings); +void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); void preview_draw(Preview* self); void preview_tick(Preview* self); void preview_free(Preview* self); diff --git a/src/resources.cpp b/src/resources.cpp index cfb925d..c9fabbf 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -1,48 +1,39 @@ #include "resources.h" -// Loads a texture, given a path and an id for it to be assigned to -void -resources_texture_init(Resources* resources, const std::string& path, s32 id) +void resources_texture_init(Resources* resources, const std::string& path, s32 id) { Texture texture; - if (resources->textures.find(id) != resources->textures.end() && resources->textures[id].id != resources->textures[TEXTURE_ERROR].id) + if (resources->textures.find(id) != resources->textures.end() && resources->textures[id].id != resources->textures[TEXTURE_NONE].id) texture_free(&resources->textures[id]); - if (texture_from_path_init(&texture, path)) - resources->textures[id] = texture; - else - texture.isInvalid = true; + if (!texture_from_path_init(&texture, path)) + texture.isInvalid = true; - resources->textures[id] = texture; + resources->textures[id] = texture; } -// Loads in resources -void -resources_init(Resources* self) +void resources_init(Resources* self) { texture_from_data_init(&self->atlas, (u8*)TEXTURE_ATLAS, TEXTURE_ATLAS_LENGTH); - - for (s32 i = 0; i < SHADER_COUNT; i++) + + for (s32 i = 0; i < SHADER_COUNT; i++) shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); } -// Frees resources -void -resources_free(Resources* self) +void resources_free(Resources* self) { resources_textures_free(self); - - for (s32 i = 0; i < SHADER_COUNT; i++) + + for (s32 i = 0; i < SHADER_COUNT; i++) shader_free(&self->shaders[i]); - texture_free(&self->atlas); } -// Frees loaded textures -void -resources_textures_free(Resources* self) +void resources_textures_free(Resources* self) { - for (auto & [id, texture] : self->textures) + for (auto & [id, texture] : self->textures) texture_free(&self->textures[id]); + + log_info(RESOURCES_TEXTURES_FREE_INFO); } \ No newline at end of file diff --git a/src/resources.h b/src/resources.h index 79335d9..94e3e3c 100644 --- a/src/resources.h +++ b/src/resources.h @@ -4,6 +4,8 @@ #include "texture.h" #include "shader.h" +#define RESOURCES_TEXTURES_FREE_INFO "Freed texture resources" + struct Resources { GLuint shaders[SHADER_COUNT]; diff --git a/src/settings.cpp b/src/settings.cpp index d171cc4..88e6aa7 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,23 +1,17 @@ #include "settings.h" -static void _settings_setting_load(Settings* self, const std::string& line); -static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry); - -static void -_settings_setting_load(Settings* self, const std::string& line) +static void _settings_setting_load(Settings* self, const std::string& line) { for (s32 i = 0; i < SETTINGS_COUNT; i++) { const std::string& key = SETTINGS_ENTRIES[i].key; size_t keyLength = key.length(); - // Compare keys if (line.compare(0, keyLength, key) == 0) { const char* value = line.c_str() + keyLength; void* target = (u8*)self + SETTINGS_ENTRIES[i].offset; - // Based on type, assign value to offset of settings switch (SETTINGS_ENTRIES[i].type) { case SETTINGS_TYPE_INT: @@ -40,9 +34,7 @@ _settings_setting_load(Settings* self, const std::string& line) } } -// Writes a given setting to the stream -static void -_settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) +static void _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) { u8* selfPointer = (u8*)self; std::string value; @@ -68,60 +60,51 @@ _settings_setting_write(Settings* self, std::ostream& out, SettingsEntry entry) out << entry.key << value << "\n"; } -// Saves the current settings -// Note: this is just for this program's settings, additional imgui settings handled elsewhere -void -settings_save(Settings* self) +void settings_save(Settings* self) { - std::ifstream input(PATH_SETTINGS); + std::ifstream input(SETTINGS_PATH); std::string oldContents; if (!input) { - std::cout << STRING_ERROR_SETTINGS_INIT << PATH_SETTINGS << std::endl; + log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); return; } - // We're writing after the imgui stuff oldContents.assign((std::istreambuf_iterator(input)), std::istreambuf_iterator()); input.close(); - std::ofstream output(PATH_SETTINGS); + std::ofstream output(SETTINGS_PATH); if (!output) { - std::cout << STRING_ERROR_SETTINGS_INIT << PATH_SETTINGS << std::endl; + log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); return; } - // [Settings] output << SETTINGS_SECTION << "\n"; - // Write each setting for (s32 i = 0; i < SETTINGS_COUNT; i++) _settings_setting_write(self, output, SETTINGS_ENTRIES[i]); - // Write the the imgui section output << "\n" << SETTINGS_SECTION_IMGUI << "\n"; output << oldContents; output.close(); } -// Load settings -void -settings_load(Settings* self) +void settings_init(Settings* self) { - std::ifstream file(PATH_SETTINGS); + std::ifstream file(SETTINGS_PATH); + if (!file) { - std::cerr << STRING_ERROR_SETTINGS_INIT << PATH_SETTINGS << std::endl; + log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); return; } std::string line; bool inSettingsSection = false; - // Iterare through settings lines until the imgui section is reached, then end while (std::getline(file, line)) { if (line == SETTINGS_SECTION) @@ -130,10 +113,7 @@ settings_load(Settings* self) continue; } - if (line == SETTINGS_SECTION_IMGUI) - break; - - if (inSettingsSection) - _settings_setting_load(self, line); + 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 0edc1f7..ad20453 100644 --- a/src/settings.h +++ b/src/settings.h @@ -6,6 +6,8 @@ #define SETTINGS_BUFFER_ITEM 0xFF #define SETTINGS_SECTION "[Settings]" #define SETTINGS_SECTION_IMGUI "# Dear ImGui" +#define SETTINGS_INIT_ERROR "Failed to read settings file! ({})" +#define SETTINGS_PATH "settings.ini" enum SettingsValueType { @@ -22,7 +24,6 @@ struct SettingsEntry s32 offset; }; -#define SETTINGS_COUNT 44 struct Settings { s32 windowW = 1920; @@ -69,9 +70,13 @@ struct Settings f32 editorBackgroundColorG = 0.184f; f32 editorBackgroundColorB = 0.286f; f32 editorBackgroundColorA = 1.0f; + f32 toolColorR = 1.0f; + f32 toolColorG = 1.0f; + f32 toolColorB = 1.0f; + f32 toolColorA = 1.0f; }; -const SettingsEntry SETTINGS_ENTRIES[SETTINGS_COUNT] = +const SettingsEntry SETTINGS_ENTRIES[] = { {"windowW=", SETTINGS_TYPE_INT, offsetof(Settings, windowW)}, {"windowH=", SETTINGS_TYPE_INT, offsetof(Settings, windowH)}, @@ -116,8 +121,13 @@ const SettingsEntry SETTINGS_ENTRIES[SETTINGS_COUNT] = {"editorBackgroundColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorBackgroundColorR)}, {"editorBackgroundColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorBackgroundColorG)}, {"editorBackgroundColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorBackgroundColorB)}, - {"editorBackgroundColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorBackgroundColorA)} + {"editorBackgroundColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorBackgroundColorA)}, + {"toolColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, toolColorR)}, + {"toolColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, toolColorG)}, + {"toolColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, toolColorB)}, + {"toolColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, toolColorA)} }; +constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); void settings_save(Settings* self); -void settings_load(Settings* self); \ No newline at end of file +void settings_init(Settings* self); \ No newline at end of file diff --git a/src/shader.cpp b/src/shader.cpp index de7e910..76de08b 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -1,25 +1,21 @@ #include "shader.h" -static bool _shader_compile(GLuint* self, const std::string& text); - -// Compiles the shader; returns true/false based on success -static bool -_shader_compile(GLuint* self, const std::string& text) +static bool _shader_compile(GLuint* self, const std::string& text) { std::string compileLog; s32 isCompile; const GLchar* source = text.c_str(); - glShaderSource(*self, 1, &source, NULL); + glShaderSource(*self, 1, &source, nullptr); glCompileShader(*self); glGetShaderiv(*self, GL_COMPILE_STATUS, &isCompile); if (!isCompile) { - glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, NULL, &compileLog[0]); - std::cout << STRING_ERROR_SHADER_INIT << *self << std::endl << compileLog << std::endl; + glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, nullptr, &compileLog[0]); + log_error(std::format(SHADER_INIT_ERROR, *self, compileLog)); return false; } @@ -27,8 +23,7 @@ _shader_compile(GLuint* self, const std::string& text) } // Initializes a given shader with vertex/fragment -bool -shader_init(GLuint* self, const std::string& vertex, const std::string& fragment) +bool shader_init(GLuint* self, const std::string& vertex, const std::string& fragment) { GLuint vertexHandle; GLuint fragmentHandle; @@ -36,11 +31,7 @@ shader_init(GLuint* self, const std::string& vertex, const std::string& fragment vertexHandle = glCreateShader(GL_VERTEX_SHADER); fragmentHandle = glCreateShader(GL_FRAGMENT_SHADER); - if - ( - !_shader_compile(&vertexHandle, vertex) || - !_shader_compile(&fragmentHandle, fragment) - ) + if (!_shader_compile(&vertexHandle, vertex) || !_shader_compile(&fragmentHandle, fragment)) return false; *self = glCreateProgram(); @@ -56,9 +47,7 @@ shader_init(GLuint* self, const std::string& vertex, const std::string& fragment return true; } -// Frees a given shader -void -shader_free(GLuint* self) +void shader_free(GLuint* self) { glDeleteProgram(*self); } \ No newline at end of file diff --git a/src/shader.h b/src/shader.h index 314745a..276a64c 100644 --- a/src/shader.h +++ b/src/shader.h @@ -3,6 +3,7 @@ #include "COMMON.h" #define SHADER_INFO_LOG_MAX 0xFF +#define SHADER_INIT_ERROR "Failed to initialize shader {}:\n{}" bool shader_init(GLuint* self, const std::string& vertex, const std::string& fragment); void shader_free(GLuint* self); \ No newline at end of file diff --git a/src/snapshots.cpp b/src/snapshots.cpp index 1307457..0ebf85c 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -1,112 +1,62 @@ #include "snapshots.h" -// Pushes the undo stack -void -snapshots_undo_stack_push(Snapshots* self, Snapshot* snapshot) +static void _snapshot_stack_push(SnapshotStack* stack, const Snapshot* snapshot) { - // If stack over the limit, shift it - if (self->undoStack.top >= SNAPSHOT_STACK_MAX) + if (stack->top >= SNAPSHOT_STACK_MAX) { for (s32 i = 0; i < SNAPSHOT_STACK_MAX - 1; i++) - self->undoStack.snapshots[i] = self->undoStack.snapshots[i + 1]; - - self->undoStack.top = SNAPSHOT_STACK_MAX - 1; + stack->snapshots[i] = stack->snapshots[i + 1]; + stack->top = SNAPSHOT_STACK_MAX - 1; } - - self->undoStack.snapshots[self->undoStack.top++] = *snapshot; - self->redoStack.top = 0; + stack->snapshots[stack->top++] = *snapshot; } -// Pops the undo stack -bool -snapshots_undo_stack_pop(Snapshots* self, Snapshot* snapshot) +static bool _snapshot_stack_pop(SnapshotStack* stack, Snapshot* snapshot) { - if (self->undoStack.top == 0) - return false; - - *snapshot = self->undoStack.snapshots[--self->undoStack.top]; + if (stack->top == 0) return false; + *snapshot = stack->snapshots[--stack->top]; return true; } -// Pushes the redo stack -void -snapshots_redo_stack_push(Snapshots* self, Snapshot* snapshot) +static void _snapshot_set(Snapshots* self, const Snapshot& snapshot) { - if (self->redoStack.top >= SNAPSHOT_STACK_MAX) - { - for (s32 i = 0; i < SNAPSHOT_STACK_MAX - 1; i++) - self->redoStack.snapshots[i] = self->redoStack.snapshots[i + 1]; - self->redoStack.top = SNAPSHOT_STACK_MAX - 1; - } - - self->redoStack.snapshots[self->redoStack.top++] = *snapshot; + *self->anm2 = snapshot.anm2; + *self->reference = snapshot.reference; + self->preview->time = snapshot.time; } -// Pops the redo stack -bool -snapshots_redo_stack_pop(Snapshots* self, Snapshot* snapshot) -{ - if (self->redoStack.top == 0) - return false; - - *snapshot = self->redoStack.snapshots[--self->redoStack.top]; - return true; -} - -// Initializes snapshots -void -snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, f32* time, Input* input) +void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview) { self->anm2 = anm2; self->reference = reference; - self->time = time; - self->input = input; + self->preview = preview; } -// Ticks snapshots -void -snapshots_tick(Snapshots* self) +void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot) { - /* Undo */ - if (input_press(self->input, INPUT_UNDO)) - self->isUndo = true; - - // isUndo disconnected, if another part of the program wants to set it - if (self->isUndo) + _snapshot_stack_push(&self->undoStack, snapshot); + self->redoStack.top = 0; +} + +void snapshots_undo(Snapshots* self) +{ + Snapshot snapshot; + if (_snapshot_stack_pop(&self->undoStack, &snapshot)) { - Snapshot snapshot; - if (snapshots_undo_stack_pop(self, &snapshot)) - { - Snapshot current = {*self->anm2, *self->reference, *self->time}; - snapshots_redo_stack_push(self, ¤t); - - *self->anm2 = snapshot.anm2; - *self->reference = snapshot.reference; - *self->time = snapshot.time; - } - - self->isUndo = false; + Snapshot current = {*self->anm2, *self->reference, self->preview->time}; + _snapshot_stack_push(&self->redoStack, ¤t); + _snapshot_set(self, snapshot); } +} - /* Redo */ - if (input_press(self->input, INPUT_REDO)) - self->isRedo = true; - - // isRedo disconnected, if another part of the program wants to set it - if (self->isRedo) +void snapshots_redo(Snapshots* self) +{ + Snapshot snapshot; + if (_snapshot_stack_pop(&self->redoStack, &snapshot)) { - Snapshot snapshot; - if (snapshots_redo_stack_pop(self, &snapshot)) - { - Snapshot current = {*self->anm2, *self->reference, *self->time}; - snapshots_undo_stack_push(self, ¤t); - - *self->anm2 = snapshot.anm2; - *self->reference = snapshot.reference; - *self->time = snapshot.time; - } - - self->isRedo = false; + Snapshot current = {*self->anm2, *self->reference, self->preview->time}; + _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 c650362..719bc33 100644 --- a/src/snapshots.h +++ b/src/snapshots.h @@ -1,7 +1,7 @@ #pragma once #include "anm2.h" -#include "input.h" +#include "preview.h" #define SNAPSHOT_STACK_MAX 100 @@ -20,17 +20,14 @@ struct SnapshotStack struct Snapshots { - Anm2* anm2 = NULL; - Anm2Reference* reference = NULL; - f32* time = NULL; - Input* input = NULL; + Anm2* anm2 = nullptr; + Preview* preview = nullptr; + Anm2Reference* reference = nullptr; SnapshotStack undoStack; SnapshotStack redoStack; - bool isUndo = false; - bool isRedo = false; }; -void snapshots_undo_stack_push(Snapshots* self, Snapshot* snapshot); -bool snapshots_undo_stack_pop(Snapshots* self, Snapshot* snapshot); -void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, f32* time, Input* input); -void snapshots_tick(Snapshots* self); \ No newline at end of file +void snapshots_undo_stack_push(Snapshots* self, const Snapshot* snapshot); +void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview); +void snapshots_undo(Snapshots* self); +void snapshots_redo(Snapshots* self); \ No newline at end of file diff --git a/src/state.cpp b/src/state.cpp index 4cac41d..42b14b0 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -1,182 +1,133 @@ #include "state.h" -static void _tick(State* state); -static void _draw(State* state); - -static void -_tick(State* state) +static void _tick(State* self) { - SDL_Event event; - SDL_MouseWheelEvent* mouseWheelEvent; + SDL_GetWindowSize(self->window, &self->settings.windowW, &self->settings.windowH); - state->input.mouse.wheelDeltaY = 0; + editor_tick(&self->editor); + preview_tick(&self->preview); + dialog_tick(&self->dialog); + imgui_tick(&self->imgui); - while(SDL_PollEvent(&event)) - { - ImGui_ImplSDL3_ProcessEvent(&event); - - switch (event.type) - { - case SDL_EVENT_QUIT: - state->isRunning = false; - break; - case SDL_EVENT_MOUSE_WHEEL: - mouseWheelEvent = &event.wheel; - state->input.mouse.wheelDeltaY = mouseWheelEvent->y; - break; - default: - break; - } - } - - SDL_GetWindowSize(state->window, &state->settings.windowW, &state->settings.windowH); - - input_tick(&state->input); - editor_tick(&state->editor); - preview_tick(&state->preview); - tool_tick(&state->tool); - snapshots_tick(&state->snapshots); - dialog_tick(&state->dialog); - imgui_tick(&state->imgui); - - if (input_release(&state->input, INPUT_SAVE)) - { - // Open dialog if path empty, otherwise save in-place - if (state->anm2.path.empty()) - dialog_anm2_save(&state->dialog); - else - anm2_serialize(&state->anm2, state->anm2.path); - } - - if (input_release(&state->input, INPUT_PLAY)) - { - state->preview.isPlaying = !state->preview.isPlaying; - state->preview.isRecording = false; - } + if (self->imgui.isQuit) self->isRunning = false; } -static void -_draw(State* state) +static void _draw(State* self) { - editor_draw(&state->editor); - preview_draw(&state->preview); + editor_draw(&self->editor); + preview_draw(&self->preview); imgui_draw(); - SDL_GL_SwapWindow(state->window); + SDL_GL_SwapWindow(self->window); } -void -init(State* state) +void init(State* self) { - settings_load(&state->settings); + settings_init(&self->settings); - std::cout << STRING_INFO_INIT << std::endl; + log_info(STATE_INIT_INFO); if (!SDL_Init(SDL_INIT_VIDEO)) { - std::cout << STRING_ERROR_SDL_INIT << SDL_GetError() << std::endl; - quit(state); + log_error(std::format(STATE_SDL_INIT_ERROR, SDL_GetError())); + quit(self); } + + log_info(STATE_SDL_INIT_INFO); SDL_CreateWindowAndRenderer ( - STRING_WINDOW_TITLE, - state->settings.windowW, - state->settings.windowH, + WINDOW_TITLE, + self->settings.windowW, + self->settings.windowH, WINDOW_FLAGS, - &state->window, - &state->renderer + &self->window, + &self->renderer ); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - state->glContext = SDL_GL_CreateContext(state->window); + glewInit(); - if (!state->glContext) + self->glContext = SDL_GL_CreateContext(self->window); + + if (!self->glContext) { - std::cout << STRING_ERROR_GL_CONTEXT_INIT << SDL_GetError() << std::endl; - quit(state); + log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError())); + quit(self); } - std::cout << STRING_INFO_SDL_INIT << "(" << STRING_INFO_OPENGL << glGetString(GL_VERSION) << ")" << std::endl; - - glewInit(); + log_info(std::format(STATE_GL_CONTEXT_INIT_INFO, (const char*)glGetString(GL_VERSION))); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); - glLineWidth(LINE_WIDTH); + glLineWidth(STATE_GL_LINE_WIDTH); - std::cout << STRING_INFO_GLEW_INIT << std::endl; - - resources_init(&state->resources); - dialog_init(&state->dialog, &state->anm2, &state->reference, &state->resources, state->window); - tool_init(&state->tool, &state->input); - snapshots_init(&state->snapshots, &state->anm2, &state->reference, &state->time, &state->input); - preview_init(&state->preview, &state->anm2, &state->reference, &state->time, &state->resources, &state->settings); - editor_init(&state->editor, &state->anm2, &state->reference, &state->resources, &state->settings); + resources_init(&self->resources); + clipboard_init(&self->clipboard, &self->anm2); + dialog_init(&self->dialog, &self->anm2, &self->reference, &self->resources, self->window); + snapshots_init(&self->snapshots, &self->anm2, &self->reference, &self->preview); + preview_init(&self->preview, &self->anm2, &self->reference, &self->resources, &self->settings); + editor_init(&self->editor, &self->anm2, &self->reference, &self->resources, &self->settings); imgui_init ( - &state->imgui, - &state->dialog, - &state->resources, - &state->input, - &state->anm2, - &state->reference, - &state->time, - &state->editor, - &state->preview, - &state->settings, - &state->tool, - &state->snapshots, - state->window, - &state->glContext + &self->imgui, + &self->dialog, + &self->resources, + &self->anm2, + &self->reference, + &self->editor, + &self->preview, + &self->settings, + &self->snapshots, + &self->clipboard, + self->window, + &self->glContext ); - if (state->isArgument) + if (self->is_argument()) { - anm2_deserialize(&state->anm2, &state->resources, state->argument); - window_title_from_path_set(state->window, state->argument); + anm2_deserialize(&self->anm2, &self->resources, self->argument); + window_title_from_path_set(self->window, self->argument); } else - anm2_new(&state->anm2); + anm2_new(&self->anm2); } -void -loop(State* state) +void loop(State* self) { - state->tick = SDL_GetTicks(); + self->tick = SDL_GetTicks(); - while (state->tick > state->lastTick + TICK_DELAY) + while (self->tick > self->lastTick + TICK_DELAY) { - state->tick = SDL_GetTicks(); + self->tick = SDL_GetTicks(); - if (state->tick - state->lastTick < TICK_DELAY) - SDL_Delay(TICK_DELAY - (state->tick - state->lastTick)); + if (self->tick - self->lastTick < TICK_DELAY) + SDL_Delay(TICK_DELAY - (self->tick - self->lastTick)); - _tick(state); + _tick(self); + + self->lastTick = self->tick; - state->lastTick = state->tick; } - _draw(state); + _draw(self); } -void -quit(State* state) +void quit(State* self) { imgui_free(); - settings_save(&state->settings); - preview_free(&state->preview); - editor_free(&state->editor); - resources_free(&state->resources); + settings_save(&self->settings); + preview_free(&self->preview); + editor_free(&self->editor); + resources_free(&self->resources); - SDL_GL_DestroyContext(state->glContext); + SDL_GL_DestroyContext(self->glContext); SDL_Quit(); - std::cout << STRING_INFO_QUIT << std::endl; -} - + log_info(STATE_QUIT_INFO); +} \ No newline at end of file diff --git a/src/state.h b/src/state.h index ec5c656..2c002b6 100644 --- a/src/state.h +++ b/src/state.h @@ -2,10 +2,13 @@ #include "imgui.h" -#define WINDOW_WIDTH 1920 -#define WINDOW_HEIGHT 1080 -#define WINDOW_FLAGS SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL -#define LINE_WIDTH 2.0f +#define STATE_INIT_INFO "Initializing..." +#define STATE_SDL_INIT_ERROR "Failed to initialize SDL! {}" +#define STATE_SDL_INIT_INFO "Initialized SDL" +#define STATE_GL_CONTEXT_INIT_ERROR "Failed to initialize OpenGL context! {}" +#define STATE_GL_CONTEXT_INIT_INFO "Initialized OpenGL context (OpenGL {})" +#define STATE_QUIT_INFO "Exiting..." +#define STATE_GL_LINE_WIDTH 2.0f struct State { @@ -13,22 +16,21 @@ struct State SDL_Renderer* renderer; SDL_GLContext glContext; Imgui imgui; - Input input; Dialog dialog; Editor editor; Preview preview; Anm2 anm2; Anm2Reference reference; - f32 time; Resources resources; Settings settings; - Tool tool; Snapshots snapshots; - bool isArgument = false; + Clipboard clipboard; bool isRunning = true; - std::string argument; + std::string argument{}; u64 lastTick = 0; u64 tick = 0; + + bool is_argument() const { return !argument.empty(); } }; void init(State* state); diff --git a/src/texture.cpp b/src/texture.cpp index 9789615..be32776 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -5,75 +5,55 @@ #define STBI_NO_HDR #define STB_IMAGE_IMPLEMENTATION #include - #define STB_IMAGE_WRITE_IMPLEMENTATION #include -// Generates GL texture and sets parameters -void -texture_gl_set(Texture* self, void* data) +void texture_gl_set(Texture* self, void* data) { 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); } -// Initializes texture from path; returns true/false on success -bool -texture_from_path_init(Texture* self, const std::string& path) +bool texture_from_path_init(Texture* self, const std::string& path) { - void* data; - - data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, 4); + void* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS); if (!data) { - std::cout << STRING_ERROR_TEXTURE_INIT << path << std::endl; + log_error(std::format(TEXTURE_INIT_ERROR, path)); return false; } - std::cout << STRING_INFO_TEXTURE_INIT << path << std::endl; + log_info(std::format(TEXTURE_INIT_INFO, path)); texture_gl_set(self, data); return true; } -// Initializes texture from data; returns true/false on success -bool -texture_from_data_init(Texture* self, const u8* data, u32 length) +bool texture_from_data_init(Texture* self, const u8* data, u32 length) { - void* textureData; + void* textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, &self->channels, TEXTURE_CHANNELS); - textureData = stbi_load_from_memory(data, length, &self->size.x, &self->size.y, &self->channels, 4); - - if (!textureData) - return false; + if (!textureData) return false; texture_gl_set(self, textureData); return true; } -// Writes an image to the path from the data/size -bool -texture_from_data_write(const std::string& path, const u8* data, s32 width, s32 height) +bool texture_from_data_write(const std::string& path, const u8* data, s32 width, s32 height) { - return (bool)stbi_write_png(path.c_str(), width, height, 4, data, width * 4); + return (bool)stbi_write_png(path.c_str(), width, height, TEXTURE_CHANNELS, data, width * TEXTURE_CHANNELS); } -// Frees texture -void -texture_free(Texture* self) +void texture_free(Texture* self) { glDeleteTextures(1, &self->id); *self = Texture{}; diff --git a/src/texture.h b/src/texture.h index dc54141..40e51ce 100644 --- a/src/texture.h +++ b/src/texture.h @@ -2,6 +2,10 @@ #include "COMMON.h" +#define TEXTURE_CHANNELS 4 +#define TEXTURE_INIT_INFO "Initialized texture from file: {}" +#define TEXTURE_INIT_ERROR "Failed to initialize texture from file: {}" + struct Texture { GLuint id = 0; diff --git a/src/tool.cpp b/src/tool.cpp deleted file mode 100644 index a420bbe..0000000 --- a/src/tool.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "tool.h" - -// Initializes tools -void -tool_init(Tool* self, Input* input) -{ - self->input = input; -} - -// Ticks tools -void -tool_tick(Tool* self) -{ - if (!self->isEnabled) return; - - // Input handling for tools - if (input_release(self->input, INPUT_PAN)) - self->type = TOOL_PAN; - - if (input_release(self->input, INPUT_MOVE)) - self->type = TOOL_MOVE; - - if (input_release(self->input, INPUT_SCALE)) - self->type = TOOL_SCALE; - - if (input_release(self->input, INPUT_ROTATE)) - self->type = TOOL_ROTATE; - - if (input_release(self->input, INPUT_CROP)) - self->type = TOOL_CROP; -} \ No newline at end of file diff --git a/src/tool.h b/src/tool.h deleted file mode 100644 index 67ceaeb..0000000 --- a/src/tool.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "input.h" - -enum ToolType -{ - TOOL_PAN, - TOOL_MOVE, - TOOL_ROTATE, - TOOL_SCALE, - TOOL_CROP, - TOOL_COUNT -}; - -struct Tool -{ - Input* input = NULL; - ToolType type = TOOL_PAN; - bool isEnabled = false; -}; - -void tool_init(Tool* self, Input* input); -void tool_tick(Tool* self); \ No newline at end of file diff --git a/src/window.cpp b/src/window.cpp index 8fcc9d4..bf9243f 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -1,15 +1,9 @@ #include "window.h" -/* Sets the window title from the given anm2 */ -void -window_title_from_path_set(SDL_Window* self, const std::string& path) +void window_title_from_path_set(SDL_Window* self, const std::string& path) { - if (path.empty()) - { - std::string windowTitle = path; - windowTitle = windowTitle + " (" + path + ")"; - SDL_SetWindowTitle(self, windowTitle.c_str()); - } + if (!path.empty()) + SDL_SetWindowTitle(self, std::format(WINDOW_TITLE_FORMAT, path).c_str()); else - SDL_SetWindowTitle(self, STRING_WINDOW_TITLE); + SDL_SetWindowTitle(self, WINDOW_TITLE); } \ No newline at end of file diff --git a/src/window.h b/src/window.h index e314ae8..f53dd95 100644 --- a/src/window.h +++ b/src/window.h @@ -2,4 +2,10 @@ #include "COMMON.h" +#define WINDOW_TITLE "Anm2Ed" +#define WINDOW_TITLE_FORMAT "Anm2Ed ({})" +#define WINDOW_WIDTH 1920 +#define WINDOW_HEIGHT 1080 +#define WINDOW_FLAGS SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL + void window_title_from_path_set(SDL_Window* self, const std::string& path); \ No newline at end of file