From b9c9105621ef7e5b799123f4341f519b60d7e1e4 Mon Sep 17 00:00:00 2001 From: shweet Date: Sat, 9 Aug 2025 00:32:14 -0400 Subject: [PATCH] The Update(TM), Part 2 --- CMakeLists.txt | 17 +- assets/atlas.png | Bin 1696 -> 1698 bytes src/COMMON.h | 101 ++-- src/PACKED.h | 307 +++++----- src/anm2.cpp | 367 ++++++++---- src/anm2.h | 34 +- src/canvas.cpp | 283 +++++++++- src/canvas.h | 82 ++- src/clipboard.cpp | 27 +- src/clipboard.h | 4 - src/dialog.cpp | 25 +- src/dialog.h | 7 +- src/editor.cpp | 291 +--------- src/editor.h | 19 +- src/imgui.cpp | 1354 ++++++++++++++++++++++++++++----------------- src/imgui.h | 682 ++++++++++++++++------- src/preview.cpp | 564 ++++--------------- src/preview.h | 25 +- src/settings.cpp | 105 +++- src/settings.h | 145 ++--- src/shader.cpp | 8 +- src/snapshots.cpp | 5 +- src/snapshots.h | 5 +- src/state.cpp | 62 ++- src/state.h | 19 +- src/texture.cpp | 42 +- src/texture.h | 6 +- src/tool.h | 52 ++ src/window.h | 5 +- 29 files changed, 2656 insertions(+), 1987 deletions(-) create mode 100644 src/tool.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d61608..da46b39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ endif() project(anm2ed CXX) - -find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) +find_package(SDL3 REQUIRED) find_package(GLEW REQUIRED) find_package(OpenGL REQUIRED) @@ -28,22 +27,30 @@ if (WIN32) enable_language("RC") set (WIN32_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/win_icon.rc) 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") + set(CMAKE_CXX_FLAGS "-O2 -std=c++23 -Wall -Wextra -pedantic -fmax-errors=1") else() - set(CMAKE_CXX_FLAGS "/std:c++latest /EHsc") # /std:c++latest is required to make MSVC grant access to all the latest C++ stuff (C++23 is listed as preview even on dev previews of MSVC....) + set(CMAKE_CXX_FLAGS "/std:c++latest /EHsc") +endif() + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG -g") +else() + set(CMAKE_BUILD_TYPE "Release") endif() if(NOT MSVC) target_link_libraries(${PROJECT_NAME} PRIVATE m) endif() - target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL GLEW::GLEW SDL3::SDL3) +target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL GLEW::GLEW SDL3::SDL3) message("System: ${CMAKE_SYSTEM_NAME}") message("Project: ${PROJECT_NAME}") diff --git a/assets/atlas.png b/assets/atlas.png index f1376183a7c692b2a4dee25dbad65e98ef7f85f9..0ea39268c0825af2b6ae03150b7818a427611090 100644 GIT binary patch delta 1607 zcmV-N2Dtg44WbQ@IUrO?L_t(|+U;G7PU|oXWz{|uz6}0!mq-K!Uq6l=r|pqh9DnWe z%Dz;_KO+zsWB0Zl!Rp{iz=?or9ctG`X_4q>U{jK4G4%n`3IICeEnfBOZWYRS@diNq znPCJsjz^GInIKO1=rdgaWJw;CW>orTL5gZZL{QN2^meyVC2rrOsY}03?~nQhu0O7C z>poSr1N}z&AMZuR(VwXTz%hRX0Do5h76Yh>`2752@B8@p`1Aei>+5y|0Bsz-?+uYA zRAOdnWMuzLnTAknGq$7Oy`~TK19aRH-$vj~lcJfWEDdOb?ow-Mg+~)4(j;FS%P6_1 zwv5l?+HbTYFY~>U>1BwA`POsB>r~r1QRa`I+ci&Q)cfqDjt`9xX2VeF8Gl^7?^S#5 z_ZXkk-_dv1c#xJs9+8M;WZfDVl9@UU0B0ygWb}hDJ(6^3_oega6^d~l$6QoYlx+@? z_S-78k47|S>7OQI5$4ad-`$BLk(9>Fu?`PJV~@x?%<^DY=6kMr(tW8yYE@c~(8e(1 zBPvWKjQ&{C;FU0~|JkT6%YTHr0YFvQijOVkOL24*0Cl26FbPus+;k%S8!AAEu(9P| zsb<5~Nvw8bX}DkKp3|P;sncuaajU8;6%%_YnJb$0qS{z))(VGO5$?bHM})`EbS6uh zaF(Q>bAFPv&S-$w??|&(T=c?4Gmk`KZ71!B4vYh5RAG(zlD+l-fPa($5QlsU0FVg? z>pEp9TDXg9fMsiuh)ZI$<>##SQ*|ThC9I@EbF`9K@;YHHijn%)@`STE;f+q1^p0+X z`XD$*%S|*`Q-GdH7}{?ir}ixB5UoD?{q0%p`QzkE`#d^Q&e?k=sMLG4P}Gw_Y7W_x zh;gQkGmBCmh)+6mX3+ET>IVq8Wh>yi9f=Do^%%FCCdi_S<< z6T&4p+UM-{t84EW&!%SZ=IunPx6G`bn{AKOgfan$KA#G43u`wo!dcP4tTbh2(yKM0 zTa!!!A%B?N8?a)J0k$!-Hqb8m#X0kAK*>Wq-yT$ zyStc=)XpOspiPFX3LT?ck21F`xtVX7AZ_mxqQ0mJ@iN%812GW*Zw>|RbJMDoNHh_( zkw>NWQ2(m~%7i1&w}3_8 z0nL0l-XXwkkw)2Br`2ztm6N`op>{UT*m=gbNiRDiT}XkAlfX#v%}dhm#rC(zj!aEZ zii)m95pCU`=>@Gdpfjamq~9X|`W--Ybuf2Qi04_{IXpAW(AM@?{m(KuH1?Sn=hm5` zNq-u*J~g5#TGE(V_tF>~&STKE{T^mmgN!m=Vm!C1%_9VnxepHjcS}{dlShza0mcOD z``$k)Z-sl2P_8crr%T`^yRVHQdQgdUulxK9Yhwj!4>g})^=W;VBwrdkx+W;{uBkqN zQ`HRSQ=gCtfcLa6uetP^t+X&NgZVV-(0?}BMMFrWG>(LlGxN#e?3vn7t{B(tC33=4 zblHmc<>;0l?d+ny+Sy~%IUgvsMT&)Lu})JxlsT5<&m>0lnswZaw%ux`#O&1^u21dC>`R5Xx!>|xN|mtw4Hr04S$GH z8>nW`T`SR8KEp>um`=gd&WBs#gteC)nIAPtuU-Y6a>7N&(d`T;N}WNYjyNY(tp)AD zz&cLs2Y{%)}da@a@0^mjMe`!DgP*eaE07V5r0Z>!`Trm^AGHH9(_NWQni}Ch7 zD}mUq&U|hflm2bxF$kCT0f1yLH-ACfRiz&>EA5uqf7Mk0R1|c)GxfHc*#z!1aisy3 zh?NO?`A7zG!rG+#%6y?71|tqM(X`3V@;lpa3X33V{A!gS@oY9;!=dfER>f{sI$(f{@Mpt32IUrI=L_t(|+U;G7PU|oXWz{|uz6}0!mr4W$Uq6l=r|FSd9Dn=g znSH5@e?}lO#_ny~gVn&3fD-}LI@GT9(jw8%z=kBzV(J5=82~iKTfA!5-71vv;yVEQ z&j=&9aXf-F%LH-4N1y2eAWQP7G^5f#3Q|-PB7%aBr?tEFDslTJO$$Di+CUthNs0PN#veQ$^~ zp%ODnJtNy^$~1)bHhnw#-D~<#KS0MV@vR5mG%1={%F=*+&`oMBt?+1qM4IGlW9cOq z)t3HwT>Fi7U&^rbm)4?Y?yWyh1U~Tz^F83X411Y zp;?nl10jDH-5W4tj{&zQf)#*hwu>+j(SVFe?-c+wKCzz^DSV} zcR({=j&}%fTclBT)@k+IX62;sXQ-X^Gj^V_ZPLq*NEcFI<0LRreDjjDdvX6;WJjhZ zC`CorqKLL`&-8-U8qkr_Fw*Y|fPM!MT^-Dw6ykXncMi`8GqklmR{x_64vl@}#kqB+ zXp(=%txt_;ik38H*1a?ahw~V8ZNG;Z)*z!yml)5jYV!y|WbVTQz}-?+?&J~VSb#pk z`o8y%%3I-HB$R8*!RZos$?j`oh#piT-RnO8!rEAY+C$AJSbbXGCCQh@j;;xcylbit z;8Zn(`P3(50^mKZ%WE#ZW-Bes%V0i@I<$XHcF_xr%npA< zuMJc)=&qG$ET7>cB21^?Y3IXDal+cm_RNo(q*t$k4mshXb#%*!ZAUcXEE=`qoYdba zsAwJCrNR~J?21YQ3V>DqqW~x>01AMj0-yjWDgX+AqIf2JWzzPn9Z?gy7vt@FW&*KY zo%!4}CjHyYV-POw0|3dMZi2R}N3_V4{y*OW;9wA}P1BJ+uMEH+AJ94eiJ3%4 zZQZ=I6P7?I<{uaZQ4osx^GK`({V@E|eST?LwQ>3%%+qPiK%aY300000NkvXXu0mjf D?`#gA diff --git a/src/COMMON.h b/src/COMMON.h index 5a4c0dd..f22337c 100644 --- a/src/COMMON.h +++ b/src/COMMON.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -45,12 +43,9 @@ typedef double f64; 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)) -#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 ROUND_NEAREST_MULTIPLE(value, multiple) (roundf((value) / (multiple)) * (multiple)) +#define FLOAT_TO_U8(x) (static_cast((x) * 255.0f)) +#define U8_TO_FLOAT(x) ((x) / 255.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f) #define TICK_DELAY 33.3f #define TICK_CATCH_UP_MAX (33.3f * 5) @@ -58,14 +53,14 @@ using namespace glm; #define TICK_RATE (SECOND / TICK_DELAY) #define ID_NONE -1 #define INDEX_NONE -1 -#define LENGTH_NONE -1 +#define TIME_NONE -1.0f #define UV_VERTICES(uvMin, uvMax) \ { \ 0, 0, uvMin.x, uvMin.y, \ 1, 0, uvMax.x, uvMin.y, \ 1, 1, uvMax.x, uvMax.y, \ - 0, 1, uvMin.x, uvMax.y, \ + 0, 1, uvMin.x, uvMax.y \ } static const f32 GL_VERTICES[] = @@ -76,7 +71,7 @@ static const f32 GL_VERTICES[] = 0, 1 }; -static const f32 GL_UV_VERTICES[] = +constexpr f32 GL_UV_VERTICES[] = { 0, 0, 0.0f, 0.0f, 1, 0, 1.0f, 0.0f, @@ -88,8 +83,9 @@ 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}; +static const vec4 COLOR_PINK = {1.0f, 0.0f, 1.0f, 1.0f}; 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 vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 0.0f}; static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f}; static inline void log_error(const std::string& string) @@ -102,6 +98,11 @@ static inline void log_info(const std::string& string) std::println("[INFO] {}", string); } +static inline void log_warning(const std::string& string) +{ + std::println("[WARNING] {}", string); +} + static inline bool string_to_bool(const std::string& string) { if (string == "1") return true; @@ -147,28 +148,11 @@ static inline s32 map_next_id_get(const std::map& map) } template -static inline s32 vector_next_id_get(const std::vector& vec) +static inline T* map_find(std::map& map, s32 id) { - 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); + if (auto it = map.find(id); it != map.end()) + return &it->second; + return nullptr; } template @@ -219,6 +203,39 @@ static inline void map_insert_shift(std::map& map, s32 index, const T& v map[insertIndex] = value; } +static inline mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale) +{ + vec2 scaleAbsolute = glm::abs(scale); + vec2 scaleSign = glm::sign(scale); + vec2 pivotScaled = pivot * scaleAbsolute; + vec2 sizeScaled = size * scaleAbsolute; + + mat4 model(1.0f); + model = glm::translate(model, vec3(position - pivotScaled, 0.0f)); + model = glm::translate(model, vec3(pivotScaled, 0.0f)); + model = glm::scale(model, vec3(scaleSign, 1.0f)); + model = glm::rotate(model, glm::radians(rotation), vec3(0, 0, 1)); + model = glm::translate(model, vec3(-pivotScaled, 0.0f)); + model = glm::scale(model, vec3(sizeScaled, 1.0f)); + return model; +} + +static inline mat4 quad_parent_model_get(vec2 position, vec2 pivot, f32 rotation, vec2 scale) +{ + vec2 scaleSign = glm::sign(scale); + vec2 scaleAbsolute = glm::abs(scale); + f32 handedness = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; + + mat4 local(1.0f); + local = glm::translate(local, vec3(pivot, 0.0f)); + local = glm::scale(local, vec3(scaleSign, 1.0f)); // mirror if needed + local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1)); + local = glm::translate(local, vec3(-pivot, 0.0f)); + local = glm::scale(local, vec3(scaleAbsolute, 1.0f)); + + return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; +} + #define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \ static inline std::string function(s32 index) \ { \ @@ -230,3 +247,21 @@ static inline void map_insert_shift(std::map& map, s32 index, const T& v { \ return static_cast(string_to_enum(string, stringArray, count)); \ }; + + +enum DataType +{ + TYPE_INT, + TYPE_BOOL, + TYPE_FLOAT, + TYPE_STRING, + TYPE_IVEC2, + TYPE_VEC2, + TYPE_VEC4 +}; + +enum OriginType +{ + ORIGIN_TOP_LEFT, + ORIGIN_CENTER +}; \ No newline at end of file diff --git a/src/PACKED.h b/src/PACKED.h index 3092378..e74980b 100644 --- a/src/PACKED.h +++ b/src/PACKED.h @@ -4,151 +4,151 @@ #include "COMMON.h" -const u32 TEXTURE_ATLAS_LENGTH = 1696; +const u32 TEXTURE_ATLAS_LENGTH = 1698; 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, 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, + 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x06, 0x54, 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, + 0xd5, 0x3e, 0x14, 0xbe, 0x0c, 0xfe, 0x74, 0x97, 0x48, 0x04, 0x05, 0x5f, + 0x3f, 0x8e, 0x1d, 0xa7, 0xed, 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 + 0xef, 0x10, 0x69, 0xfb, 0xfe, 0xfe, 0xfe, 0xfb, 0xee, 0xed, 0xf3, 0xca, + 0xbe, 0x54, 0xc6, 0x3f, 0x23, 0x10, 0x19, 0x63, 0xf7, 0xb6, 0x1d, 0xc1, + 0xea, 0xe0, 0x49, 0xc0, 0x89, 0x80, 0x6a, 0x1d, 0x6a, 0xd7, 0x46, 0x69, + 0x91, 0xe8, 0x67, 0x60, 0x53, 0x92, 0x68, 0x31, 0xf5, 0x01, 0xd2, 0x0a, + 0x00, 0x3a, 0xe3, 0x2d, 0x5e, 0xf5, 0xeb, 0x6e, 0x15, 0xca, 0x78, 0xf1, + 0x06, 0x40, 0xfb, 0x99, 0x61, 0x04, 0x37, 0x8e, 0x47, 0x90, 0x56, 0x99, + 0x20, 0x4e, 0xf8, 0xe8, 0x33, 0x5d, 0x00, 0x64, 0x49, 0x1e, 0x95, 0x66, + 0x54, 0xfa, 0x67, 0x41, 0x8a, 0x6a, 0x42, 0x44, 0x50, 0xd0, 0xf1, 0xf4, + 0x76, 0xb7, 0x51, 0x25, 0x6e, 0xdf, 0xa4, 0xa9, 0x4b, 0xbf, 0x4e, 0xef, + 0x8f, 0xfa, 0x06, 0xae, 0x3f, 0xae, 0x6f, 0xeb, 0x3e, 0x55, 0xb5, 0x03, + 0xfd, 0x46, 0xfa, 0x1f, 0xef, 0x45, 0xc6, 0xd1, 0x9f, 0xa9, 0x02, 0xc0, + 0x31, 0x7f, 0x05, 0x00, 0x56, 0xff, 0x16, 0x03, 0xa8, 0x89, 0xf8, 0xfc, + 0xfc, 0x64, 0xef, 0xfb, 0xf8, 0xf8, 0xf8, 0xf3, 0xfd, 0xeb, 0xeb, 0xeb, + 0x76, 0x04, 0x00, 0x6d, 0x1c, 0xbd, 0xef, 0x0d, 0x91, 0x26, 0x54, 0x62, + 0x66, 0x69, 0x64, 0x64, 0xff, 0x4c, 0x99, 0x86, 0x50, 0x6b, 0x33, 0xb6, + 0xa3, 0xdf, 0xbd, 0xa6, 0x0f, 0xf5, 0x03, 0x74, 0xdc, 0x12, 0xdf, 0x46, + 0xe0, 0x4d, 0x93, 0xa2, 0x99, 0xa5, 0x2c, 0x0d, 0x68, 0x83, 0xee, 0x52, + 0x6b, 0x69, 0x85, 0x47, 0x13, 0x24, 0xd2, 0x24, 0x5f, 0x1b, 0xcb, 0x28, + 0xb9, 0xa8, 0xb6, 0x8c, 0xcf, 0xe3, 0xda, 0x6f, 0xb4, 0x23, 0x2f, 0xf3, + 0xbd, 0x92, 0xe9, 0x65, 0x88, 0x87, 0xf9, 0xd6, 0x73, 0xc6, 0xeb, 0x54, + 0xdb, 0x3a, 0x51, 0xe6, 0x8f, 0x9f, 0xdb, 0x35, 0x4f, 0x64, 0xd4, 0xfb, + 0xec, 0xa4, 0x8e, 0x0f, 0x8d, 0x10, 0x66, 0xc3, 0x50, 0xe9, 0x19, 0x5c, + 0xbc, 0xef, 0x55, 0x7b, 0xee, 0xf7, 0x18, 0x9f, 0xd3, 0xdf, 0xd1, 0x77, + 0xd7, 0x78, 0x90, 0x96, 0x41, 0x1e, 0x91, 0x88, 0x65, 0x64, 0xdd, 0x1a, + 0x18, 0x92, 0x99, 0x3a, 0x0d, 0x00, 0x67, 0x28, 0x45, 0x64, 0xf4, 0x83, + 0x30, 0x3d, 0x92, 0x74, 0x69, 0xf7, 0xa5, 0xf3, 0xe7, 0x15, 0x8a, 0x71, + 0x1e, 0xc7, 0x5c, 0x54, 0x54, 0x94, 0x6d, 0x0e, 0x91, 0xf6, 0xdb, 0x2a, + 0xb5, 0x8f, 0x46, 0x34, 0x67, 0xe9, 0x9f, 0x26, 0x62, 0x11, 0xe6, 0xcf, + 0xb4, 0xdf, 0xdd, 0x89, 0x23, 0x91, 0x94, 0xc6, 0xcc, 0xb1, 0x0e, 0x0f, + 0x44, 0x63, 0x8f, 0xc8, 0x3b, 0xcc, 0xf2, 0x60, 0x57, 0xe6, 0x7b, 0xae, + 0x79, 0xd2, 0x7d, 0xa9, 0x42, 0x6a, 0x55, 0x5a, 0x8f, 0xd0, 0xc6, 0x30, + 0xe3, 0x23, 0x2a, 0x4c, 0x25, 0x8c, 0xfe, 0x58, 0xd2, 0xe0, 0x95, 0x30, + 0xad, 0xff, 0xd9, 0xa8, 0x2e, 0xcb, 0x84, 0xba, 0x01, 0x40, 0x55, 0xd8, + 0x8a, 0x8f, 0x2d, 0xe6, 0x4b, 0x71, 0x74, 0x14, 0x00, 0x75, 0xa2, 0x43, + 0x30, 0x09, 0x52, 0xff, 0xdc, 0x74, 0x22, 0xfd, 0x1b, 0x2a, 0x40, 0x88, + 0xb0, 0xb1, 0xe5, 0x5f, 0xa9, 0x66, 0xc3, 0xd5, 0x49, 0xac, 0x76, 0x63, + 0x69, 0xb8, 0x5f, 0xe7, 0x9e, 0xd3, 0x9e, 0xe1, 0xa9, 0xd3, 0x6b, 0xe5, + 0x71, 0xab, 0xaa, 0x2b, 0x15, 0x13, 0x7b, 0x29, 0x99, 0x2b, 0x9a, 0xf5, + 0xa2, 0xda, 0x58, 0x6e, 0xd6, 0x0a, 0x87, 0x5a, 0x11, 0xee, 0xbf, 0xfb, + 0x47, 0x84, 0xc7, 0xcf, 0x74, 0x26, 0x4b, 0x9a, 0x70, 0x96, 0xa4, 0x9f, + 0x73, 0x7e, 0x92, 0xb4, 0xce, 0x68, 0x80, 0xd7, 0xef, 0x48, 0xb3, 0x57, + 0x5c, 0xf4, 0xc2, 0x45, 0x33, 0x8f, 0x44, 0x62, 0x6d, 0x27, 0xed, 0x88, + 0x0e, 0x8c, 0x03, 0x67, 0x54, 0x61, 0x8d, 0xf9, 0x92, 0xbd, 0xf6, 0x00, + 0x80, 0x94, 0x01, 0x10, 0x87, 0x7c, 0x0a, 0x00, 0x90, 0x09, 0x08, 0xeb, + 0x3a, 0x65, 0x28, 0x5a, 0xb8, 0x8b, 0x6a, 0x80, 0x65, 0x6b, 0x91, 0x88, + 0x4b, 0x62, 0xb4, 0xe5, 0xe7, 0xac, 0xf6, 0x53, 0x75, 0x23, 0xe9, 0x25, + 0xac, 0xa4, 0x42, 0x73, 0xb4, 0x92, 0x59, 0xf2, 0x3a, 0x61, 0x2d, 0x8a, + 0x91, 0xfa, 0xd7, 0xf2, 0x84, 0xb3, 0x38, 0xe1, 0x8d, 0x4e, 0x98, 0xf4, + 0x8e, 0x6e, 0x84, 0xfa, 0x20, 0x38, 0x47, 0xcb, 0x4d, 0x34, 0x58, 0x53, + 0x80, 0x9e, 0x49, 0x18, 0xda, 0x6f, 0x1f, 0xa7, 0xf6, 0x2c, 0xea, 0x10, + 0xad, 0x3e, 0xfa, 0xfd, 0xed, 0x59, 0xed, 0xf9, 0xe3, 0xe4, 0x4b, 0xfb, + 0x3c, 0x3a, 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, 0xfd, 0xb0, 0x52, 0x04, + 0xf5, 0x31, 0x9a, 0x4f, 0xf2, 0x94, 0x22, 0x10, 0xad, 0x78, 0xda, 0x52, + 0xc4, 0xca, 0x62, 0x5c, 0x46, 0x26, 0xeb, 0x91, 0xfc, 0x59, 0xe6, 0xbd, + 0xca, 0x7c, 0xca, 0xcb, 0x97, 0xa3, 0x8b, 0xce, 0x48, 0x51, 0x13, 0xc2, + 0x25, 0x38, 0xda, 0xe7, 0xec, 0xf6, 0xab, 0x6b, 0xef, 0x19, 0xcf, 0xa6, + 0x66, 0xf0, 0xe6, 0xed, 0x44, 0xaa, 0xb7, 0xcc, 0xac, 0x9e, 0x9b, 0x6d, + 0x8f, 0xd4, 0x84, 0x32, 0x01, 0x88, 0x3e, 0x9f, 0x0a, 0x71, 0x0b, 0x6b, + 0x37, 0x2f, 0xc2, 0x59, 0xd1, 0xc0, 0xac, 0x34, 0x65, 0x66, 0xd2, 0xab, + 0x35, 0xa1, 0x5b, 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, + 0x98, 0xdd, 0x1b, 0xb0, 0x62, 0x8f, 0x01, 0xb7, 0x27, 0x82, 0x15, 0x80, + 0x68, 0xb6, 0x8b, 0x30, 0x10, 0xd1, 0x80, 0x8c, 0x49, 0xef, 0x23, 0x00, + 0x88, 0x30, 0x5f, 0xdd, 0x1d, 0xe3, 0xdd, 0x8d, 0xe2, 0xb9, 0x97, 0xaa, + 0xe1, 0x0a, 0x1f, 0xe0, 0x4d, 0xd2, 0xa4, 0x6a, 0xee, 0xec, 0xbb, 0xbb, + 0x98, 0x8f, 0xd4, 0xce, 0x23, 0x1a, 0xa0, 0x4d, 0x86, 0xac, 0x0a, 0x1d, + 0xa3, 0x5b, 0x8f, 0x32, 0xb7, 0x2c, 0xb9, 0x99, 0x6f, 0x99, 0x20, 0x6d, + 0xef, 0x13, 0xa2, 0xbe, 0xa8, 0x09, 0xf1, 0x32, 0xd8, 0xb5, 0x03, 0x31, + 0x11, 0x00, 0x6f, 0x0e, 0x05, 0xed, 0x73, 0xd3, 0xaa, 0x95, 0x48, 0x34, + 0x11, 0xb5, 0x91, 0x47, 0xa5, 0xf6, 0x51, 0x00, 0x50, 0x80, 0x5c, 0xcc, + 0x47, 0x9c, 0x65, 0x74, 0x97, 0x64, 0x56, 0x5c, 0x9d, 0x59, 0x5d, 0x9c, + 0xf1, 0x01, 0x1e, 0xff, 0x80, 0x32, 0xff, 0xa6, 0x31, 0x42, 0xda, 0x70, + 0xa6, 0x6d, 0xe6, 0x43, 0x27, 0x1e, 0xc6, 0xec, 0xd7, 0xca, 0x84, 0x23, + 0xcf, 0xb7, 0x80, 0x45, 0xdf, 0x01, 0xcd, 0x7c, 0x39, 0xde, 0x21, 0xc0, + 0x6d, 0x91, 0x46, 0xd9, 0x59, 0xa7, 0xd5, 0x6f, 0x9f, 0x95, 0x93, 0xbe, + 0x9f, 0xa1, 0x76, 0x36, 0xce, 0xd8, 0x79, 0xc6, 0xb6, 0x49, 0x2f, 0x3b, + 0x23, 0x5d, 0x48, 0x81, 0x8d, 0x93, 0xc0, 0x48, 0xf1, 0xcd, 0x4b, 0xd2, + 0xee, 0xc5, 0xf6, 0xb7, 0xc8, 0x8e, 0x4c, 0x4d, 0x50, 0x8a, 0x8a, 0xae, + 0x45, 0x11, 0x6d, 0xdd, 0x9e, 0xe9, 0x05, 0xad, 0x35, 0xa0, 0x33, 0xa5, + 0x61, 0xa4, 0xdf, 0x23, 0x00, 0xfa, 0x1d, 0x40, 0x74, 0x75, 0x30, 0x77, + 0x52, 0x88, 0xe7, 0x59, 0xdc, 0x39, 0x3c, 0x33, 0xcc, 0xd0, 0xd6, 0xf6, + 0x58, 0xfd, 0xcf, 0x32, 0x38, 0x34, 0xf6, 0x99, 0x17, 0xe7, 0xd6, 0x99, + 0xa2, 0x49, 0x1a, 0xb7, 0x3e, 0x35, 0xa2, 0x29, 0x5a, 0xd2, 0x98, 0x59, + 0xf7, 0xd2, 0x18, 0x1c, 0xce, 0x63, 0xd0, 0xb5, 0xfd, 0x1e, 0x66, 0x58, + 0x83, 0x8c, 0x32, 0x5d, 0x62, 0x3c, 0xb7, 0xaa, 0xcd, 0x23, 0x04, 0x91, + 0xb9, 0x0f, 0x0f, 0x00, 0x77, 0x4b, 0x55, 0xb9, 0x93, 0x47, 0x90, 0x63, + 0x01, 0xc6, 0x04, 0xeb, 0xfb, 0xde, 0x3f, 0x2a, 0x6f, 0x85, 0x7b, 0x91, + 0x50, 0xae, 0x2f, 0x07, 0xa7, 0x4b, 0xe0, 0x25, 0xbb, 0xaf, 0x8d, 0x21, + 0x7a, 0x50, 0x89, 0x74, 0xaf, 0xfb, 0xfc, 0x0b, 0x6b, 0x63, 0x05, 0x6a, + 0x0f, 0x35, 0x9f, 0x60, 0xf5, 0x69, 0x7d, 0x97, 0x24, 0x5f, 0x1a, 0x3b, + 0xba, 0x26, 0x28, 0xf2, 0xae, 0xa9, 0x3e, 0x80, 0x53, 0xd5, 0x0c, 0xe6, + 0x53, 0x9f, 0x90, 0x09, 0x80, 0xf7, 0xb4, 0x2e, 0xaf, 0xb9, 0xf4, 0x9a, + 0xad, 0xb4, 0x30, 0x2f, 0x83, 0xf9, 0x34, 0xea, 0xd0, 0x36, 0xd9, 0x45, + 0x43, 0x48, 0xa4, 0x34, 0x8e, 0x84, 0x92, 0x33, 0xf3, 0xc9, 0xe1, 0xec, + 0x99, 0xda, 0x50, 0xae, 0x18, 0xd7, 0xed, 0x25, 0x72, 0xc2, 0x54, 0x74, + 0xd9, 0x8a, 0xf7, 0xe5, 0xe8, 0x96, 0x1f, 0xed, 0xec, 0xa2, 0xbe, 0xda, + 0xd9, 0x63, 0xd3, 0x39, 0x1f, 0x28, 0xb5, 0x45, 0x8a, 0x85, 0x6a, 0xb1, + 0x4e, 0x53, 0x3d, 0x94, 0x39, 0x96, 0xe4, 0xcf, 0x24, 0x46, 0xf4, 0x9a, + 0x75, 0xdc, 0x8c, 0xb6, 0xdd, 0x6a, 0xa6, 0xc4, 0xec, 0xd5, 0x1c, 0xe4, + 0xfa, 0x66, 0x49, 0x36, 0x1a, 0xf1, 0x70, 0x12, 0x9a, 0xb5, 0xe0, 0x2a, + 0xab, 0x18, 0xe6, 0x19, 0xcb, 0x5e, 0x47, 0xb6, 0xdd, 0xa9, 0xb9, 0xd1, + 0x54, 0xdd, 0x2b, 0x3d, 0x7b, 0x31, 0x9f, 0xeb, 0x67, 0xdc, 0xa1, 0x28, + 0x1d, 0xe2, 0x87, 0x68, 0xdc, 0xea, 0x77, 0xb8, 0x73, 0x36, 0x7f, 0xb4, + 0x9d, 0x7d, 0x30, 0x0d, 0x88, 0x51, 0x1b, 0xa8, 0x66, 0xd0, 0x5d, 0x2b, + 0xd1, 0x58, 0x3e, 0xc3, 0x47, 0x44, 0x98, 0x4e, 0xc1, 0xd3, 0xce, 0x87, + 0x5b, 0xe2, 0x84, 0xb5, 0x97, 0x1d, 0x99, 0x1f, 0x35, 0x49, 0xaf, 0x5e, + 0x05, 0x9d, 0x72, 0xc2, 0x45, 0xc7, 0xd1, 0xed, 0x0c, 0x27, 0x4a, 0x9d, + 0x41, 0xa3, 0x8e, 0x38, 0x27, 0x55, 0xad, 0x05, 0xed, 0xc1, 0xc0, 0x3a, + 0x4e, 0xec, 0x07, 0x80, 0xa8, 0xc3, 0x9a, 0x9d, 0x43, 0x2d, 0x7a, 0xb2, + 0x19, 0xb1, 0x02, 0xe0, 0x45, 0xed, 0x7f, 0x69, 0x40, 0x01, 0x50, 0x54, + 0x00, 0x14, 0x00, 0x45, 0x05, 0x40, 0x01, 0x50, 0x54, 0x00, 0x5c, 0x31, + 0x13, 0xbe, 0x32, 0x69, 0x7b, 0xd6, 0xf6, 0xa8, 0x09, 0xdd, 0x8b, 0xf1, + 0xf6, 0x3d, 0x2b, 0x81, 0xd8, 0xae, 0xce, 0x7c, 0x6e, 0x1a, 0x93, 0xfe, + 0x6d, 0xe5, 0x31, 0x08, 0x97, 0xf6, 0x01, 0x80, 0x64, 0x2f, 0x37, 0x41, + 0xdb, 0x55, 0xa5, 0x1f, 0x31, 0x2b, 0xed, 0x96, 0xd9, 0x7f, 0xd5, 0x55, + 0x00, 0x54, 0x14, 0x74, 0xbc, 0x33, 0xf5, 0xb6, 0x9b, 0xd9, 0x04, 0xee, + 0x34, 0x71, 0xa5, 0x01, 0x95, 0x88, 0x95, 0x09, 0x7a, 0xf9, 0x48, 0x06, + 0x72, 0xc2, 0xda, 0xa4, 0xfc, 0xca, 0x7c, 0xa0, 0x34, 0xa0, 0x4c, 0xd0, + 0x31, 0x1a, 0x03, 0x66, 0xc2, 0xcb, 0xb3, 0xe1, 0x4b, 0x6b, 0x00, 0x00, + 0xc2, 0xf2, 0x65, 0x33, 0x97, 0xac, 0x05, 0x8d, 0x6b, 0x60, 0x91, 0x03, + 0xa4, 0x76, 0x59, 0x1d, 0x5d, 0xa6, 0x68, 0xde, 0xc1, 0x17, 0x00, 0x49, + 0x40, 0xec, 0xb9, 0x34, 0xb1, 0xa2, 0xa0, 0x8a, 0x82, 0x0a, 0x80, 0xa2, + 0x02, 0xa0, 0x00, 0x28, 0x3a, 0x0a, 0x80, 0xfe, 0x5f, 0x83, 0xbc, 0xb4, + 0xd7, 0x1e, 0xaa, 0x4b, 0x68, 0x80, 0x17, 0x84, 0x62, 0xfe, 0x02, 0x13, + 0x84, 0x82, 0x90, 0xcd, 0xfc, 0xab, 0xef, 0x92, 0xf9, 0xe3, 0x03, 0x2c, + 0x10, 0x4a, 0xf2, 0x77, 0x70, 0xc2, 0x12, 0x08, 0xc5, 0xfc, 0x1d, 0xa3, + 0x20, 0x0a, 0x42, 0x31, 0x7f, 0x1d, 0x89, 0xb5, 0xa0, 0x1f, 0x86, 0x3f, + 0x86, 0xcf, 0x2f, 0x6d, 0xc7, 0x8f, 0xda, 0x38, 0xfd, 0x0f, 0xc3, 0x64, + 0xcf, 0x81, 0x94, 0xa2, 0x56, 0xa7, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, + 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; enum TextureType @@ -178,8 +178,8 @@ enum TextureType TEXTURE_EVENT, TEXTURE_TRIGGER, TEXTURE_PIVOT, - TEXTURE_UNINTERPOLATED, - TEXTURE_INTERPOLATED, + TEXTURE_SQUARE, + TEXTURE_CIRCLE, TEXTURE_PICKER, TEXTURE_FRAME_ALT, TEXTURE_FRAME, @@ -229,6 +229,8 @@ const vec2 ATLAS_UVS[TEXTURE_COUNT][2] = { ATLAS_UV( 48, 64), ATLAS_UV( 88,104) } /* 40 x 40 */ }; +#define ATLAS_UV_ARGS(type) ATLAS_UVS[type][0], ATLAS_UVS[type][1] + const vec2 ATLAS_SIZES[TEXTURE_COUNT] = { TEXTURE_SIZE, @@ -277,7 +279,6 @@ enum ShaderType { SHADER_LINE, SHADER_TEXTURE, - SHADER_LINE_DOTTED, SHADER_COUNT }; @@ -285,11 +286,8 @@ const std::string SHADER_VERTEX = R"( #version 330 core layout (location = 0) in vec2 i_position; layout (location = 1) in vec2 i_uv; - out vec2 i_uv_out; - uniform mat4 u_transform; - void main() { i_uv_out = i_uv; @@ -301,7 +299,6 @@ const std::string SHADER_FRAGMENT = R"( #version 330 core out vec4 o_fragColor; uniform vec4 u_color; - void main() { o_fragColor = u_color; @@ -324,25 +321,6 @@ void main() } )"; -const std::string SHADER_LINE_DOTTED_FRAGMENT = R"( -#version 330 core -uniform vec4 u_color; -out vec4 o_fragColor; -void main() -{ - float patternX = mod(gl_FragCoord.x, 10.0); - float patternY = mod(gl_FragCoord.y, 10.0); - if (patternX < 5.0 || patternY < 5.0) - { - o_fragColor = u_color; - } - else - { - discard; - } -} -)"; - #define SHADER_UNIFORM_COLOR "u_color" #define SHADER_UNIFORM_TRANSFORM "u_transform" #define SHADER_UNIFORM_TINT "u_tint" @@ -352,6 +330,5 @@ void main() const ShaderData SHADER_DATA[SHADER_COUNT] = { {SHADER_VERTEX, SHADER_FRAGMENT}, - {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT}, - {SHADER_VERTEX, SHADER_LINE_DOTTED_FRAGMENT} + {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT} }; diff --git a/src/anm2.cpp b/src/anm2.cpp index 9ab3390..6077a47 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -11,7 +11,7 @@ static void _anm2_created_on_set(Anm2* self) std::tm localTime = *std::localtime(&time); std::ostringstream timeString; - timeString << std::put_time(&localTime, STRING_ANM2_CREATED_ON_FORMAT); + timeString << std::put_time(&localTime, ANM2_CREATED_ON_FORMAT); self->createdOn = timeString.str(); } @@ -54,7 +54,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Spritesheets spritesheetsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_SPRITESHEETS]); - for (auto & [id, spritesheet] : self->spritesheets) + for (auto& [id, spritesheet] : self->spritesheets) { XMLElement* spritesheetElement; @@ -70,7 +70,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Layers layersElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_LAYERS]); - for (auto & [id, layer] : self->layers) + for (auto& [id, layer] : self->layers) { XMLElement* layerElement; @@ -88,7 +88,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Nulls nullsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULLS]); - for (auto & [id, null] : self->nulls) + for (auto& [id, null] : self->nulls) { XMLElement* nullElement; @@ -108,7 +108,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Events eventsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_EVENTS]); - for (auto & [id, event] : self->events) + for (auto& [id, event] : self->events) { XMLElement* eventElement; @@ -125,7 +125,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Animations animationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_ANIMATIONS]); - animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->defaultAnimation.c_str()); // DefaultAnimation + animationsElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DEFAULT_ANIMATION], self->animations[self->defaultAnimationID].name.c_str()); // DefaultAnimation for (auto& [id, animation] : self->animations) { @@ -160,13 +160,13 @@ bool anm2_serialize(Anm2* self, const std::string& path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); // YScale frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); // Delay frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); // Visible - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); // RedTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); // GreenTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); // BlueTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); // AlphaTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); // RedOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); // GreenOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); // BlueOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], FLOAT_TO_U8(frame.tintRGBA.r)); // RedTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], FLOAT_TO_U8(frame.tintRGBA.g)); // GreenTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], FLOAT_TO_U8(frame.tintRGBA.b)); // BlueTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], FLOAT_TO_U8(frame.tintRGBA.a)); // AlphaTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], FLOAT_TO_U8(frame.offsetRGB.r)); // RedOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], FLOAT_TO_U8(frame.offsetRGB.g)); // GreenOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], FLOAT_TO_U8(frame.offsetRGB.b)); // BlueOffset frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); // Rotation frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); // Interpolated @@ -189,7 +189,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) layerAnimationElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_LAYER_ID], layerID); // LayerId layerAnimationElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], layerAnimation.isVisible); // Visible - for (auto & frame : layerAnimation.frames) + for (auto& frame : layerAnimation.frames) { XMLElement* frameElement; @@ -208,13 +208,13 @@ bool anm2_serialize(Anm2* self, const std::string& path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); // YScale frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); /* Delay */ frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); // Visible - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); // RedTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); // GreenTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); // BlueTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); // AlphaTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); // RedOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); // GreenOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); // BlueOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], FLOAT_TO_U8(frame.tintRGBA.r)); // RedTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], FLOAT_TO_U8(frame.tintRGBA.g)); // GreenTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], FLOAT_TO_U8(frame.tintRGBA.b)); // BlueTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], FLOAT_TO_U8(frame.tintRGBA.a)); // AlphaTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], FLOAT_TO_U8(frame.offsetRGB.r)); // RedOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], FLOAT_TO_U8(frame.offsetRGB.g)); // GreenOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], FLOAT_TO_U8(frame.offsetRGB.b)); // BlueOffset frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); // Rotation frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); // Interpolated @@ -229,7 +229,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // NullAnimations nullAnimationsElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_NULL_ANIMATIONS]); - for (const auto & [nullID, nullAnimation] : animation.nullAnimations) + for (const auto& [nullID, nullAnimation] : animation.nullAnimations) { XMLElement* nullAnimationElement; @@ -238,7 +238,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) nullAnimationElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NULL_ID], nullID); // NullId nullAnimationElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], nullAnimation.isVisible); // Visible - for (const auto & frame : nullAnimation.frames) + for (const auto& frame : nullAnimation.frames) { XMLElement* frameElement; @@ -253,13 +253,13 @@ bool anm2_serialize(Anm2* self, const std::string& path) frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_Y_SCALE], frame.scale.y); // XScale frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_DELAY], frame.delay); // Delay frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_VISIBLE], frame.isVisible); // Visible - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.r)); // RedTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.g)); // GreenTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.b)); // BlueTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], COLOR_FLOAT_TO_INT(frame.tintRGBA.a)); // AlphaTint - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.r)); // RedOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.g)); // GreenOffset - frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], COLOR_FLOAT_TO_INT(frame.offsetRGB.b)); // BlueOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_TINT], FLOAT_TO_U8(frame.tintRGBA.r)); // RedTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_TINT], FLOAT_TO_U8(frame.tintRGBA.g)); // GreenTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_TINT], FLOAT_TO_U8(frame.tintRGBA.b)); // BlueTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ALPHA_TINT], FLOAT_TO_U8(frame.tintRGBA.a)); // AlphaTint + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_RED_OFFSET], FLOAT_TO_U8(frame.offsetRGB.r)); // RedOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_GREEN_OFFSET], FLOAT_TO_U8(frame.offsetRGB.g)); // GreenOffset + frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_BLUE_OFFSET], FLOAT_TO_U8(frame.offsetRGB.b)); // BlueOffset frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ROTATION], frame.rotation); // Rotation frameElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_INTERPOLATED], frame.isInterpolated); // Interpolated @@ -274,7 +274,7 @@ bool anm2_serialize(Anm2* self, const std::string& path) // Triggers triggersElement = document.NewElement(ANM2_ELEMENT_STRINGS[ANM2_ELEMENT_TRIGGERS]); - for (const auto & frame : animation.triggers.frames) + for (const auto& frame : animation.triggers.frames) { XMLElement* triggerElement; @@ -328,10 +328,11 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) s32 layerMapIndex = 0; bool isLayerMapSet = false; bool isFirstAnimationDone = false; + std::string defaultAnimation{}; if (!self || path.empty()) return false; - *self = Anm2{}; + anm2_new(self); xmlError = xmlDocument.LoadFile(path.c_str()); @@ -418,13 +419,13 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) self->createdOn = xmlAttribute->Value(); break; case ANM2_ATTRIBUTE_VERSION: // Version - self->version = atoi(xmlAttribute->Value()); + self->version = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_FPS: // FPS - self->fps = atoi(xmlAttribute->Value()); + self->fps = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_ID: // ID - id = atoi(xmlAttribute->Value()); + id = std::atoi(xmlAttribute->Value()); switch (anm2Element) { case ANM2_ELEMENT_SPRITESHEET: // Spritesheet @@ -448,7 +449,7 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) } break; case ANM2_ATTRIBUTE_LAYER_ID: // LayerId - id = atoi(xmlAttribute->Value()); + id = std::atoi(xmlAttribute->Value()); if (!isLayerMapSet) { @@ -460,7 +461,7 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) item = &animation->layerAnimations[id]; break; case ANM2_ATTRIBUTE_NULL_ID: // NullId - id = atoi(xmlAttribute->Value()); + id = std::atoi(xmlAttribute->Value()); animation->nullAnimations[id] = addItem; item = &animation->nullAnimations[id]; break; @@ -487,52 +488,52 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) } break; case ANM2_ATTRIBUTE_SPRITESHEET_ID: - layer->spritesheetID = atoi(xmlAttribute->Value()); + layer->spritesheetID = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_SHOW_RECT: null->isShowRect = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_DEFAULT_ANIMATION: - self->defaultAnimation = xmlAttribute->Value(); + defaultAnimation = xmlAttribute->Value(); break; case ANM2_ATTRIBUTE_FRAME_NUM: - animation->frameNum = atoi(xmlAttribute->Value()); + animation->frameNum = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_LOOP: animation->isLoop = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_POSITION: - frame->position.x = atof(xmlAttribute->Value()); + frame->position.x = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_POSITION: - frame->position.y = atof(xmlAttribute->Value()); + frame->position.y = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_PIVOT: - frame->pivot.x = atof(xmlAttribute->Value()); + frame->pivot.x = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_PIVOT: - frame->pivot.y = atof(xmlAttribute->Value()); + frame->pivot.y = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_CROP: - frame->crop.x = atof(xmlAttribute->Value()); + frame->crop.x = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_CROP: - frame->crop.y = atof(xmlAttribute->Value()); + frame->crop.y = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_WIDTH: - frame->size.x = atof(xmlAttribute->Value()); + frame->size.x = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_HEIGHT: - frame->size.y = atof(xmlAttribute->Value()); + frame->size.y = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_X_SCALE: - frame->scale.x = atof(xmlAttribute->Value()); + frame->scale.x = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_Y_SCALE: - frame->scale.y = atof(xmlAttribute->Value()); + frame->scale.y = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_DELAY: - frame->delay = atoi(xmlAttribute->Value()); + frame->delay = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_VISIBLE: switch (anm2Element) @@ -550,37 +551,37 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) } break; case ANM2_ATTRIBUTE_RED_TINT: - frame->tintRGBA.r = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->tintRGBA.r = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_TINT: - frame->tintRGBA.g = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->tintRGBA.g = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_TINT: - frame->tintRGBA.b = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->tintRGBA.b = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_ALPHA_TINT: - frame->tintRGBA.a = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->tintRGBA.a = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_RED_OFFSET: - frame->offsetRGB.r = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->offsetRGB.r = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_GREEN_OFFSET: - frame->offsetRGB.g = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->offsetRGB.g = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_BLUE_OFFSET: - frame->offsetRGB.b = COLOR_INT_TO_FLOAT(atoi(xmlAttribute->Value())); + frame->offsetRGB.b = U8_TO_FLOAT(std::atoi(xmlAttribute->Value())); break; case ANM2_ATTRIBUTE_ROTATION: - frame->rotation = atof(xmlAttribute->Value()); + frame->rotation = std::atof(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_INTERPOLATED: frame->isInterpolated = string_to_bool(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_EVENT_ID: - frame->eventID = atoi(xmlAttribute->Value()); + frame->eventID = std::atoi(xmlAttribute->Value()); break; case ANM2_ATTRIBUTE_AT_FRAME: - frame->atFrame = atoi(xmlAttribute->Value()); + frame->atFrame = std::atoi(xmlAttribute->Value()); break; default: break; @@ -589,7 +590,8 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) xmlAttribute = xmlAttribute->Next(); } - 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(); @@ -615,6 +617,11 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) } } + // Set default animation ID + for (auto& [id, animation] : self->animations) + if (animation.name == defaultAnimation) + self->defaultAnimationID = id; + log_info(std::format(ANM2_READ_INFO, path)); // Return to old working directory @@ -625,24 +632,48 @@ bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path) void anm2_layer_add(Anm2* self) { + + + + + + + + s32 id = map_next_id_get(self->layers); self->layers[id] = Anm2Layer{}; - - for (auto & [animationID, animation] : self->animations) + self->layerMap[self->layers.size() - 1] = id; + + for (auto& [_, animation] : self->animations) animation.layerAnimations[id] = Anm2Item{}; } void anm2_layer_remove(Anm2* self, s32 id) { - // Make sure the layer exists - auto it = self->layers.find(id); - if (it == self->layers.end()) - return; + if (!self->layers.contains(id)) + return; self->layers.erase(id); - for (auto & [animationID, animation] : self->animations) + for (auto it = self->layerMap.begin(); it != self->layerMap.end(); ++it) + { + if (it->second == id) + { + self->layerMap.erase(it); + break; + } + } + + std::map newLayerMap; + s32 newIndex = 0; + + for (const auto& [_, layerID] : self->layerMap) + newLayerMap[newIndex++] = layerID; + + self->layerMap = std::move(newLayerMap); + + for (auto& [_, animation] : self->animations) animation.layerAnimations.erase(id); } @@ -652,23 +683,40 @@ void anm2_null_add(Anm2* self) self->nulls[id] = Anm2Null{}; - for (auto & [animationID, animation] : self->animations) + for (auto& [_, animation] : self->animations) animation.nullAnimations[id] = Anm2Item{}; } void anm2_null_remove(Anm2* self, s32 id) { - // Make sure the null exists - auto it = self->nulls.find(id); - if (it == self->nulls.end()) - return; + if (!self->nulls.contains(id)) + return; self->nulls.erase(id); - for (auto & [animationID, animation] : self->animations) - animation.nullAnimations.erase(id); + std::map newNulls; + s32 newID = 0; + + for (const auto& [_, null] : self->nulls) + newNulls[newID++] = null; + + self->nulls = std::move(newNulls); + + for (auto& [_, animation] : self->animations) + { + if (animation.nullAnimations.contains(id)) + animation.nullAnimations.erase(id); + + std::map newNullAnims; + s32 newAnimID = 0; + for (const auto& [_, nullAnim] : animation.nullAnimations) + newNullAnims[newAnimID++] = nullAnim; + + animation.nullAnimations = std::move(newNullAnims); + } } + s32 anm2_animation_add(Anm2* self) { s32 id = map_next_id_get(self->animations); @@ -676,7 +724,7 @@ s32 anm2_animation_add(Anm2* self) for (auto& [layerID, layer] : self->layers) animation.layerAnimations[layerID] = Anm2Item{}; - for (auto & [nullID, null] : self->nulls) + for (auto& [nullID, null] : self->nulls) animation.nullAnimations[nullID] = Anm2Item{}; animation.rootAnimation.frames.push_back(Anm2Frame{}); @@ -699,13 +747,19 @@ void anm2_new(Anm2* self) Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference) { - auto it = self->animations.find(reference->animationID); - if (it == self->animations.end()) return nullptr; - return &it->second; + if (reference->animationID == ID_NONE) return nullptr; + + if (!self->animations.contains(reference->animationID)) + return nullptr; + + return &self->animations[reference->animationID]; } Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference) { + if (reference->itemType == ANM2_NONE) + return nullptr; + Anm2Animation* animation = anm2_animation_from_reference(self, reference); if (!animation) return nullptr; @@ -715,19 +769,11 @@ Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference) case ANM2_ROOT: return &animation->rootAnimation; case ANM2_LAYER: - { - auto it = animation->layerAnimations.find(reference->itemID); - if (it == animation->layerAnimations.end()) - return nullptr; - return &it->second; - } + if (!animation->layerAnimations.contains(reference->itemID)) return nullptr; + return &animation->layerAnimations[reference->itemID]; case ANM2_NULL: - { - auto it = animation->nullAnimations.find(reference->itemID); - if (it == animation->nullAnimations.end()) - return nullptr; - return &it->second; - } + if (!animation->nullAnimations.contains(reference->itemID)) return nullptr; + return &animation->nullAnimations[reference->itemID]; case ANM2_TRIGGERS: return &animation->triggers; default: @@ -740,7 +786,9 @@ Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference) Anm2Item* item = anm2_item_from_reference(self, reference); if (!item) return nullptr; - if (reference->frameIndex < 0 || reference->frameIndex >= (s32)item->frames.size()) return nullptr; + + if (reference->frameIndex <= INDEX_NONE || reference->frameIndex >= (s32)item->frames.size()) + return nullptr; return &item->frames[reference->frameIndex]; } @@ -777,19 +825,19 @@ void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, Anm2Animation* animation = anm2_animation_from_reference(self, &reference); if (!animation) return; - if (time < 0 || time > animation->frameNum) return; + + time = std::clamp(time, 0.0f, animation->frameNum - 1.0f); Anm2Item* item = anm2_item_from_reference(self, &reference); if (!item) return; - Anm2Frame* nextFrame = nullptr; + Anm2Frame* frameNext = nullptr; s32 delayCurrent = 0; s32 delayNext = 0; for (auto [i, iFrame] : std::views::enumerate(item->frames)) { - if (reference.itemType == ANM2_TRIGGERS) { if ((s32)time == iFrame.atFrame) @@ -807,9 +855,9 @@ void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, if (time >= delayCurrent && time < delayNext) { if (i + 1 < (s32)item->frames.size()) - nextFrame = &item->frames[i + 1]; + frameNext = &item->frames[i + 1]; else - nextFrame = nullptr; + frameNext = nullptr; break; } @@ -820,15 +868,15 @@ void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, if (reference.itemType == ANM2_TRIGGERS) return; - if (frame->isInterpolated && nextFrame) + if (frame->isInterpolated && frameNext && frame->delay > 1) { - f32 interpolationTime = (time - delayCurrent) / (delayNext - delayCurrent); + f32 interpolation = (time - delayCurrent) / (delayNext - delayCurrent); - frame->rotation = glm::mix(frame->rotation, nextFrame->rotation, interpolationTime);; - frame->position = glm::mix(frame->position, nextFrame->position, interpolationTime);; - frame->scale = glm::mix(frame->scale, nextFrame->scale, interpolationTime);; - frame->offsetRGB = glm::mix(frame->offsetRGB, nextFrame->offsetRGB, interpolationTime);; - frame->tintRGBA = glm::mix(frame->tintRGBA, nextFrame->tintRGBA, interpolationTime);; + frame->rotation = glm::mix(frame->rotation, frameNext->rotation, interpolation); + frame->position = glm::mix(frame->position, frameNext->position, interpolation); + frame->scale = glm::mix(frame->scale, frameNext->scale, interpolation); + frame->offsetRGB = glm::mix(frame->offsetRGB, frameNext->offsetRGB, interpolation); + frame->tintRGBA = glm::mix(frame->tintRGBA, frameNext->tintRGBA, interpolation); } } @@ -870,7 +918,8 @@ 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 nullptr; + if (!animation || !item) + return nullptr; if (item) { @@ -879,7 +928,7 @@ Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) if (reference->itemType == ANM2_TRIGGERS) { - for (auto & frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr; + for (auto& frameCheck : item->frames) if (frameCheck.atFrame == time) return nullptr; frame.atFrame = time; index = item->frames.size(); @@ -888,7 +937,7 @@ Anm2Frame* anm2_frame_add(Anm2* self, Anm2Reference* reference, s32 time) { s32 frameDelayCount = 0; - for (auto & frameCheck : item->frames) + for (auto& frameCheck : item->frames) frameDelayCount += frameCheck.delay; if (frameDelayCount + ANM2_FRAME_DELAY_MIN > animation->frameNum) return nullptr; @@ -929,4 +978,106 @@ void anm2_reference_item_clear(Anm2Reference* self) void anm2_reference_frame_clear(Anm2Reference* self) { self->frameIndex = INDEX_NONE; +} + +void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type) +{ + Anm2Animation newAnimation = self->animations[animationID]; + + newAnimation.rootAnimation.frames.clear(); + + for (auto& [id, layerAnimation] : newAnimation.layerAnimations) + layerAnimation.frames.clear(); + + for (auto& [id, nullAnimation] : newAnimation.nullAnimations) + nullAnimation.frames.clear(); + + newAnimation.triggers.frames.clear(); + + auto merge_item = [&](Anm2Item& destinationItem, const Anm2Item& sourceItem) + { + switch (type) + { + case ANM2_MERGE_APPEND_FRAMES: + destinationItem.frames.insert(destinationItem.frames.end(), sourceItem.frames.begin(), sourceItem.frames.end()); + break; + case ANM2_MERGE_PREPEND_FRAMES: + destinationItem.frames.insert(destinationItem.frames.begin(), sourceItem.frames.begin(), sourceItem.frames.end()); + break; + case ANM2_MERGE_REPLACE_FRAMES: + if (destinationItem.frames.size() < sourceItem.frames.size()) + destinationItem.frames.resize(sourceItem.frames.size()); + for (s32 i = 0; i < (s32)sourceItem.frames.size(); i++) + destinationItem.frames[i] = sourceItem.frames[i]; + break; + case ANM2_MERGE_IGNORE: + break; + } + }; + + for (auto mergeID : mergeIDs) + { + const Anm2Animation& mergeAnimation = self->animations[mergeID]; + + merge_item(newAnimation.rootAnimation, mergeAnimation.rootAnimation); + + for (const auto& [id, layerAnimation] : mergeAnimation.layerAnimations) + merge_item(newAnimation.layerAnimations[id], layerAnimation); + + for (const auto& [id, nullAnimation] : mergeAnimation.nullAnimations) + merge_item(newAnimation.nullAnimations[id], nullAnimation); + + merge_item(newAnimation.triggers, mergeAnimation.triggers); + } + + self->animations[animationID] = newAnimation; + + anm2_animation_length_set(&self->animations[animationID]); +} + +void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation) +{ + Anm2Item* item = anm2_item_from_reference(self, reference); + if (!item) return; + + Anm2Frame* frame = anm2_frame_from_reference(self, reference); + if (!frame) return; + + Anm2Reference referenceNext = *reference; + referenceNext.frameIndex = reference->frameIndex + 1; + + Anm2Frame* frameNext = anm2_frame_from_reference(self, &referenceNext); + if (!frameNext) return; + + const Anm2Frame baseFrame = *frame; + const Anm2Frame baseFrameNext = *frameNext; + + s32 delay = 0; + s32 insertIndex = reference->frameIndex; + + while (delay < baseFrame.delay) + { + f32 interpolation = (f32)delay / baseFrame.delay; + + Anm2Frame baked = *frame; + baked.delay = std::min(interval, baseFrame.delay - delay); + baked.isInterpolated = (insertIndex == reference->frameIndex) ? baseFrame.isInterpolated : false; + + baked.rotation = glm::mix(baseFrame.rotation, baseFrameNext.rotation, interpolation); + baked.position = glm::mix(baseFrame.position, baseFrameNext.position, interpolation); + baked.scale = glm::mix(baseFrame.scale, baseFrameNext.scale, interpolation); + baked.offsetRGB = glm::mix(baseFrame.offsetRGB, baseFrameNext.offsetRGB, interpolation); + baked.tintRGBA = glm::mix(baseFrame.tintRGBA, baseFrameNext.tintRGBA, interpolation); + + if (isRoundScale) baked.scale = vec2((s32)baked.scale.x, (s32)baked.scale.y); + if (isRoundRotation) baked.rotation = (s32)baked.rotation; + + if (insertIndex == reference->frameIndex) + item->frames[insertIndex] = baked; + else + item->frames.insert(item->frames.begin() + insertIndex, baked); + insertIndex++; + + delay += baked.delay; + } } \ No newline at end of file diff --git a/src/anm2.h b/src/anm2.h index 3400e20..340017b 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -17,7 +17,7 @@ #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" +#define ANM2_CREATED_ON_FORMAT "%d-%B-%Y %I:%M:%S %p" /* Elements */ #define ANM2_ELEMENT_LIST \ @@ -132,7 +132,7 @@ struct Anm2Spritesheet struct Anm2Layer { std::string name = "New Layer"; - s32 spritesheetID = -1; + s32 spritesheetID = ID_NONE; }; struct Anm2Null @@ -154,12 +154,12 @@ struct Anm2Frame s32 delay = ANM2_FRAME_DELAY_MIN; 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}; - vec2 size = {0.0f, 0.0f}; - vec2 scale = {100.0f, 100.0f}; - vec3 offsetRGB = {0.0f, 0.0f, 0.0f}; + vec2 crop{}; + vec2 pivot{}; + vec2 position{}; + vec2 size{}; + vec2 scale{}; + vec3 offsetRGB{}; vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; }; @@ -183,7 +183,6 @@ struct Anm2Animation struct Anm2 { std::string path{}; - std::string defaultAnimation{}; std::string createdBy = "robot"; std::string createdOn{}; std::map spritesheets; @@ -191,9 +190,10 @@ struct Anm2 std::map nulls; std::map events; std::map animations; - std::map layerMap; // id, index + std::map layerMap; // index, id + s32 defaultAnimationID{}; s32 fps = 30; - s32 version = 0; + s32 version{}; }; struct Anm2Reference @@ -223,6 +223,14 @@ struct Anm2FrameWithReference Anm2Frame frame; }; +enum Anm2MergeType +{ + ANM2_MERGE_APPEND_FRAMES, + ANM2_MERGE_REPLACE_FRAMES, + ANM2_MERGE_PREPEND_FRAMES, + ANM2_MERGE_IGNORE +}; + void anm2_layer_add(Anm2* self); void anm2_layer_remove(Anm2* self, s32 id); void anm2_null_add(Anm2* self); @@ -244,4 +252,6 @@ 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(Anm2Animation* self); -void anm2_animation_length_set(Anm2Animation* self); \ No newline at end of file +void anm2_animation_length_set(Anm2Animation* self); +void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector& mergeIDs, Anm2MergeType type); +void anm2_frame_bake(Anm2* self, Anm2Reference* reference, s32 interval, bool isRoundScale, bool isRoundRotation); \ No newline at end of file diff --git a/src/canvas.cpp b/src/canvas.cpp index 6010b26..6225dc3 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -2,10 +2,19 @@ #include "canvas.h" -void canvas_init(Canvas* self, vec2 size) +static void _canvas_texture_free(Canvas* self) { - self->size = size; + if (self->fbo != 0) glDeleteFramebuffers(1, &self->fbo); + if (self->rbo != 0) glDeleteRenderbuffers(1, &self->rbo); + if (self->texture != 0) glDeleteTextures(1, &self->texture); +} +static void _canvas_texture_init(Canvas* self, vec2& size) +{ + _canvas_texture_free(self); + + self->size = size; + glGenFramebuffers(1, &self->fbo); glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); @@ -24,6 +33,43 @@ void canvas_init(Canvas* self, vec2 size) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void canvas_init(Canvas* self) +{ + // Axis + glGenVertexArrays(1, &self->axisVAO); + glGenBuffers(1, &self->axisVBO); + + glBindVertexArray(self->axisVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->axisVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + + // Grid + glGenVertexArrays(1, &self->gridVAO); + glGenBuffers(1, &self->gridVBO); + + glBindVertexArray(self->gridVAO); + glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); + + // Rect + glGenVertexArrays(1, &self->rectVAO); + glGenBuffers(1, &self->rectVBO); + + glBindVertexArray(self->rectVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(GL_VERTICES), GL_VERTICES, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); // Texture glGenVertexArrays(1, &self->textureVAO); @@ -36,15 +82,240 @@ void canvas_init(Canvas* self, vec2 size) 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); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GL_TEXTURE_INDICES), GL_TEXTURE_INDICES, GL_DYNAMIC_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); +} + +mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin) +{ + f32 zoomFactor = PERCENT_TO_UNIT(zoom); + mat4 projection = glm::ortho(0.0f, self->size.x, 0.0f, self->size.y, -1.0f, 1.0f); + mat4 view = mat4{1.0f}; + + switch (origin) + { + case ORIGIN_TOP_LEFT: + view = glm::translate(view, vec3(pan, 0.0f)); + break; + default: + view = glm::translate(view, vec3((self->size * 0.5f) + pan, 0.0f)); + break; + } + + view = glm::scale(view, vec3(zoomFactor, zoomFactor, 1.0f)); + + return projection * view; +} + +void canvas_clear(vec4& color) +{ + glClearColor(color.r, color.g, color.b, color.a); + glClear(GL_COLOR_BUFFER_BIT); +} + +void canvas_viewport_set(Canvas* self) +{ + glViewport(0, 0, (s32)self->size.x, (s32)self->size.y); +} + +void canvas_texture_set(Canvas* self) +{ + static vec2 previousSize = {-1, -1}; + + if (previousSize != self->size) + { + _canvas_texture_init(self, self->size); + previousSize = self->size; + } +} + +void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, f32& zoom, ivec2& size, ivec2& offset, vec4& color) +{ + if (size.x <= 0 || size.y <= 0) + return; // avoid div-by-zero + + std::vector vertices; + + vec2 gridSize = self->size * (PERCENT_TO_UNIT(CANVAS_ZOOM_MAX - zoom)); + + // First visible vertical line <= 0 + s32 startX = -(offset.x % size.x); + if (startX > 0) startX -= size.x; + + for (s32 x = startX; x <= gridSize.x; x += size.x) + { + vertices.push_back((f32)x); + vertices.push_back(0.0f); + vertices.push_back((f32)x); + vertices.push_back((f32)gridSize.y); + } + + // First visible horizontal line <= 0 + s32 startY = -(offset.y % size.y); + if (startY > 0) startY -= size.y; + + for (s32 y = startY; y <= gridSize.y; y += size.y) + { + vertices.push_back(0.0f); + vertices.push_back((f32)y); + vertices.push_back((f32)gridSize.x); + vertices.push_back((f32)y); + } + + s32 vertexCount = (s32)vertices.size() / 2; + + if (vertexCount == 0) + return; + + glBindVertexArray(self->gridVAO); + glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); + + glUseProgram(shader); + glBindVertexArray(self->gridVAO); + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a); + glDrawArrays(GL_LINES, 0, vertexCount); glBindVertexArray(0); -} \ No newline at end of file + glUseProgram(0); +} + +void canvas_texture_draw(Canvas* self, GLuint& shader, GLuint& texture, mat4& transform, const f32* vertices, vec4 tint, vec3 colorOffset) +{ + glUseProgram(shader); + + glBindVertexArray(self->textureVAO); + + glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(GL_UV_VERTICES), vertices, GL_DYNAMIC_DRAW); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_TEXTURE), 0); + glUniform3fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(colorOffset)); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TINT), 1, value_ptr(tint)); + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glUseProgram(0); +} + +void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color) +{ + glUseProgram(shader); + + glBindVertexArray(self->rectVAO); + + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); + + glDrawArrays(GL_LINE_LOOP, 0, 4); + + glBindVertexArray(0); + glUseProgram(0); +} + +void canvas_rect_dotted_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color) +{ + glUseProgram(shader); + + glBindVertexArray(self->rectVAO); + + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color)); + + glDrawArrays(GL_LINE_LOOP, 0, 4); + + glBindVertexArray(0); + glUseProgram(0); +} + +void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color) +{ + glUseProgram(shader); + glBindVertexArray(self->axisVAO); + glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); + glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a); + glDrawArrays(GL_LINES, 0, 4); + glBindVertexArray(0); + glUseProgram(0); +} + +void canvas_bind(Canvas* self) +{ + glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); +} + +void canvas_unbind(void) +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +void canvas_free(Canvas* self) +{ + _canvas_texture_free(self); +} + +mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale, vec2 pivotAlt, f32 rotationAlt) +{ + vec2 scaleAbsolute = glm::abs(scale); + vec2 scaleSign = glm::sign(scale); + f32 usedSign = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; + + vec2 sizeScaled = size * scaleAbsolute; + vec2 pivotScaled = pivot * scaleAbsolute; + vec2 pivotAltScaled = pivotAlt * scaleAbsolute; + + vec2 pivotAltMirrored = pivotScaled + (pivotAltScaled - pivotScaled) * scaleSign; + + mat4 model = glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f)); + model = glm::translate(model, vec3(pivotScaled, 0.0f)); + model = glm::scale(model, vec3(scaleSign, 1.0f)); + model = glm::rotate(model, glm::radians(rotation) * usedSign, vec3(0,0,1)); + model = glm::translate(model, vec3(-pivotScaled, 0.0f)); + model = glm::translate(model, vec3(pivotAltMirrored, 0.0f)); + model = glm::rotate(model, glm::radians(rotationAlt) * usedSign, vec3(0,0,1)); + model = glm::translate(model, vec3(-pivotAltMirrored, 0.0f)); + model = glm::scale(model, vec3(sizeScaled, 1.0f)); + + return transform * model; +} + +/* +mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale, vec2 pivotAlt, f32 rotationAlt) +{ + vec2 scaleAbsolute = abs(scale); + vec2 signScale = glm::sign(scale); + vec2 pivotScaled = pivot * scaleAbsolute; + vec2 pivotAltScaled = pivotAlt * scaleAbsolute; + vec2 sizeScaled = size * scaleAbsolute; + + mat4 model = glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f)); + + model = glm::translate(model, vec3(pivotScaled, 0.0f)); + model = glm::scale(model, vec3(signScale, 1.0f)); // Flip + model = glm::rotate(model, radians(rotation), vec3(0,0,1)); + model = glm::translate(model, vec3(-pivotScaled, 0.0f)); + + model = glm::translate(model, vec3(pivotAltScaled, 0.0f)); + model = glm::rotate(model, radians(rotationAlt), vec3(0,0,1)); + model = glm::translate(model, vec3(-pivotAltScaled, 0.0f)); + + model = glm::scale(model, vec3(sizeScaled, 1.0f)); + + return transform * model; +} +*/ \ No newline at end of file diff --git a/src/canvas.h b/src/canvas.h index b012a63..9e12075 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -1,33 +1,75 @@ #pragma once -#include "texture.h" -#include "shader.h" +#include "resources.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_ZOOM_MIN 1.0f +#define CANVAS_ZOOM_MAX 2000.0f +#define CANVAS_ZOOM_STEP 10.0f +#define CANVAS_ZOOM_MOD 10.0f #define CANVAS_GRID_MIN 1 -#define CANVAS_GRID_MAX 100 +#define CANVAS_GRID_MAX 1000 +#define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f) + +static const vec2 CANVAS_GRID_SIZE = {3200, 1600}; +static const vec2 CANVAS_PIVOT_SIZE = {4, 4}; 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 + -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; + GLuint fbo{}; + GLuint rbo{}; + GLuint axisVAO{}; + GLuint axisVBO{}; + GLuint rectVAO{}; + GLuint rectVBO{}; + GLuint gridVAO{}; + GLuint gridVBO{}; + GLuint texture{}; + GLuint textureEBO{}; + GLuint textureVAO{}; + GLuint textureVBO{}; + vec2 size{}; }; -void canvas_init(Canvas* self, vec2 size); \ No newline at end of file +void canvas_init(Canvas* self); +mat4 canvas_transform_get(Canvas* self, vec2& pan, f32& zoom, OriginType origin); +void canvas_clear(vec4& color); +void canvas_bind(Canvas* self); +void canvas_viewport_set(Canvas* self); +void canvas_unbind(void); +void canvas_texture_set(Canvas* self); +void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, f32& zoom, ivec2& size, ivec2& offset, vec4& color); +void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color); +void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color); +void canvas_free(Canvas* self); +void canvas_draw(Canvas* self); + +mat4 canvas_mvp_get +( + mat4& transform, + vec2 size, + vec2 position = {0.0f, 0.0f}, + vec2 pivot = {0.0f, 0.0f}, + f32 rotation = {0.0f}, + vec2 scale = {1.0f, 1.0f}, + vec2 pivotAlt = {0.0f, 0.0f}, + f32 rotationAlt = {0.0f} +); + +void canvas_texture_draw +( + Canvas* self, + GLuint& shader, + GLuint& texture, + mat4& transform, + const f32* vertices = GL_UV_VERTICES, + vec4 tint = COLOR_OPAQUE, + vec3 colorOffset = COLOR_OFFSET_NONE +); \ No newline at end of file diff --git a/src/clipboard.cpp b/src/clipboard.cpp index ade416c..9021ef6 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -47,17 +47,6 @@ static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2) } 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; } @@ -81,7 +70,7 @@ static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* locati if (!animation || !anm2Item) break; s32 insertIndex = (reference->itemType == ANM2_TRIGGERS) ? - reference->frameIndex : MAX(reference->frameIndex, (s32)anm2Item->frames.size()); + reference->frameIndex : std::max(reference->frameIndex, (s32)anm2Item->frames.size()); anm2Item->frames.insert(anm2Item->frames.begin() + insertIndex, frameWithReference->frame); @@ -95,23 +84,11 @@ static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* locati if (std::holds_alternative(*location)) index = std::get(*location); - index = CLAMP(index, 0, (s32)anm2->animations.size()); + index = std::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; } diff --git a/src/clipboard.h b/src/clipboard.h index df4db7b..b89c852 100644 --- a/src/clipboard.h +++ b/src/clipboard.h @@ -7,7 +7,6 @@ enum ClipboardItemType CLIPBOARD_NONE, CLIPBOARD_FRAME, CLIPBOARD_ANIMATION, - CLIPBOARD_EVENT }; struct ClipboardItem @@ -22,9 +21,6 @@ struct ClipboardItem ClipboardItem(const Anm2AnimationWithID& anim) : data(anim), type(CLIPBOARD_ANIMATION) {} - - ClipboardItem(const Anm2EventWithID& event) - : data(event), type(CLIPBOARD_EVENT) {} }; using ClipboardLocation = std::variant; diff --git a/src/dialog.cpp b/src/dialog.cpp index 5d61891..52a32c2 100644 --- a/src/dialog.cpp +++ b/src/dialog.cpp @@ -14,9 +14,13 @@ static void _dialog_callback(void* userdata, const char* const* filelist, s32 fi { self->path = filelist[0]; self->isSelected = true; + self->selectedFilter = filter; } else + { self->isSelected = false; + self->selectedFilter = INDEX_NONE; + } } void dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window) @@ -51,8 +55,10 @@ void dialog_png_replace(Dialog* self) self->type = DIALOG_PNG_REPLACE; } -void dialog_tick(Dialog* self) +void dialog_update(Dialog* self) { + self->isJustSelected = false; + if (self->isSelected) { Texture texture; @@ -73,7 +79,7 @@ void dialog_tick(Dialog* self) case DIALOG_PNG_OPEN: id = map_next_id_get(self->resources->textures); self->anm2->spritesheets[id] = Anm2Spritesheet{}; - self->path = self->anm2->spritesheets[id].path; + self->anm2->spritesheets[id].path = self->path; resources_texture_init(self->resources, self->path, id); break; case DIALOG_PNG_REPLACE: @@ -85,8 +91,23 @@ void dialog_tick(Dialog* self) break; } + self->lastType = self->type; + self->lastPath = self->path; + self->type = DIALOG_NONE; self->path.clear(); + + self->isJustSelected = true; self->isSelected = false; } } +void +dialog_reset(Dialog* self) +{ + self->lastType = DIALOG_NONE; + self->type = DIALOG_NONE; + self->lastPath.clear(); + self->path.clear(); + self->isJustSelected = false; + self->isSelected = false; +} \ No newline at end of file diff --git a/src/dialog.h b/src/dialog.h index f9f895b..bdfcdc4 100644 --- a/src/dialog.h +++ b/src/dialog.h @@ -31,9 +31,13 @@ struct Dialog Resources* resources = nullptr; SDL_Window* window = nullptr; std::string path{}; + std::string lastPath{}; s32 replaceID = ID_NONE; + s32 selectedFilter{}; DialogType type = DIALOG_NONE; + DialogType lastType = DIALOG_NONE; bool isSelected = false; + bool isJustSelected = false; }; void dialog_init(Dialog* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, SDL_Window* window); @@ -42,4 +46,5 @@ void dialog_png_open(Dialog* self); void dialog_png_replace(Dialog* self); void dialog_anm2_save(Dialog* self); void dialog_frame_directory_open(Dialog* self); -void dialog_tick(Dialog* self); \ No newline at end of file +void dialog_update(Dialog* self); +void dialog_reset(Dialog* self); \ No newline at end of file diff --git a/src/editor.cpp b/src/editor.cpp index f308fba..293fb4d 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -2,47 +2,6 @@ #include "editor.h" -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 - for (s32 i = 0; i <= verticalLineCount; i++) - { - s32 x = i * self->settings->editorGridSizeX - self->settings->editorGridOffsetX; - f32 normX = (2.0f * x) / EDITOR_SIZE.x - 1.0f; - - vertices.push_back(normX); - vertices.push_back(-1.0f); - vertices.push_back(normX); - vertices.push_back(1.0f); - } - - // Horizontal - for (s32 i = 0; i <= horizontalLineCount; i++) - { - s32 y = i * self->settings->editorGridSizeY - self->settings->editorGridOffsetY; - f32 normY = (2.0f * y) / EDITOR_SIZE.y - 1.0f; - - vertices.push_back(-1.0f); - vertices.push_back(normY); - vertices.push_back(1.0f); - vertices.push_back(normY); - } - - glBindVertexArray(self->gridVAO); - glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); - - return (s32)vertices.size(); -} - void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { self->anm2 = anm2; @@ -50,253 +9,53 @@ void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* self->resources = resources; self->settings = settings; - // Framebuffer + texture - 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)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); - - 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)EDITOR_SIZE.x, (s32)EDITOR_SIZE.y); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Grid - glGenVertexArrays(1, &self->gridVAO); - glGenBuffers(1, &self->gridVBO); - - // Border - glGenVertexArrays(1, &self->borderVAO); - glGenBuffers(1, &self->borderVBO); - - glBindVertexArray(self->borderVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->borderVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(GL_VERTICES), GL_VERTICES, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)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 position attribute - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); - - glBindVertexArray(0); - - _editor_grid_set(self); + canvas_init(&self->canvas); } 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); + ivec2& gridSize = self->settings->editorGridSize; + ivec2& gridOffset = self->settings->editorGridOffset; + vec4& gridColor = self->settings->editorGridColor; + GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; + GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT); - // Get normalized panning - glm::vec2 ndcPan = glm::vec2(-self->settings->editorPanX / (EDITOR_SIZE.x / 2.0f), -self->settings->editorPanY / (EDITOR_SIZE.y / 2.0f)); + canvas_texture_set(&self->canvas); + + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->editorBackgroundColor); - glm::mat4 editorTransform = glm::translate(glm::mat4(1.0f), glm::vec3(ndcPan, 0.0f)); - editorTransform = glm::scale(editorTransform, glm::vec3(zoomFactor, zoomFactor, 1.0f)); - - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); - glViewport(0, 0, EDITOR_SIZE.x, EDITOR_SIZE.y); - - glClearColor - ( - self->settings->editorBackgroundColorR, - self->settings->editorBackgroundColorG, - self->settings->editorBackgroundColorB, - self->settings->editorBackgroundColorA - ); - - glClear(GL_COLOR_BUFFER_BIT); - - // Drawing the selected spritesheet - if (self->spritesheetID > -1) + if (self->spritesheetID != ID_NONE) { - Texture* texture = &self->resources->textures[self->spritesheetID]; + Texture texture = self->resources->textures[self->spritesheetID]; + mat4 mvp = canvas_mvp_get(transform, texture.size); + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, mvp); - glm::mat4 spritesheetTransform = editorTransform; - glm::vec2 ndcScale = glm::vec2(texture->size.x, texture->size.y) / (EDITOR_SIZE * 0.5f); - - spritesheetTransform = glm::scale(spritesheetTransform, glm::vec3(ndcScale, 1.0f)); - - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(GL_UV_VERTICES), GL_UV_VERTICES, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture->id); - - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(COLOR_OPAQUE)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(COLOR_OFFSET_NONE)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(spritesheetTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindTexture(GL_TEXTURE_2D, 0); - - glBindVertexArray(0); - glUseProgram(0); - - // Border around the spritesheet if (self->settings->editorIsBorder) - { - glUseProgram(shaderLineDotted); - - glBindVertexArray(self->borderVAO); - - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, glm::value_ptr(spritesheetTransform)); - glUniform4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), 1, glm::value_ptr(EDITOR_BORDER_TINT)); - - glDrawArrays(GL_LINE_LOOP, 0, 4); - - glBindVertexArray(0); - glUseProgram(0); - } + canvas_rect_draw(&self->canvas, shaderLine, mvp, EDITOR_BORDER_COLOR); Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, self->reference); - - // Drawing the frame's crop and pivot + if (frame) { - // Crop - glm::mat4 rectTransform = editorTransform; - - glm::vec2 rectNDCPos = frame->crop / (EDITOR_SIZE / 2.0f); - glm::vec2 rectNDCScale = frame->size / (EDITOR_SIZE / 2.0f); - - rectTransform = glm::translate(rectTransform, glm::vec3(rectNDCPos, 0.0f)); - rectTransform = glm::scale(rectTransform, glm::vec3(rectNDCScale, 1.0f)); - - glUseProgram(shaderLineDotted); - - glBindVertexArray(self->borderVAO); - - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, glm::value_ptr(rectTransform)); - glUniform4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), 1, glm::value_ptr(EDITOR_FRAME_TINT)); - - glDrawArrays(GL_LINE_LOOP, 0, 4); - - glBindVertexArray(0); - glUseProgram(0); - - // Pivot - glm::mat4 pivotTransform = editorTransform; - glm::vec2 pivotNDCPos = ((frame->crop + frame->pivot) - (EDITOR_PIVOT_SIZE / 2.0f)) / (EDITOR_SIZE / 2.0f); - glm::vec2 pivotNDCScale = EDITOR_PIVOT_SIZE / (EDITOR_SIZE / 2.0f); - - pivotTransform = glm::translate(pivotTransform, glm::vec3(pivotNDCPos, 0.0f)); - pivotTransform = glm::scale(pivotTransform, glm::vec3(pivotNDCScale, 1.0f)); - - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); + mvp = canvas_mvp_get(transform, frame->size, frame->crop); + canvas_rect_draw(&self->canvas, shaderLine, mvp, EDITOR_FRAME_COLOR); + mvp = canvas_mvp_get(transform, CANVAS_PIVOT_SIZE, frame->crop + frame->pivot, CANVAS_PIVOT_SIZE * 0.5f); f32 vertices[] = ATLAS_UV_VERTICES(TEXTURE_PIVOT); - - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, self->resources->atlas.id); - - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(EDITOR_FRAME_TINT)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(COLOR_OFFSET_NONE)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(pivotTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, mvp, vertices, EDITOR_PIVOT_COLOR); } } - // Grid if (self->settings->editorIsGrid) - { - static ivec2 previousGridSize = {-1, -1}; - static ivec2 previousGridOffset = {-1, -1}; - static s32 gridVertexCount = -1; + canvas_grid_draw(&self->canvas, shaderLine, transform, self->settings->editorZoom, gridSize, gridOffset, gridColor); - glm::mat4 gridTransform = editorTransform; - glm::vec2 gridNDCPos = (EDITOR_SIZE / 2.0f) / (EDITOR_SIZE / 2.0f); - - gridTransform = glm::translate(gridTransform, glm::vec3(gridNDCPos, 0.0f)); - - ivec2 gridSize = ivec2(self->settings->editorGridSizeX, self->settings->editorGridSizeY); - ivec2 gridOffset = ivec2(self->settings->editorGridOffsetX, self->settings->editorGridOffsetY); - - if (previousGridSize != gridSize || previousGridOffset != gridOffset) - { - gridVertexCount = _editor_grid_set(self); - previousGridSize = gridSize; - previousGridOffset = gridOffset; - } - - glUseProgram(shaderLine); - glBindVertexArray(self->gridVAO); - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(gridTransform)); - - glUniform4f - ( - glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), - self->settings->editorGridColorR, self->settings->editorGridColorG, self->settings->editorGridColorB, self->settings->editorGridColorA - ); - - glDrawArrays(GL_LINES, 0, gridVertexCount); - - glBindVertexArray(0); - glUseProgram(0); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void editor_tick(Editor* self) -{ - self->settings->editorZoom = CLAMP(self->settings->editorZoom, EDITOR_ZOOM_MIN, EDITOR_ZOOM_MAX); + canvas_unbind(); } void editor_free(Editor* self) { - glDeleteTextures(1, &self->texture); - glDeleteFramebuffers(1, &self->fbo); - glDeleteRenderbuffers(1, &self->rbo); + canvas_free(&self->canvas); } \ No newline at end of file diff --git a/src/editor.h b/src/editor.h index 3c662e0..2d9c485 100644 --- a/src/editor.h +++ b/src/editor.h @@ -1,23 +1,21 @@ #pragma once #include "anm2.h" +#include "canvas.h" #include "resources.h" #include "settings.h" -#include "canvas.h" -#define EDITOR_ZOOM_MIN 1 -#define EDITOR_ZOOM_MAX 1000 -#define EDITOR_ZOOM_STEP 25 +#define EDITOR_ZOOM_MIN 1.0f +#define EDITOR_ZOOM_MAX 1000.0f +#define EDITOR_ZOOM_STEP 25.0 #define EDITOR_GRID_MIN 1 #define EDITOR_GRID_MAX 1000 #define EDITOR_GRID_OFFSET_MIN 0 #define EDITOR_GRID_OFFSET_MAX 100 -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; -const vec4 EDITOR_FRAME_TINT = COLOR_RED; +static const vec4 EDITOR_BORDER_COLOR = COLOR_OPAQUE; +static const vec4 EDITOR_FRAME_COLOR = COLOR_RED; +static const vec4 EDITOR_PIVOT_COLOR = COLOR_PINK; struct Editor { @@ -25,6 +23,7 @@ struct Editor Anm2Reference* reference = nullptr; Resources* resources = nullptr; Settings* settings = nullptr; + Canvas canvas; GLuint fbo; GLuint rbo; GLuint gridVAO; @@ -39,7 +38,5 @@ struct Editor }; void editor_init(Editor* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); - void editor_draw(Editor* self); -void editor_tick(Editor* self); void editor_free(Editor* self); \ No newline at end of file diff --git a/src/imgui.cpp b/src/imgui.cpp index cee1e67..e1bbf3b 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -2,15 +2,42 @@ #include "imgui.h" +static bool _imgui_window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color) +{ + if (!self || !color) return false; + + ImGuiIO& io = ImGui::GetIO(); + ivec2 framebufferPosition = {(s32)(position.x * io.DisplayFramebufferScale.x), (s32)(position.y * io.DisplayFramebufferScale.y)}; + ivec2 framebufferSize{}; + SDL_GetWindowSizeInPixels(self, &framebufferSize.x, &framebufferSize.y); + + if + ( + framebufferPosition.x < 0 || framebufferPosition.y < 0 || + framebufferPosition.x >= framebufferSize.x || + framebufferPosition.y >= framebufferSize.y + ) + return false; + + uint8_t rgba[4]; + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(framebufferPosition.x, framebufferSize.y - 1 - framebufferPosition.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + + *color = vec4(U8_TO_FLOAT(rgba[0]), U8_TO_FLOAT(rgba[1]), U8_TO_FLOAT(rgba[2]), U8_TO_FLOAT(rgba[3])); + + return true; +} + template static void _imgui_clipboard_hovered_item_set(Imgui* self, const T& data) { self->clipboard->hoveredItem = ClipboardItem(data); } -static void _imgui_atlas_image(Imgui* self, TextureType texture) +static void _imgui_atlas_image(Imgui* self, TextureType type) { - ImGui::Image(self->resources->atlas.id, VEC2_TO_IMVEC2(ATLAS_SIZES[texture]), IMVEC2_ATLAS_UV_GET(texture)); + ImGui::Image(self->resources->atlas.id, ATLAS_SIZES[type], ATLAS_UV_ARGS(type)); } static void _imgui_item_text(const ImguiItem& item) @@ -18,11 +45,6 @@ static void _imgui_item_text(const ImguiItem& item) ImGui::Text(item.label.c_str()); } -static void _imgui_text_string(const std::string& string) -{ - ImGui::Text(string.c_str()); -} - static bool _imgui_is_window_hovered(void) { return ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); @@ -62,31 +84,27 @@ static void _imgui_item(Imgui* self, const ImguiItem& item, bool* isActivated) } if (isActivated && self->isHotkeysEnabled && (item.is_chord() && ImGui::IsKeyChordPressed(item.chord))) - { - bool isFocus = !item.is_focus_window() || (imgui_nav_window_root_get() == item.focusWindow); - - if (isFocus) + if (item.is_focus_window() && (imgui_nav_window_root_get() == item.focusWindow)) *isActivated = true; - } if (item.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) ImGui::SetTooltip(item.tooltip.c_str()); if (isActivated && *isActivated) { - if (item.isUndoable) imgui_undo_stack_push(self); + if (item.isUndoable) imgui_undo_stack_push(self, item.action); if (item.function) item.function(self); - + if (item.is_popup()) { ImGui::OpenPopup(item.popup.c_str()); switch (item.popupType) { - case POPUP_CENTER_SCREEN: + case IMGUI_POPUP_CENTER_SCREEN: ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); break; - case POPUP_BY_ITEM: + case IMGUI_POPUP_BY_ITEM: default: ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x, ImGui::GetItemRectMin().y + ImGui::GetItemRectSize().y)); break; @@ -109,7 +127,7 @@ static bool _imgui_item_selectable(Imgui* self, const ImguiItem& item) if (item.isInactive || item.color.is_normal()) { - ImVec4 color = item.isInactive ? IMGUI_INACTIVE_COLOR : item.color.normal; + vec4 color = item.isInactive ? IMGUI_INACTIVE_COLOR : item.color.normal; ImGui::PushStyleColor(ImGuiCol_Text, color); flags |= ImGuiSelectableFlags_Disabled; } @@ -123,110 +141,130 @@ static bool _imgui_item_selectable(Imgui* self, const ImguiItem& item) return isActivated; } -static bool _imgui_item_inputint(Imgui* self, const ImguiItem& item, s32* value) +static bool _imgui_item_inputint(Imgui* self, const ImguiItem& item, s32& value) { if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - 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); + bool isActivated = ImGui::InputInt(item.label.c_str(), &value, item.step, item.stepFast, item.flags); + + if (item.min != 0 || item.max != 0) + value = std::clamp(value, (s32)item.min, (s32)item.max); - _imgui_item(self, item, nullptr); + _imgui_item(self, item, &isActivated); return isActivated; } -static void _imgui_item_inputint2(Imgui* self, const ImguiItem& item, s32 value[2]) +static bool _imgui_item_inputint2(Imgui* self, const ImguiItem& item, ivec2& value) { if (item.is_size()) ImGui::SetNextItemWidth(item.size.x); - 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); - } + ImGui::InputInt2(item.label.c_str(), value_ptr(value)); + bool isActivated = ImGui::IsItemActivated(); - if (item.max > 0) - { - value[0] = MAX(value[0], (s32)item.max); - value[1] = MAX(value[1], (s32)item.max); - } + if (item.min > 0 || item.max > 0) + for (s32 i = 0; i < 2; i++) + value[i] = std::clamp(value[i], (s32)item.min, (s32)item.max); - _imgui_item(self, item, nullptr); + _imgui_item(self, item, &isActivated); + + return isActivated; } -static bool _imgui_item_inputtext(Imgui* self, const ImguiItem& item, std::string* buffer) +static bool _imgui_item_inputtext(Imgui* self, const ImguiItem& item, std::string& buffer) { - if ((s32)buffer->size() < item.max) buffer->resize(item.max); + if ((s32)buffer.size() < item.max) buffer.resize(item.max); - if (item.is_size()) - ImGui::SetNextItemWidth(item.size.x); - else - ImGui::SetNextItemWidth(ImGui::CalcTextSize(buffer->c_str()).x); - - bool isActivated = ImGui::InputText(item.label.c_str(), &(*buffer)[0], item.max, item.flags); - _imgui_item(self, item, nullptr); + ImVec2 size = item.is_size() ? item.size : ImVec2(-FLT_MIN, 0); + + ImGui::SetNextItemWidth(size.x); + + ImGui::InputText(item.label.c_str(), &buffer[0], item.max, item.flags); + bool isActivated = ImGui::IsItemActivated(); + _imgui_item(self, item, &isActivated); return isActivated; } -static void _imgui_item_checkbox(Imgui* self, const ImguiItem& item, bool* value) +static bool _imgui_item_checkbox(Imgui* self, const ImguiItem& item, bool& value) { - ImGui::Checkbox(item.label.c_str(), value); + ImGui::Checkbox(item.label.c_str(), &value); + bool isActivated = ImGui::IsItemActivated(); if (item.is_mnemonic() && ImGui::IsKeyChordPressed(ImGuiMod_Alt | item.mnemonicKey)) { - *value = !*value; + value = !value; ImGui::CloseCurrentPopup(); } - _imgui_item(self, item, nullptr); + _imgui_item(self, item, &isActivated); + + return isActivated; } -static bool _imgui_item_radio_button(Imgui* self, const ImguiItem& item, s32* value) +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); - bool isActivated = ImGui::RadioButton(item.label.c_str(), value, item.value); + return isActivated; +} + +static bool _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()); + bool isActivated = ImGui::IsItemActivated(); + _imgui_item(self, item, &isActivated); + return isActivated; +} + +static bool _imgui_item_colorbutton(Imgui* self, const ImguiItem& item, vec4& color) +{ + ImGui::ColorButton(item.label.c_str(), color, item.flags); + bool isActivated = ImGui::IsItemActivated(); _imgui_item(self, item, &isActivated); return isActivated; } -static void _imgui_item_dragfloat(Imgui* self, const ImguiItem& item, f32* value) +static bool _imgui_item_coloredit3(Imgui* self, const ImguiItem& item, vec3& value) { - ImGui::DragFloat(item.label.c_str(), value, item.speed, item.min, item.max, item.format.c_str()); - _imgui_item(self, item, nullptr); + ImGui::ColorEdit3(item.label.c_str(), value_ptr(value), item.flags); + bool isActivated = ImGui::IsItemActivated(); + _imgui_item(self, item, &isActivated); + return isActivated; } -static void _imgui_item_coloredit3(Imgui* self, const ImguiItem& item, f32 value[3]) +static bool _imgui_item_coloredit4(Imgui* self, const ImguiItem& item, vec4& value) { - ImGui::ColorEdit3(item.label.c_str(), value, item.flags); - _imgui_item(self, item, nullptr); + ImGui::ColorEdit4(item.label.c_str(), value_ptr(value), item.flags); + bool isActivated = ImGui::IsItemActivated(); + _imgui_item(self, item, &isActivated); + return isActivated; } -static void _imgui_item_coloredit4(Imgui* self, const ImguiItem& item, f32 value[4]) +static bool _imgui_item_dragfloat2(Imgui* self, const ImguiItem& item, vec2& value) { - 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); + ImGui::DragFloat2(item.label.c_str(), value_ptr(value), item.speed, item.min, item.max, item.format.c_str()); + bool isActivated = ImGui::IsItemActivated(); + _imgui_item(self, item, &isActivated); + return isActivated; } static bool _imgui_item_button(Imgui* self, const ImguiItem& item) { - bool isActivated = ImGui::Button(item.label.c_str(), item.size); + ImVec2 size = item.is_size() ? item.size : ImVec2(0, 0); + + if (item.isSizeToChild) + size.x = (ImGui::GetWindowSize().x - ImGui::GetStyle().ItemSpacing.x * (item.childRowItemCount + 1)) / item.childRowItemCount; + + bool isActivated = ImGui::Button(item.label.c_str(), 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 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; @@ -244,14 +282,17 @@ static bool _imgui_item_selectable_inputtext(Imgui* self, const ImguiItem& item, itemRenamable.size = size; self->isRename = true; - isActivated = _imgui_item_inputtext(self, itemRenamable, &buffer); + isActivated = _imgui_item_inputtext(self, itemRenamable, buffer); if (isActivated || _imgui_is_no_click_on_item()) { - *string = buffer; + if (itemRenamable.isUndoable) imgui_undo_stack_push(self, itemRenamable.action); + + string = buffer; renameID = ID_NONE; itemID = ID_NONE; self->isRename = false; + } ImGui::PopID(); @@ -262,7 +303,7 @@ static bool _imgui_item_selectable_inputtext(Imgui* self, const ImguiItem& item, if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { - buffer = *string; + buffer = string; renameID = id; itemID = item.id; ImGui::SetKeyboardFocusHere(-1); @@ -274,7 +315,7 @@ static bool _imgui_item_selectable_inputtext(Imgui* self, const ImguiItem& item, return isActivated; } -static bool _imgui_item_selectable_inputint(Imgui* self, const ImguiItem& item, s32* value, s32 id) +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; @@ -290,15 +331,12 @@ static bool _imgui_item_selectable_inputint(Imgui* self, const ImguiItem& item, 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()) + isActivated = _imgui_item_inputint(self, itemChangeable, value); + + if (isActivated || _imgui_is_no_click_on_item()) { + if (itemChangeable.isUndoable) imgui_undo_stack_push(self, itemChangeable.action); + itemID = ID_NONE; changeID = ID_NONE; self->isChangeValue = false; @@ -337,25 +375,47 @@ static bool _imgui_item_atlas_image_selectable(Imgui* self, const ImguiItem& ite return _imgui_item_selectable(self, item); } +static bool _imgui_item_text_inputtext(Imgui* self, const ImguiItem& item, std::string& string) +{ + ImguiItem copyItem = item; + copyItem.label = string; + bool isActivated = _imgui_item_selectable_inputtext(self, copyItem, string, item.id); + ImGui::SameLine(); + ImGui::Text(item.label.c_str()); -static bool _imgui_item_atlas_image_selectable_inputtext(Imgui* self, ImguiItem& item, std::string* string, s32 id) + return isActivated; +} + +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) +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_checkbox_selectable(Imgui* self, const ImguiItem& item, bool& value) +{ + ImguiItem checkboxItem = item; + checkboxItem.label = IMGUI_INVISIBLE_LABEL_MARKER + item.label; + + _imgui_item_checkbox(self, checkboxItem, value); + ImGui::SameLine(); + _imgui_atlas_image(self, item.texture); + ImGui::SameLine(); + return _imgui_item_selectable(self, item); +} + 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 imageSize = (ATLAS_SIZES[item.texture]); ImVec2 buttonSize = item.is_size() ? item.size : imageSize; if (item.color.is_normal()) ImGui::PushStyleColor(ImGuiCol_Button, item.color.normal); @@ -372,10 +432,10 @@ static bool _imgui_item_atlas_image_button(Imgui* self, const ImguiItem& item) 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); + ImGui::GetWindowDrawList()->AddImage(self->resources->atlas.id, imageMin, imageMax, ATLAS_UV_ARGS(item.texture)); } else - isActivated = ImGui::ImageButton(item.label.c_str(), self->resources->atlas.id, buttonSize, IMVEC2_ATLAS_UV_GET(item.texture)); + isActivated = ImGui::ImageButton(item.label.c_str(), self->resources->atlas.id, buttonSize, ATLAS_UV_ARGS(item.texture)); _imgui_item(self, item, &isActivated); if (item.color.is_normal()) ImGui::PopStyleColor(); @@ -459,6 +519,8 @@ static void _imgui_timeline(Imgui* self) static const ImU32 frameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_FRAME_MULTIPLE_COLOR); static const ImU32 headerFrameColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_COLOR); static const ImU32 headerFrameMultipleColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_COLOR); + static const ImU32 headerFrameInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_INACTIVE_COLOR); + static const ImU32 headerFrameMultipleInactiveColor = ImGui::GetColorU32(IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR); static const ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); static ImVec2 scroll{}; static ImVec2 pickerPos{}; @@ -467,11 +529,11 @@ static void _imgui_timeline(Imgui* self) static ImVec2 itemMin{}; static ImVec2 mousePos{}; static ImVec2 localMousePos{}; - static s32 frameIndex = INDEX_NONE; + static s32 frameTime = 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); @@ -483,6 +545,10 @@ static void _imgui_timeline(Imgui* self) return; } + s32 actualLength = anm2_animation_length_get(animation); + ImVec2 actualFramesSize = {actualLength * frameSize.x, frameSize.y}; + ImVec2 framesSize = {animation->frameNum * frameSize.x, frameSize.y}; + ImVec2 defaultItemSpacing = ImGui::GetStyle().ItemSpacing; ImVec2 defaultWindowPadding = ImGui::GetStyle().WindowPadding; ImVec2 defaultFramePadding = ImGui::GetStyle().FramePadding; @@ -497,7 +563,7 @@ static void _imgui_timeline(Imgui* self) ImVec2 clipRectMin = ImGui::GetWindowDrawList()->GetClipRectMin(); ImVec2 clipRectMax = ImGui::GetWindowDrawList()->GetClipRectMax(); clipRectMin.x += IMGUI_TIMELINE_ITEM_SIZE.x; - + ImVec2 scrollDelta = {0, 0}; if (_imgui_is_window_hovered()) @@ -519,14 +585,14 @@ static void _imgui_timeline(Imgui* self) 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)); + frameTime = std::clamp((s32)(localMousePos.x / frameSize.x), 0, animation->frameNum - 1); if (ImGui::IsMouseDown(ImGuiMouseButton_Left) && _imgui_is_window_hovered()) isHeaderClicked = true; if (isHeaderClicked) { - self->preview->time = frameIndex; + self->preview->time = frameTime; if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) isHeaderClicked = false; @@ -534,58 +600,76 @@ static void _imgui_timeline(Imgui* self) 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++) - { - 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; - drawList->AddRectFilled(bgMin, bgMax, bgColor); - - if (i % IMGUI_TIMELINE_FRAME_MULTIPLE == 0) + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + ImVec2 dummySize = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; + ImGui::Dummy(dummySize); + + f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; + s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; + start = (start < 0) ? 0 : start; + + s32 end = (s32)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; + end = (end > ANM2_FRAME_NUM_MAX) ? ANM2_FRAME_NUM_MAX : end; + + pickerPos = ImVec2(cursorPos.x + self->preview->time * frameSize.x, cursorPos.y); + + for (s32 i = start; i < end; i++) + { + bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; + bool isInactive = i >= animation->frameNum; + + f32 startX = cursorPos.x + i * frameSize.x; + f32 endX = startX + frameSize.x; + ImVec2 positionStart(startX, cursorPos.y); + ImVec2 positionEnd(endX, cursorPos.y + frameSize.y); + + ImU32 bgColor = isInactive + ? (isMultiple ? headerFrameMultipleInactiveColor : headerFrameInactiveColor) + : (isMultiple ? headerFrameMultipleColor : headerFrameColor); + + drawList->AddRectFilled(positionStart, positionEnd, bgColor); + + if (isMultiple) { - std::string 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()); + std::string buffer = std::to_string(i); + ImVec2 textSize = ImGui::CalcTextSize(buffer.c_str()); + ImVec2 textPosition(startX + (frameSize.x - textSize.x) * 0.5f, cursorPos.y + (frameSize.y - textSize.y) * 0.5f); + drawList->AddText(textPosition, textColor, buffer.c_str()); } - - _imgui_atlas_image(self, TEXTURE_FRAME); - - if (i < animation->frameNum - 1) - ImGui::SameLine(); + + drawList->AddImage(self->resources->atlas.id, positionStart, positionEnd, ATLAS_UV_ARGS(TEXTURE_FRAME)); } + _imgui_item_end_child(); // IMGUI_TIMELINE_HEADER + + ImGui::SetNextWindowPos(ImGui::GetWindowPos()); + ImGui::SetNextWindowSize(ImGui::GetWindowSize()); + _imgui_item_begin(IMGUI_TIMELINE_PICKER); + 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); + drawList = ImGui::GetWindowDrawList(); - 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); + drawList->PushClipRect(clipRectMin, clipRectMax, true); + drawList->AddImage(self->resources->atlas.id, pos, ImVec2(pos.x + frameSize.x, pos.y + frameSize.y), ATLAS_UV_ARGS(TEXTURE_PICKER)); + drawList->AddRectFilled(lineStart, lineEnd, IMGUI_PICKER_LINE_COLOR); + drawList->PopClipRect(); - foregroundDrawList->PopClipRect(); - - _imgui_item_end_child(); // IMGUI_TIMELINE_HEADER + _imgui_item_end(); }; - std::function timeline_item_child = [&](Anm2Reference reference, s32* index) + 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)); + ImVec2 buttonSize = ImVec2(TEXTURE_SIZE) + (defaultFramePadding * ImVec2(2, 2)); Anm2Type& type = reference.itemType; Anm2Layer* layer = nullptr; @@ -616,13 +700,13 @@ static void _imgui_timeline(Imgui* self) 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)) + if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, layer->name, index)) *self->reference = reference; break; 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)) + if (_imgui_item_atlas_image_selectable_inputtext(self, imguiItemSelectable, null->name, index)) *self->reference = reference; break; default: @@ -658,7 +742,7 @@ static void _imgui_timeline(Imgui* self) 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_item_atlas_image_selectable_inputint(self, spritesheetIDItem, layer->spritesheetID, index); } ImGui::SetCursorScreenPos({childPos.x + childSize.x - buttonAreaWidth, childPos.y + defaultWindowPadding.y}); @@ -682,7 +766,7 @@ static void _imgui_timeline(Imgui* self) ImGui::PopID(); - (*index)++; + index++; }; std::function timeline_items_child = [&]() @@ -694,58 +778,104 @@ static void _imgui_timeline(Imgui* self) _imgui_item_begin_child(IMGUI_TIMELINE_ITEMS_CHILD); ImGui::SetScrollY(scroll.y); - timeline_item_child({animationID, ANM2_ROOT, ID_NONE, INDEX_NONE}, &index); + timeline_item_child({animationID, ANM2_ROOT, ID_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& [i, id] : std::ranges::reverse_view(self->anm2->layerMap)) + timeline_item_child({animationID, ANM2_LAYER, id}, index); for (auto & [id, null] : animation->nullAnimations) - timeline_item_child({animationID, ANM2_NULL, id, INDEX_NONE}, &index); + timeline_item_child({animationID, ANM2_NULL, id}, index); - timeline_item_child({animationID, ANM2_TRIGGERS, ID_NONE, INDEX_NONE}, &index); + timeline_item_child({animationID, ANM2_TRIGGERS, ID_NONE}, index); _imgui_item_end_child(); // IMGUI_TIMELINE_ITEMS_CHILD + + if (isItemSwap) + { + Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); + + switch (swapItemReference.itemType) + { + case ANM2_LAYER: + { + s32 indexA = INDEX_NONE; + s32 indexB = INDEX_NONE; + + for (const auto& [index, id] : self->anm2->layerMap) + { + if (id == self->reference->itemID) + indexA = index; + else if (id == swapItemReference.itemID) + indexB = index; + } + + if ((indexA != INDEX_NONE) && (indexB != INDEX_NONE)) + std::swap(self->anm2->layerMap[indexA], self->anm2->layerMap[indexB]); + 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; + } }; - std::function timeline_item_frames = [&](Anm2Reference reference, s32* index) + 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::PushID(index); - _imgui_item_begin_child(itemFramesChild); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + + f32 viewWidth = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; + + ImguiItem imguiItemFrames = IMGUI_TIMELINE_ITEM_FRAMES_CHILD; + imguiItemFrames.size = actualFramesSize.x > framesSize.x ? actualFramesSize : framesSize; + + _imgui_item_begin_child(imguiItemFrames); + + ImVec2 startPos = ImGui::GetCursorPos(); + ImVec2 cursorPos = ImGui::GetCursorScreenPos(); if (_imgui_is_window_hovered()) { hoverReference = reference; - hoverReference.frameIndex = frameIndex; - self->clipboard->location = hoverReference; + hoverReference.frameIndex = anm2_frame_index_from_time(self->anm2, reference, frameTime); + + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) + { + *self->reference = reference; + self->clipboard->location = hoverReference; + } } - ImVec2 startPos = ImGui::GetCursorPos(); + s32 start = (s32)std::floor(scroll.x / frameSize.x) - 1; + if (start < 0) start = 0; - for (s32 i = 0; i < animation->frameNum; i++) + s32 end = (s32)std::ceil((scroll.x + viewWidth) / frameSize.x) + 1; + if (end > ANM2_FRAME_NUM_MAX) end = ANM2_FRAME_NUM_MAX; + + for (s32 i = start; i < end; i++) { - bool isMultiple = i % IMGUI_TIMELINE_FRAME_MULTIPLE == 0; - - ImVec2 framePos = ImGui::GetCursorScreenPos(); - ImVec2 bgMin = framePos; - ImVec2 bgMax = {framePos.x + frameSize.x, framePos.y + frameSize.y}; - + bool isMultiple = (i % IMGUI_TIMELINE_FRAME_MULTIPLE) == 0; ImU32 bgColor = isMultiple ? frameMultipleColor : frameColor; - drawList->AddRectFilled(bgMin, bgMax, bgColor); - - _imgui_atlas_image(self, TEXTURE_FRAME_ALT); + f32 startX = cursorPos.x + i * frameSize.x; + f32 endX = startX + frameSize.x; + ImVec2 startPosition(startX, cursorPos.y); + ImVec2 endPosition(endX, cursorPos.y + frameSize.y); - if (i < animation->frameNum - 1) - ImGui::SameLine(); + drawList->AddRectFilled(startPosition, endPosition, bgColor); + drawList->AddImage(self->resources->atlas.id, startPosition, endPosition, ATLAS_UV_ARGS(TEXTURE_FRAME_ALT)); } ImGui::SetCursorPos(startPos); @@ -756,7 +886,7 @@ static void _imgui_timeline(Imgui* self) reference.frameIndex = i; ImguiItem frameButton = *IMGUI_TIMELINE_FRAMES[type]; ImVec2 framePos = ImGui::GetCursorPos(); - frameButton.texture = frame.isInterpolated ? TEXTURE_INTERPOLATED : TEXTURE_UNINTERPOLATED; + frameButton.texture = frame.isInterpolated ? TEXTURE_CIRCLE : TEXTURE_SQUARE; frameButton.size = {frameSize.x * frame.delay, frameSize.y}; frameButton.isSelected = reference == *self->reference; @@ -771,8 +901,7 @@ static void _imgui_timeline(Imgui* self) if (_imgui_item_atlas_image_button(self, frameButton)) { *self->reference = reference; - - _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); + _imgui_spritesheet_editor_set(self, self->anm2->layers[self->reference->itemID].spritesheetID); } if (ImGui::IsItemHovered()) @@ -784,11 +913,11 @@ static void _imgui_timeline(Imgui* self) if (type == ANM2_TRIGGERS) { if (ImGui::IsItemActivated()) - imgui_undo_stack_push(self); + imgui_undo_stack_push(self, IMGUI_ACTION_TRIGGER_MOVE); if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) { - frame.atFrame = frameIndex; + frame.atFrame = frameTime; ImGui::EndDragDropSource(); } } @@ -809,7 +938,7 @@ static void _imgui_timeline(Imgui* self) Anm2Reference swapReference = *(Anm2Reference*)payload->Data; if (swapReference != reference) { - imgui_undo_stack_push(self); + imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_SWAP); Anm2Frame* swapFrame = anm2_frame_from_reference(self->anm2, &reference); Anm2Frame* dragFrame = anm2_frame_from_reference(self->anm2, &swapReference); @@ -831,9 +960,6 @@ static void _imgui_timeline(Imgui* self) if (i < (s32)item->frames.size() - 1) ImGui::SameLine(); - if (_imgui_is_no_click_on_item()) - anm2_reference_frame_clear(self->reference); - ImGui::PopID(); }; @@ -841,10 +967,10 @@ static void _imgui_timeline(Imgui* self) timeline_item_frame(i, frame); _imgui_item_end_child(); // itemFramesChild - + ImGui::PopID(); - (*index)++; + index++; }; std::function timeline_frames_child = [&]() @@ -858,15 +984,15 @@ static void _imgui_timeline(Imgui* self) ImGui::SetScrollX(scroll.x); ImGui::SetScrollY(scroll.y); - timeline_item_frames({animationID, ANM2_ROOT, ID_NONE, INDEX_NONE}, &index); + timeline_item_frames(Anm2Reference(animationID, ANM2_ROOT), 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& [i, id] : std::ranges::reverse_view(self->anm2->layerMap)) + timeline_item_frames(Anm2Reference(animationID, ANM2_LAYER, id), index); for (auto & [id, null] : animation->nullAnimations) - timeline_item_frames({animationID, ANM2_NULL, id, INDEX_NONE}, &index); + timeline_item_frames(Anm2Reference(animationID, ANM2_NULL, id), index); - timeline_item_frames({animationID, ANM2_TRIGGERS, ID_NONE, INDEX_NONE}, &index); + timeline_item_frames(Anm2Reference(animationID, ANM2_TRIGGERS), index); _imgui_item_end_child(); // IMGUI_TIMELINE_FRAMES_CHILD }; @@ -884,41 +1010,23 @@ static void _imgui_timeline(Imgui* self) 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); + Anm2Frame* frame = anm2_frame_from_reference(self->anm2, self->reference); - if (ImGui::BeginPopup(IMGUI_TIMELINE_ADD_ELEMENT_POPUP)) + _imgui_item_begin_child(IMGUI_TIMELINE_FOOTER_ITEM_CHILD); + + if(_imgui_item_button(self, IMGUI_TIMELINE_ADD_ITEM)) + ImGui::OpenPopup(IMGUI_TIMELINE_ADD_ITEM.popup.c_str()); + + if (ImGui::BeginPopup(IMGUI_TIMELINE_ADD_ITEM.popup.c_str())) { - if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ELEMENT_LAYER)) + if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ITEM_LAYER)) anm2_layer_add(self->anm2); - if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ELEMENT_NULL)) + if (_imgui_item_selectable(self, IMGUI_TIMELINE_ADD_ITEM_NULL)) anm2_null_add(self->anm2); ImGui::EndPopup(); @@ -926,7 +1034,7 @@ static void _imgui_timeline(Imgui* self) ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_ELEMENT)) + if (_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_ITEM)) { switch (self->reference->itemType) { @@ -943,8 +1051,14 @@ static void _imgui_timeline(Imgui* self) anm2_reference_item_clear(self->reference); } + _imgui_item_end_child(); //IMGUI_TIMELINE_FOOTER_ITEM_CHILD + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::SameLine(); - + ImGui::PopStyleVar(); + + _imgui_item_begin_child(IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD); + ImguiItem playPauseItem = self->preview->isPlaying ? IMGUI_TIMELINE_PAUSE : IMGUI_TIMELINE_PLAY; if (_imgui_item_button(self, playPauseItem)) self->preview->isPlaying = !self->preview->isPlaying; @@ -958,7 +1072,6 @@ static void _imgui_timeline(Imgui* self) if(_imgui_item_button(self, IMGUI_TIMELINE_REMOVE_FRAME)) { - imgui_undo_stack_push(self); if (anm2_frame_from_reference(self->anm2, self->reference)) { Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); @@ -969,28 +1082,73 @@ static void _imgui_timeline(Imgui* self) ImGui::SameLine(); + if (_imgui_item_button(self, IMGUI_TIMELINE_BAKE)) + { + if (frame) + ImGui::OpenPopup(IMGUI_TIMELINE_BAKE.popup.c_str()); + else + ImGui::CloseCurrentPopup(); + } + + if (ImGui::BeginPopupModal(IMGUI_TIMELINE_BAKE.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + static s32 interval = 1; + static bool isRoundScale = true; + static bool isRoundRotation = true; + + if (frame) + { + _imgui_item_begin_child(IMGUI_TIMELINE_BAKE_CHILD); + + ImguiItem bakeIntervalItem = IMGUI_TIMELINE_BAKE_INTERVAL; + bakeIntervalItem.max = frame->delay; + + _imgui_item_inputint(self, bakeIntervalItem, interval); + + _imgui_item_checkbox(self, IMGUI_TIMELINE_BAKE_ROUND_SCALE, isRoundScale); + _imgui_item_checkbox(self, IMGUI_TIMELINE_BAKE_ROUND_ROTATION, isRoundRotation); + + if (_imgui_item_button(self, IMGUI_TIMELINE_BAKE_CONFIRM)) + { + anm2_frame_bake(self->anm2, self->reference, interval, isRoundScale, isRoundRotation); + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_TIMELINE_BAKE_CANCEL)) + ImGui::CloseCurrentPopup(); + + _imgui_item_end_child(); //IMGUI_TIMELINE_BAKE_CHILD) + + ImGui::EndPopup(); + } + else + { + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + } + + 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_item_inputint(self, IMGUI_TIMELINE_ANIMATION_LENGTH, animation->frameNum); ImGui::SameLine(); - _imgui_item_inputint(self, IMGUI_TIMELINE_FPS, &self->anm2->fps); + _imgui_item_inputint(self, IMGUI_TIMELINE_FPS, self->anm2->fps); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_TIMELINE_LOOP, &animation->isLoop); + _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)); + _imgui_item_text_inputtext(self, IMGUI_TIMELINE_CREATED_BY, self->anm2->createdBy); - if (_imgui_is_no_click_on_item()) - anm2_reference_item_clear(self->reference); + _imgui_item_end_child(); //IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD _imgui_item_end(); // IMGUI_TIMELINE - self->preview->time = CLAMP(self->preview->time, 0.0f, animation->frameNum - 1); + self->preview->time = std::clamp(self->preview->time, 0.0f, (f32)(animation->frameNum - 1)); } static void _imgui_taskbar(Imgui* self) @@ -1003,7 +1161,7 @@ static void _imgui_taskbar(Imgui* self) _imgui_item_selectable(self, IMGUI_TASKBAR_FILE); - if (ImGui::BeginPopup(IMGUI_FILE_POPUP)) + if (ImGui::BeginPopup(IMGUI_TASKBAR_FILE.popup.c_str())) { _imgui_item_selectable(self, IMGUI_FILE_NEW); _imgui_item_selectable(self, IMGUI_FILE_OPEN); @@ -1014,21 +1172,27 @@ static void _imgui_taskbar(Imgui* self) ImGui::SameLine(); - _imgui_item_selectable(self, IMGUI_TASKBAR_PLAYBACK); - - if (ImGui::BeginPopup(IMGUI_PLAYBACK_POPUP)) + _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD); + + if (ImGui::BeginPopup(IMGUI_TASKBAR_WIZARD.popup.c_str())) + { + _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID); + _imgui_item_selectable(self, IMGUI_TASKBAR_WIZARD_RECORD_GIF_ANIMATION); + ImGui::EndPopup(); + } + + if (ImGui::BeginPopupModal(IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - _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_TASKBAR_PLAYBACK); + + if (ImGui::BeginPopup(IMGUI_TASKBAR_PLAYBACK.popup.c_str())) { - _imgui_item_selectable(self, IMGUI_WIZARD_RECORD_GIF_ANIMATION); + _imgui_item_checkbox(self, IMGUI_PLAYBACK_ALWAYS_LOOP, self->settings->playbackIsLoop); ImGui::EndPopup(); } @@ -1053,7 +1217,7 @@ static void _imgui_tools(Imgui* self) if (i != TOOL_COLOR) { - ImVec4 buttonColor = self->tool == (ToolType)i ? ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered] : ImGui::GetStyle().Colors[ImGuiCol_Button]; + vec4 buttonColor = self->settings->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); @@ -1061,18 +1225,43 @@ static void _imgui_tools(Imgui* self) ImGui::PopStyleColor(); } else - _imgui_item_coloredit4(self, IMGUI_TOOL_COLOR, &self->settings->toolColorR); + _imgui_item_coloredit4(self, IMGUI_TOOL_COLOR, self->settings->toolColor); usedWidth += ImGui::GetItemRectSize().x + ImGui::GetStyle().ItemSpacing.x; } _imgui_item_end(); // IMGUI_TOOLS + + if (self->settings->tool == TOOL_COLOR_PICKER) + { + if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) + { + vec2 mousePos{}; + SDL_GetMouseState(&mousePos.x, &mousePos.y); + + vec4 color{}; + if (_imgui_window_color_from_position_get(self->window, mousePos, &color)) + { + self->settings->toolColor = color; + + ImGui::BeginTooltip(); + _imgui_item_colorbutton(self, IMGUI_TOOL_COLOR_PICKER_TOOLTIP_COLOR, color); + ImGui::EndTooltip(); + } + } + } + + if (TOOL_MOUSE_CURSOR_IS_CONSTANT[self->settings->tool]) + SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[self->settings->tool])); } static void _imgui_animations(Imgui* self) { _imgui_item_begin(IMGUI_ANIMATIONS); - ImVec2 windowSize = ImGui::GetContentRegionAvail(); + + ImguiItem animationsChild = IMGUI_ANIMATIONS_CHILD; + animationsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_ANIMATIONS_FOOTER_HEIGHT; + _imgui_item_begin_child(animationsChild); std::function animation_item = [&](s32 id, Anm2Animation& animation) { @@ -1080,14 +1269,13 @@ static void _imgui_animations(Imgui* self) ImguiItem animationItem = IMGUI_ANIMATION; animationItem.isSelected = self->reference->animationID == id; - animationItem.size.x = windowSize.x; - if (animation.name == self->anm2->defaultAnimation) + if (id == self->anm2->defaultAnimationID) 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)) + if (_imgui_item_atlas_image_selectable_inputtext(self, animationItem, animation.name, id)) { self->reference->animationID = id; anm2_reference_item_clear(self->reference); @@ -1116,7 +1304,7 @@ static void _imgui_animations(Imgui* self) s32 sourceID = *(s32*)payload->Data; if (sourceID != id) { - imgui_undo_stack_push(self); + imgui_undo_stack_push(self, IMGUI_ACTION_ANIMATION_SWAP); map_swap(self->anm2->animations, sourceID, id); } } @@ -1129,9 +1317,13 @@ static void _imgui_animations(Imgui* self) for (auto & [id, animation] : self->anm2->animations) animation_item(id, animation); + + _imgui_item_end_child(); // animationsChild Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - + + _imgui_item_begin_child(IMGUI_ANIMATIONS_OPTIONS_CHILD); + if (_imgui_item_button(self, IMGUI_ANIMATION_ADD)) { bool isDefault = (s32)self->anm2->animations.size() == 0; @@ -1140,7 +1332,7 @@ static void _imgui_animations(Imgui* self) self->reference->animationID = id; if (isDefault) - self->anm2->defaultAnimation = self->anm2->animations[id].name; + self->anm2->defaultAnimationID = id; } ImGui::SameLine(); @@ -1154,46 +1346,118 @@ static void _imgui_animations(Imgui* self) ImGui::SameLine(); - _imgui_item_button(self, IMGUI_ANIMATION_MERGE); + _imgui_item_button(self, IMGUI_ANIMATIONS_MERGE); - if (ImGui::BeginPopupModal(IMGUI_MERGE_POPUP, nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + if (ImGui::BeginPopupModal(IMGUI_ANIMATIONS_MERGE.popup.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { - static s32 selectedRadioButton = ID_NONE; + static s32 mergeType = (s32)ANM2_MERGE_APPEND_FRAMES; static bool isDeleteAnimationsAfter = false; + static std::vector animationIDs; + static s32 lastClickedID = ID_NONE; - _imgui_item_begin_child(IMGUI_MERGE_ANIMATIONS_CHILD); + const bool isModCtrl = ImGui::IsKeyDown(IMGUI_INPUT_CTRL); + const bool isModShift = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - for (auto& [id, animation] : self->anm2->animations) - animation_item(id, animation); + static std::vector sortedIDs; + static size_t lastAnimationCount = 0; + if (self->anm2->animations.size() != lastAnimationCount) + { + sortedIDs.clear(); + for (const auto& [id, _] : self->anm2->animations) + sortedIDs.push_back(id); + std::sort(sortedIDs.begin(), sortedIDs.end()); + lastAnimationCount = self->anm2->animations.size(); + } - _imgui_item_end_child(); //IMGUI_MERGE_ANIMATIONS_CHILD + _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_CHILD); + + for (const auto& [id, animation] : self->anm2->animations) + { + ImGui::PushID(id); + + ImguiItem animationItem = IMGUI_ANIMATION; + animationItem.label = animation.name; + animationItem.isSelected = std::find(animationIDs.begin(), animationIDs.end(), id) != animationIDs.end(); + + if (_imgui_item_atlas_image_selectable(self, animationItem)) + { + if (isModCtrl) + { + auto it = std::find(animationIDs.begin(), animationIDs.end(), id); + if (it != animationIDs.end()) + animationIDs.erase(it); + else + animationIDs.push_back(id); + + lastClickedID = id; + } + else if (isModShift) + { + auto it1 = std::find(sortedIDs.begin(), sortedIDs.end(), lastClickedID); + auto it2 = std::find(sortedIDs.begin(), sortedIDs.end(), id); + if (it1 != sortedIDs.end() && it2 != sortedIDs.end()) + { + auto begin = std::min(it1, it2); + auto end = std::max(it1, it2); + for (auto it = begin; it <= end; ++it) + { + if (std::find(animationIDs.begin(), animationIDs.end(), *it) == animationIDs.end()) + animationIDs.push_back(*it); + } + } + } + else + { + animationIDs.clear(); + animationIDs.push_back(id); + lastClickedID = id; + } + } + + ImGui::PopID(); + } + + _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_CHILD - _imgui_item_begin_child(IMGUI_MERGE_ON_CONFLICT_CHILD); + _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD); - _imgui_item_text(IMGUI_MERGE_ON_CONFLICT); + _imgui_item_text(IMGUI_ANIMATIONS_MERGE_ON_CONFLICT); - _imgui_item_radio_button(self, IMGUI_MERGE_APPEND_FRAMES, &selectedRadioButton); + _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_APPEND_FRAMES, mergeType); ImGui::SameLine(); - _imgui_item_radio_button(self, IMGUI_MERGE_REPLACE_FRAMES, &selectedRadioButton); - _imgui_item_radio_button(self, IMGUI_MERGE_PREPEND_FRAMES, &selectedRadioButton); + _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_REPLACE_FRAMES, mergeType); + _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_PREPEND_FRAMES, mergeType); ImGui::SameLine(); - _imgui_item_radio_button(self, IMGUI_MERGE_IGNORE, &selectedRadioButton); + _imgui_item_radio_button(self, IMGUI_ANIMATIONS_MERGE_IGNORE, mergeType); - _imgui_item_end_child(); //IMGUI_MERGE_ON_CONFLICT_CHILD + _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD - _imgui_item_begin_child(IMGUI_MERGE_OPTIONS_CHILD); + _imgui_item_begin_child(IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD); - _imgui_item_checkbox(self, IMGUI_MERGE_DELETE_ANIMATIONS_AFTER, &isDeleteAnimationsAfter); + _imgui_item_checkbox(self, IMGUI_ANIMATIONS_MERGE_DELETE_ANIMATIONS_AFTER, isDeleteAnimationsAfter); - _imgui_item_end_child(); //IMGUI_MERGE_OPTIONS_CHILD + _imgui_item_end_child(); //IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD - if (_imgui_item_button(self, IMGUI_MERGE_CONFIRM)) + if (_imgui_item_button(self, IMGUI_ANIMATIONS_MERGE_CONFIRM)) + { + anm2_animation_merge(self->anm2, self->reference->animationID, animationIDs, (Anm2MergeType)mergeType); + + if (isDeleteAnimationsAfter) + for (s32 id : animationIDs) + if (id != self->reference->animationID) + self->anm2->animations.erase(id); + + animationIDs.clear(); ImGui::CloseCurrentPopup(); - + } + ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_MERGE_CANCEL)) + if (_imgui_item_button(self, IMGUI_ANIMATIONS_MERGE_CANCEL)) + { + animationIDs.clear(); ImGui::CloseCurrentPopup(); + } ImGui::EndPopup(); } @@ -1209,117 +1473,100 @@ static void _imgui_animations(Imgui* self) 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); + self->anm2->defaultAnimationID = self->reference->animationID; + _imgui_item_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) _imgui_item_end(); } static void _imgui_events(Imgui* self) { - static s32 selectedEventID = ID_NONE; + static s32 selectedID = ID_NONE; _imgui_item_begin(IMGUI_EVENTS); - ImVec2 windowSize = ImGui::GetContentRegionAvail(); + ImguiItem eventsChild = IMGUI_EVENTS_CHILD; + eventsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_EVENTS_FOOTER_HEIGHT; + _imgui_item_begin_child(eventsChild); + 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; + eventItem.label = event.name; + eventItem.isSelected = id == selectedID; - 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(); - } + if (_imgui_item_atlas_image_selectable_inputtext(self, eventItem, event.name, id)) + selectedID = id; ImGui::PopID(); }; for (auto& [id, event] : self->anm2->events) event_item(id, event); + + _imgui_item_end_child(); // eventsChild + + _imgui_item_begin_child(IMGUI_EVENTS_OPTIONS_CHILD); if (_imgui_item_button(self, IMGUI_EVENT_ADD)) { s32 id = map_next_id_get(self->anm2->events); self->anm2->events[id] = Anm2Event{}; - selectedEventID = id; + selectedID = id; } ImGui::SameLine(); - - if (selectedEventID == ID_NONE) + + if (_imgui_item_button(self, IMGUI_EVENT_REMOVE_UNUSED)) { - _imgui_item_end(); - return; + std::unordered_set usedEventIDs; + + for (auto& [id, animation] : self->anm2->animations) + for (auto& trigger : animation.triggers.frames) + if (trigger.eventID != ID_NONE) + usedEventIDs.insert(trigger.eventID); + + for (auto it = self->anm2->events.begin(); it != self->anm2->events.end(); ) + { + if (!usedEventIDs.count(it->first)) + it = self->anm2->events.erase(it); + else + it++; + } } - if (_imgui_item_button(self, IMGUI_EVENT_REMOVE)) - { - self->anm2->events.erase(selectedEventID); - selectedEventID = ID_NONE; - } - - if (_imgui_is_no_click_on_item()) - selectedEventID = ID_NONE; - + _imgui_item_end_child(); // IMGUI_ANIMATIONS_OPTIONS_CHILD) _imgui_item_end(); } static void _imgui_spritesheets(Imgui* self) { - static s32 selectedSpritesheetID = ID_NONE; + static std::unordered_map isSelectedIDs; + static s32 highlightedID = ID_NONE; _imgui_item_begin(IMGUI_SPRITESHEETS); + + ImguiItem spritesheetsChild = IMGUI_SPRITESHEETS_CHILD; + spritesheetsChild.size.y = ImGui::GetContentRegionAvail().y - IMGUI_SPRITESHEETS_FOOTER_HEIGHT; + + _imgui_item_begin_child(spritesheetsChild); std::function spritesheet_item = [&](s32 id, Anm2Spritesheet& spritesheet) { ImGui::PushID(id); + ImguiItem spritesheetItem = IMGUI_SPRITESHEET; Texture* texture = &self->resources->textures[id]; - ImguiItem spritesheetItem = IMGUI_SPRITESHEET_CHILD; - 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); - } + spritesheetItem.label = std::format(IMGUI_SPRITESHEET_FORMAT, id, spritesheet.path); + spritesheetItem.isSelected = id == highlightedID; + + _imgui_item_begin_child(IMGUI_SPRITESHEET_CHILD); + + if (_imgui_item_atlas_image_checkbox_selectable(self, spritesheetItem, isSelectedIDs[id])) + highlightedID = id; if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) { @@ -1355,7 +1602,7 @@ static void _imgui_spritesheets(Imgui* self) else ImGui::Image(texture->id, spritesheetPreviewSize); - _imgui_item_end_child(); // spritesheetItem + _imgui_item_end_child(); // IMGUI_SPRITESHEET_CHILD ImGui::PopID(); }; @@ -1363,48 +1610,94 @@ static void _imgui_spritesheets(Imgui* self) for (auto [id, spritesheet] : self->anm2->spritesheets) spritesheet_item(id, spritesheet); - // TODO: match Nicalis - - _imgui_item_button(self, IMGUI_SPRITESHEET_ADD); + _imgui_item_end_child(); // spritesheetsChild + + _imgui_item_begin_child(IMGUI_SPRITESHEETS_OPTIONS_CHILD); + + _imgui_item_button(self, IMGUI_SPRITESHEETS_ADD); ImGui::SameLine(); - if (selectedSpritesheetID == ID_NONE) + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_RELOAD)) { - _imgui_item_end(); - return; - } - - 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; + for (auto& [id, isSelected] : isSelectedIDs) + { + if (isSelected) + { + std::filesystem::path workingPath = std::filesystem::current_path(); + working_directory_from_file_set(self->anm2->path); + resources_texture_init(self->resources, self->anm2->spritesheets[id].path, id); + std::filesystem::current_path(workingPath); + } + } } ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_SPRITESHEET_RELOAD)) + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_REPLACE)) { - 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); + if (highlightedID != ID_NONE) + { + self->dialog->replaceID = highlightedID; + dialog_png_replace(self->dialog); + } } + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_REMOVE_UNUSED)) + { + std::unordered_set usedSpritesheetIDs; + + for (auto& [layerID, layer] : self->anm2->layers) + if (layer.spritesheetID != ID_NONE) + usedSpritesheetIDs.insert(layer.spritesheetID); + + for (auto it = self->anm2->spritesheets.begin(); it != self->anm2->spritesheets.end(); ) + { + if (!usedSpritesheetIDs.count(it->first)) + { + it = self->anm2->spritesheets.erase(it); + texture_free(&self->resources->textures[it->first]); + } + else + it++; + } + } + + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SELECT_ALL)) + for (auto& [id, _] : self->anm2->spritesheets) + isSelectedIDs[id] = true; + + ImGui::SameLine(); + + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SELECT_NONE)) + for (auto& [id, _] : self->anm2->spritesheets) + isSelectedIDs[id] = false; + ImGui::SameLine(); - if (_imgui_item_button(self, IMGUI_SPRITESHEET_REPLACE)) + if (_imgui_item_button(self, IMGUI_SPRITESHEETS_SAVE)) { - self->dialog->replaceID = selectedSpritesheetID; - dialog_png_replace(self->dialog); + for (auto& [id, isSelected] : isSelectedIDs) + { + if (isSelected) + { + std::filesystem::path workingPath = std::filesystem::current_path(); + working_directory_from_file_set(self->anm2->path); + Anm2Spritesheet* spritesheet = &self->anm2->spritesheets[id]; + Texture* texture = &self->resources->textures[id]; + texture_from_gl_write(texture, spritesheet->path); + imgui_message_queue_push(self, std::format(IMGUI_ACTION_SPRITESHEET_SAVE_FORMAT, id, spritesheet->path)); + std::filesystem::current_path(workingPath); + } + } } if (_imgui_is_no_click_on_item()) - selectedSpritesheetID = ID_NONE; - + highlightedID = ID_NONE; + + _imgui_item_end_child(); //IMGUI_SPRITESHEETS_OPTIONS_CHILD _imgui_item_end(); } @@ -1414,126 +1707,132 @@ static void _imgui_animation_preview(Imgui* self) static vec2 mousePos{}; static vec2 previewPos{}; static ImVec2 previewScreenPos{}; + static vec2& pan = self->settings->previewPan; + static f32& zoom = self->settings->previewZoom; + static vec2& size = self->preview->canvas.size; + + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); _imgui_item_begin(IMGUI_ANIMATION_PREVIEW); - ImVec2 windowSize = ImGui::GetWindowSize(); - 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_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_GRID, self->settings->previewIsGrid); ImGui::SameLine(); - _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_coloredit4(self, IMGUI_ANIMATION_PREVIEW_GRID_COLOR, self->settings->previewGridColor); + _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_SIZE, self->settings->previewGridSize); + _imgui_item_inputint2(self, IMGUI_ANIMATION_PREVIEW_GRID_OFFSET, self->settings->previewGridOffset); _imgui_item_end_child(); ImGui::SameLine(); _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_item_dragfloat(self, IMGUI_ANIMATION_PREVIEW_ZOOM, zoom); + if (_imgui_item_button(self, IMGUI_ANIMATION_PREVIEW_CENTER_VIEW)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); _imgui_item_end_child(); ImGui::SameLine(); _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_BACKGROUND_SETTINGS); - _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, (f32*)&self->settings->previewBackgroundColorR); + _imgui_item_coloredit4(self, IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR, self->settings->previewBackgroundColor); + + std::vector animationStrings; + std::vector animationLabels; + std::vector animationIDs; + + animationStrings.reserve(self->anm2->animations.size() + 1); + animationIDs.reserve(self->anm2->animations.size() + 1); + animationLabels.reserve(self->anm2->animations.size() + 1); + + animationIDs.push_back(ID_NONE); + animationStrings.push_back(IMGUI_EVENT_NONE); + animationLabels.push_back(animationStrings.back().c_str()); + + for (auto & [id, animation] : self->anm2->animations) + { + animationIDs.push_back(id); + animationStrings.push_back(animation.name); + animationLabels.push_back(animationStrings.back().c_str()); + } + + s32 selectedAnimationID = std::find(animationIDs.begin(), animationIDs.end(), self->preview->animationOverlayID) - animationIDs.begin(); + + if (_imgui_item_combo(self, IMGUI_ANIMATION_PREVIEW_OVERLAY, &selectedAnimationID, animationLabels.data(), (s32)animationLabels.size())) + self->preview->animationOverlayID = animationIDs[selectedAnimationID]; + + _imgui_item_dragfloat(self, IMGUI_ANIMATION_PREVIEW_OVERLAY_TRANSPARENCY, self->settings->previewOverlayTransparency); _imgui_item_end_child(); ImGui::SameLine(); _imgui_item_begin_child(IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS); - _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_AXIS, &self->settings->previewIsAxis); + _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_coloredit4(self, IMGUI_ANIMATION_PREVIEW_AXIS_COLOR, self->settings->previewAxisColor); + _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::SameLine(); + _imgui_item_checkbox(self, IMGUI_ANIMATION_PREVIEW_BORDER, self->settings->previewIsBorder); _imgui_item_end_child(); - 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_CANVAS_CHILD_SIZE.y); + previewPos = vec2(ImGui::GetCursorPos()); - if (ImGui::IsItemHovered()) + size = ImGui::GetContentRegionAvail(); + ImGui::Image(self->preview->canvas.texture, size); + + if (!ImGui::IsItemHovered()) { - if (!isPreviewHover) - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)); - - isPreviewHover = true; - } - else - isPreviewHover = false; - - Anm2Frame trigger{}; - anm2_frame_from_time(self->anm2, &trigger, Anm2Reference{self->reference->animationID, ANM2_TRIGGERS}, self->preview->time); - - 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 (!isPreviewHover) - { - _imgui_keyboard_navigation_set(true); + if (isPreviewHover) + { + SDL_SetCursor(SDL_CreateSystemCursor(MOUSE_CURSOR_DEFAULT)); + _imgui_keyboard_navigation_set(true); + isPreviewHover = false; + } _imgui_item_end(); return; } + isPreviewHover = true; + _imgui_keyboard_navigation_set(false); - vec2 windowPos = IMVEC2_TO_VEC2(ImGui::GetWindowPos()); - mousePos = IMVEC2_TO_VEC2(ImGui::GetMousePos()); + mousePos = (vec2((ImGui::GetMousePos()) - (ImGui::GetWindowPos() + previewPos)) - (size * 0.5f) - pan) / PERCENT_TO_UNIT(zoom); - 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); + ToolType tool = self->settings->tool; + bool isLeft = ImGui::IsKeyPressed(IMGUI_INPUT_LEFT); + bool isRight = ImGui::IsKeyPressed(IMGUI_INPUT_RIGHT); + bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); + bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); + bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); 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])); + SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[tool])); - if (self->tool == TOOL_MOVE || self->tool == TOOL_SCALE || self->tool == TOOL_ROTATE) + if (self->settings->tool == TOOL_MOVE || self->settings->tool == TOOL_SCALE || self->settings->tool == TOOL_ROTATE) if (isMouseClick || isLeft || isRight || isUp || isDown) - imgui_undo_stack_push(self); + imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_TRANSFORM); - if ((self->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) - { - self->settings->previewPanX -= mouseDelta.x; - self->settings->previewPanY -= mouseDelta.y; - } + if ((self->settings->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + pan += vec2(mouseDelta.x, 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; + f32 step = isMod ? TOOL_STEP_MOD : TOOL_STEP; switch (tool) { case TOOL_MOVE: if (isMouseDown) - frame->position = IMVEC2_TO_VEC2(mousePos); + frame->position = vec2(mousePos); else { if (isLeft) frame->position.x -= step; @@ -1553,7 +1852,7 @@ static void _imgui_animation_preview(Imgui* self) break; case TOOL_SCALE: if (isMouseDown) - frame->scale += IMVEC2_TO_VEC2(ImGui::GetIO().MouseDelta); + frame->scale += vec2(mouseDelta.x, mouseDelta.y); else { if (isLeft) frame->scale.x -= step; @@ -1569,9 +1868,10 @@ static void _imgui_animation_preview(Imgui* self) 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->previewZoom = ROUND_NEAREST_FLOAT(self->settings->previewZoom + delta, CANVAS_ZOOM_STEP); - self->settings->previewZoom = CLAMP(self->settings->previewZoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); + f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + delta = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT) ? delta * CANVAS_ZOOM_MOD : delta; + zoom = ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP); + zoom = std::clamp(zoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } _imgui_item_end(); @@ -1581,64 +1881,61 @@ static void _imgui_spritesheet_editor(Imgui* self) { static bool isEditorHover = false; static vec2 mousePos = {0, 0}; + vec2& pan = self->settings->editorPan; + f32& zoom = self->settings->editorZoom; + vec2& size = self->editor->canvas.size; + ivec2& gridSize = self->settings->editorGridSize; + std::string mousePositionString = std::format(IMGUI_POSITION_FORMAT, (s32)mousePos.x, (s32)mousePos.y); _imgui_item_begin(IMGUI_SPRITESHEET_EDITOR); _imgui_item_begin_child(IMGUI_SPRITESHEET_EDITOR_GRID_SETTINGS); - _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID, &self->settings->editorIsGrid); + _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID, self->settings->editorIsGrid); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, &self->settings->editorIsGridSnap); + _imgui_item_checkbox(self, IMGUI_SPRITESHEET_EDITOR_GRID_SNAP, self->settings->editorIsGridSnap); ImGui::SameLine(); - _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_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_GRID_COLOR, self->settings->editorGridColor); + _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_SIZE, self->settings->editorGridSize); + _imgui_item_inputint2(self, IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET, self->settings->editorGridOffset); _imgui_item_end_child(); ImGui::SameLine(); _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_item_dragfloat(self, IMGUI_SPRITESHEET_EDITOR_ZOOM, self->settings->editorZoom); + if (_imgui_item_button(self, IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); _imgui_item_end_child(); ImGui::SameLine(); _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_coloredit4(self, IMGUI_SPRITESHEET_EDITOR_BACKGROUND_COLOR, self->settings->editorBackgroundColor); + _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)); + ImVec2 editorPos = ImGui::GetCursorPos(); + size = ImGui::GetContentRegionAvail(); + ImGui::Image(self->editor->canvas.texture, size); if (!ImGui::IsItemHovered()) { if (isEditorHover) { - SDL_SetCursor(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT)); + SDL_SetCursor(SDL_CreateSystemCursor(MOUSE_CURSOR_DEFAULT)); + _imgui_keyboard_navigation_set(true); isEditorHover = false; } - _imgui_keyboard_navigation_set(true); _imgui_item_end(); return; } isEditorHover = true; + _imgui_keyboard_navigation_set(false); - 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); + mousePos = (vec2((ImGui::GetMousePos()) - (ImGui::GetWindowPos() + editorPos)) - pan) / PERCENT_TO_UNIT(zoom); bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); @@ -1646,54 +1943,55 @@ static void _imgui_spritesheet_editor(Imgui* self) f32 mouseWheel = ImGui::GetIO().MouseWheel; ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; - SDL_SetCursor(SDL_CreateSystemCursor(IMGUI_TOOL_MOUSE_CURSORS[self->tool])); + SDL_SetCursor(SDL_CreateSystemCursor(TOOL_MOUSE_CURSORS[self->settings->tool])); - if ((self->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) - { - self->settings->editorPanX -= mouseDelta.x; - self->settings->editorPanY -= mouseDelta.y; - } + if ((self->settings->tool == TOOL_PAN && isMouseDown) || isMouseMiddleDown) + pan += vec2(mouseDelta.x, 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) + Texture* texture = map_find(self->resources->textures, self->editor->spritesheetID); + + if (texture) { - if (isMouseClick) + vec2 position = mousePos; + vec4 color = self->settings->tool == TOOL_ERASE ? COLOR_TRANSPARENT : self->settings->toolColor; + + switch (self->settings->tool) { - imgui_undo_stack_push(self); - - vec2 cropPosition = mousePos + IMGUI_SPRITESHEET_EDITOR_CROP_FORGIVENESS; + case TOOL_CROP: + if (!frame) break; - 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; - } + if (self->settings->editorIsGridSnap) + position = {(s32)(position.x / gridSize.x) * gridSize.x, (s32)(position.y / gridSize.y) * gridSize.y}; - 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 (isMouseClick) + { + imgui_undo_stack_push(self, IMGUI_ACTION_FRAME_CROP); + frame->crop = position; + frame->size = ivec2(0,0); + } + else if (isMouseDown) + frame->size = position - frame->crop; + break; + case TOOL_DRAW: + case TOOL_ERASE: + if (isMouseDown) + texture_pixel_set(texture, mousePos, color); + break; + default: + break; } } 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); + f32 delta = (mouseWheel > 0 || ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN)) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; + delta = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT) ? delta * CANVAS_ZOOM_MOD : delta; + self->settings->editorZoom = ROUND_NEAREST_MULTIPLE(self->settings->editorZoom + delta, CANVAS_ZOOM_STEP); + self->settings->editorZoom = std::clamp(self->settings->editorZoom, CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } _imgui_item_end(); @@ -1719,20 +2017,20 @@ static void _imgui_frame_properties(Imgui* self) if (type == ANM2_ROOT || type == ANM2_NULL || type == ANM2_LAYER) { - _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_POSITION, value_ptr(frame->position)); + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_POSITION, 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_CROP, frame->crop); + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SIZE, frame->size); + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_PIVOT, 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)); + + _imgui_item_dragfloat2(self, IMGUI_FRAME_PROPERTIES_SCALE, 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, frame->tintRGBA); + _imgui_item_coloredit3(self, IMGUI_FRAME_PROPERTIES_COLOR_OFFSET, frame->offsetRGB); if (_imgui_item_button(self, IMGUI_FRAME_PROPERTIES_FLIP_X)) frame->scale.x = -frame->scale.x; @@ -1740,9 +2038,9 @@ static void _imgui_frame_properties(Imgui* self) 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_item_checkbox(self, IMGUI_FRAME_PROPERTIES_VISIBLE, frame->isVisible); ImGui::SameLine(); - _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_INTERPOLATED, &frame->isInterpolated); + _imgui_item_checkbox(self, IMGUI_FRAME_PROPERTIES_INTERPOLATED, frame->isInterpolated); } else if (type == ANM2_TRIGGERS) @@ -1762,7 +2060,7 @@ static void _imgui_frame_properties(Imgui* self) for (auto & [id, event] : self->anm2->events) { eventIDs.push_back(id); - eventStrings.push_back(std::format(IMGUI_EVENT_FORMAT, id, event.name)); + eventStrings.push_back(event.name); eventLabels.push_back(eventStrings.back().c_str()); } @@ -1771,13 +2069,80 @@ static void _imgui_frame_properties(Imgui* self) if (_imgui_item_combo(self, IMGUI_FRAME_PROPERTIES_EVENT, &selectedEventIndex, eventLabels.data(), (s32)eventLabels.size())) frame->eventID = eventIDs[selectedEventIndex]; - _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_AT_FRAME, &frame->atFrame); - frame->atFrame = CLAMP(frame->atFrame, 0, animation->frameNum - 1); + _imgui_item_inputint(self, IMGUI_FRAME_PROPERTIES_AT_FRAME, frame->atFrame); + frame->atFrame = std::clamp(frame->atFrame, 0, animation->frameNum - 1); } _imgui_item_end(); } +static void _imgui_messages(Imgui* self) +{ + ImGuiIO& io = ImGui::GetIO(); + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4 borderColor = style.Colors[ImGuiCol_Border]; + ImVec4 textColor = style.Colors[ImGuiCol_Text]; + + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | + ImGuiWindowFlags_NoInputs; + + ImVec2 position = {io.DisplaySize.x - IMGUI_MESSAGE_PADDING, io.DisplaySize.y - IMGUI_MESSAGE_PADDING}; + + for (s32 i = (s32)self->messageQueue.size() - 1; i >= 0; --i) + { + ImguiMessage& message = self->messageQueue[i]; + f32 lifetime = message.timeRemaining / IMGUI_MESSAGE_DURATION; + borderColor.w = lifetime; + textColor.w = lifetime; + + message.timeRemaining -= io.DeltaTime; + + if (message.timeRemaining <= 0.0f) + { + self->messageQueue.erase(self->messageQueue.begin() + i); + continue; + } + + ImGui::SetNextWindowPos(position, ImGuiCond_Always, {1.0f, 1.0f}); + ImGui::PushStyleColor(ImGuiCol_Border, borderColor); + ImGui::PushStyleColor(ImGuiCol_Text, textColor); + ImGui::SetNextWindowBgAlpha(lifetime); + + ImGui::Begin(std::format(IMGUI_MESSAGE_FORMAT, i).c_str(), nullptr, flags); + ImGui::TextUnformatted(message.text.c_str()); + ImVec2 windowSize = ImGui::GetWindowSize(); + ImGui::End(); + + ImGui::PopStyleColor(2); + + position.y -= windowSize.y + IMGUI_MESSAGE_PADDING; + } + + if (self->dialog->isJustSelected) + { + switch (self->dialog->lastType) + { + case DIALOG_ANM2_OPEN: + imgui_message_queue_push(self, std::format(IMGUI_ACTION_FILE_OPEN_FORMAT, self->dialog->lastPath)); + break; + case DIALOG_ANM2_SAVE: + imgui_message_queue_push(self, std::format(IMGUI_ACTION_FILE_SAVE_FORMAT, self->dialog->lastPath)); + break; + default: + break; + } + + dialog_reset(self->dialog); + } +} + static void _imgui_persistent(Imgui* self) { if (self->preview->isRecording) @@ -1791,9 +2156,9 @@ static void _imgui_persistent(Imgui* self) } if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) - ImGui::OpenPopup(IMGUI_CONTEXT_MENU); + ImGui::OpenPopup(IMGUI_CONTEXT_MENU.popup.c_str()); - if (ImGui::BeginPopup(IMGUI_CONTEXT_MENU)) + if (ImGui::BeginPopup(IMGUI_CONTEXT_MENU.popup.c_str())) { ImguiItem pasteItem = IMGUI_PASTE; pasteItem.isInactive = self->clipboard->item.type == CLIPBOARD_NONE; @@ -1875,7 +2240,7 @@ void imgui_init ImGui::LoadIniSettingsFromDisk(SETTINGS_PATH); } -void imgui_tick(Imgui* self) +void imgui_update(Imgui* self) { ImGui_ImplSDL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame(); @@ -1883,6 +2248,7 @@ void imgui_tick(Imgui* self) _imgui_taskbar(self); _imgui_dock(self); + _imgui_messages(self); _imgui_persistent(self); self->isHotkeysEnabled = !self->isRename && !self->isChangeValue; @@ -1893,9 +2259,7 @@ void imgui_tick(Imgui* self) { if (ImGui::IsKeyChordPressed(hotkey.chord)) { - if (hotkey.is_focus_window() && (imgui_nav_window_root_get() != hotkey.focusWindow)) - continue; - + if (hotkey.is_focus_window()) continue; hotkey.function(self); } } @@ -1910,12 +2274,12 @@ void imgui_tick(Imgui* self) switch (event.type) { case SDL_EVENT_QUIT: - if (ImGui::IsPopupOpen(IMGUI_EXIT_CONFIRMATION_POPUP)) + if (ImGui::IsPopupOpen(IMGUI_EXIT_CONFIRMATION.popup.c_str())) { self->isQuit = true; break; } - ImGui::OpenPopup(IMGUI_EXIT_CONFIRMATION_POPUP); + ImGui::OpenPopup(IMGUI_EXIT_CONFIRMATION.popup.c_str()); break; default: break; diff --git a/src/imgui.h b/src/imgui.h index 06d36ca..b5d200d 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -1,5 +1,15 @@ #pragma once +#include "clipboard.h" +#include "dialog.h" +#include "editor.h" +#include "preview.h" +#include "resources.h" +#include "settings.h" +#include "snapshots.h" +#include "tool.h" +#include "window.h" + #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #define IMGUI_ENABLE_DOCKING #define IM_VEC2_CLASS_EXTRA \ @@ -8,70 +18,70 @@ 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); } - + inline ImVec2(const vec2& v) : x(v.x), y(v.y) {} \ + inline operator vec2() const { return vec2(x, y); } + #define IM_VEC4_CLASS_EXTRA \ inline bool operator==(const ImVec4& rhs) const { return x == rhs.x && y == rhs.y && z == rhs.z && w == rhs.w; } \ inline bool operator!=(const ImVec4& rhs) const { return !(*this == rhs); } \ inline ImVec4 operator+(const ImVec4& rhs) const { return ImVec4(x + rhs.x, y + rhs.y, z + rhs.z, w + rhs.w); } \ inline ImVec4 operator-(const ImVec4& rhs) const { return ImVec4(x - rhs.x, y - rhs.y, z - rhs.z, w - rhs.w); } \ - inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } - + inline ImVec4 operator*(const ImVec4& rhs) const { return ImVec4(x * rhs.x, y * rhs.y, z * rhs.z, w * rhs.w); } \ + inline ImVec4(const vec4& v) : x(v.x), y(v.y), z(v.z), w(v.w) {} \ + inline operator vec4() const { return vec4(x, y, z, w); } + #include #include #include #include -#include "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_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_PLAYBACK_POPUP "## Playback Popup" -#define IMGUI_EXIT_CONFIRMATION_POPUP "Exit Confirmation" -#define IMGUI_POSITION_FORMAT "Position: {{{:5}, {:5}}}" +#define IMGUI_POSITION_FORMAT "Position: {{{:6}, {:6}}}" #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_FOOTER_HEIGHT 40 +#define IMGUI_ANIMATIONS_FOOTER_HEIGHT 40 +#define IMGUI_EVENTS_FOOTER_HEIGHT 40 +#define IMGUI_SPRITESHEETS_FOOTER_HEIGHT 65 #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_MERGE #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]) +#define IMGUI_INVISIBLE_LABEL_MARKER "##" +#define IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT 5 +#define IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT 2 +#define IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT 4 +#define IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT 3 +#define IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT 2 +#define IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT 2 +#define IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT 2 +#define IMGUI_MESSAGE_DURATION 3.0f +#define IMGUI_MESSAGE_PADDING 10.0f +#define IMGUI_MESSAGE_FORMAT "## Message {}" +#define IMGUI_MESSAGE_UNDO_FORMAT "Undo: {}" +#define IMGUI_MESSAGE_REDO_FORMAT "Redo: {}" + +#define IMGUI_ACTION_FILE_SAVE_FORMAT "Saved anm2 to: {}" +#define IMGUI_ACTION_FILE_OPEN_FORMAT "Opened anm2: {}" +#define IMGUI_ACTION_SPRITESHEET_SAVE_FORMAT "Saved spritesheet #{} to: {}" +#define IMGUI_ACTION_TRIGGER_MOVE "Trigger AtFrame" +#define IMGUI_ACTION_FRAME_SWAP "Frame Swap" +#define IMGUI_ACTION_ANIMATION_SWAP "Animation Swap" +#define IMGUI_ACTION_FRAME_TRANSFORM "Frame Transform" +#define IMGUI_ACTION_FRAME_CROP "Frame Crop" + +#define IMGUI_SPACING 4 const ImVec2 IMGUI_TIMELINE_FRAME_SIZE = {16, 40}; 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)}; @@ -82,9 +92,13 @@ 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_TIMELINE_HEADER_FRAME_INACTIVE_COLOR = {0.113, 0.184, 0.286, 0.125}; +const ImVec4 IMGUI_TIMELINE_HEADER_FRAME_MULTIPLE_INACTIVE_COLOR = {0.113, 0.184, 0.286, 0.25}; 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 ImVec2 IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE = {100, 0}; + 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}; @@ -95,10 +109,10 @@ 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_SHIFT = ImGuiMod_Shift; +const ImGuiKey IMGUI_INPUT_CTRL = ImGuiMod_Ctrl; const ImGuiKey IMGUI_INPUT_ZOOM_IN = ImGuiKey_1; const ImGuiKey IMGUI_INPUT_ZOOM_OUT = ImGuiKey_2; -const ImGuiKey IMGUI_INPUT_PLAY = ImGuiKey_Space; inline const std::string IMGUI_FRAME_PROPERTIES_TITLE[ANM2_COUNT] = { @@ -115,35 +129,11 @@ static inline ImGuiKey imgui_key_get(char c) 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, -}; -#define SDL_MOUSE_CURSOR_NULL (-1) -const SDL_SystemCursor IMGUI_TOOL_MOUSE_CURSORS[TOOL_COUNT] = +struct ImguiMessage { - 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 + std::string text; + f32 timeRemaining; }; struct Imgui @@ -159,7 +149,7 @@ struct Imgui Clipboard* clipboard = nullptr; SDL_Window* window = nullptr; SDL_GLContext* glContext = nullptr; - ToolType tool = TOOL_PAN; + std::vector messageQueue; bool isHotkeysEnabled = true; bool isRename = false; bool isChangeValue = false; @@ -185,8 +175,8 @@ static std::vector& imgui_hotkey_registry() enum PopupType { - POPUP_BY_ITEM, - POPUP_CENTER_SCREEN + IMGUI_POPUP_BY_ITEM, + IMGUI_POPUP_CENTER_SCREEN }; struct ImguiColorSet @@ -204,19 +194,22 @@ struct ImguiItemBuilder { std::string label{}; std::string tooltip{}; + std::string action = SNAPSHOT_ACTION; std::string popup{}; std::string dragDrop{}; - std::string format{}; + std::string format = "%.1f"; std::string focusWindow{}; ImguiFunction function = nullptr; ImGuiKeyChord chord = IMGUI_CHORD_NONE; TextureType texture = TEXTURE_NONE; - PopupType popupType = POPUP_BY_ITEM; + PopupType popupType = IMGUI_POPUP_BY_ITEM; bool isUndoable = false; bool isSizeToText = true; + bool isSizeToChild = false; ImguiColorSet color{}; ImVec2 size{}; ImVec2 contentOffset{}; + s32 childRowItemCount{}; f64 speed{}; f64 min{}; f64 max{}; @@ -233,22 +226,25 @@ struct ImguiItem { std::string label{}; std::string tooltip{}; + std::string action = SNAPSHOT_ACTION; std::string popup{}; std::string dragDrop{}; - std::string format{}; + std::string format = "%.1f"; std::string focusWindow{}; ImguiFunction function = nullptr; ImGuiKeyChord chord = IMGUI_CHORD_NONE; TextureType texture = TEXTURE_NONE; - PopupType popupType = POPUP_BY_ITEM; + PopupType popupType = IMGUI_POPUP_BY_ITEM; bool isUndoable = false; bool isInactive = false; bool isSizeToText = true; + bool isSizeToChild = true; bool isSelected = false; f64 speed{}; f64 min{}; f64 max{}; s32 border{}; + s32 childRowItemCount = 1; s32 step = 1; s32 stepFast = 1; s32 value = ID_NONE; @@ -261,8 +257,9 @@ struct ImguiItem ImGuiKey mnemonicKey = ImGuiKey_None; s32 mnemonicIndex = -1; - bool is_popup() const { return !popup.empty(); } bool is_tooltip() const { return !tooltip.empty(); } + bool is_popup() const { return !popup.empty(); } + bool is_action() const { return !action.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; } @@ -275,6 +272,7 @@ struct ImguiItem tooltip = builder.tooltip; popup = builder.popup; + action = builder.action; dragDrop = builder.dragDrop; format = builder.format; focusWindow = builder.focusWindow; @@ -284,6 +282,7 @@ struct ImguiItem texture = builder.texture; isUndoable = builder.isUndoable; isSizeToText = builder.isSizeToText; + isSizeToChild = builder.isSizeToChild; color = builder.color; size = builder.size; contentOffset = builder.contentOffset; @@ -291,6 +290,7 @@ struct ImguiItem min = builder.min; max = builder.max; border = builder.border; + childRowItemCount = builder.childRowItemCount; step = builder.step; stepFast = builder.stepFast; value = builder.value; @@ -328,6 +328,11 @@ struct ImguiItem } }; +static void imgui_message_queue_push(Imgui* self, const std::string& text) +{ + self->messageQueue.push_back({text, IMGUI_MESSAGE_DURATION}); +} + static inline std::string_view imgui_nav_window_root_get() { ImGuiWindow* navWindow = ImGui::GetCurrentContext()->NavWindow; @@ -343,6 +348,7 @@ static inline void imgui_file_new(Imgui* self) { anm2_reference_clear(self->reference); anm2_new(self->anm2); + resources_textures_free(self->resources); } static inline void imgui_file_open(Imgui* self) @@ -355,7 +361,10 @@ static inline void imgui_file_save(Imgui* self) if (self->anm2->path.empty()) dialog_anm2_save(self->dialog); else + { anm2_serialize(self->anm2, self->anm2->path); + imgui_message_queue_push(self, std::format(IMGUI_ACTION_FILE_SAVE_FORMAT, self->anm2->path)); + } } static inline void imgui_file_save_as(Imgui* self) @@ -377,60 +386,67 @@ static inline void imgui_generate_gif_animation(Imgui* self) } } -static inline void imgui_undo_stack_push(Imgui* self) +static inline void imgui_undo_stack_push(Imgui* self, const std::string& action = SNAPSHOT_ACTION) { - Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time}; + Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, action}; snapshots_undo_stack_push(self->snapshots, &snapshot); } static inline void imgui_tool_pan_set(Imgui* self) { - self->tool = TOOL_PAN; + self->settings->tool = TOOL_PAN; } static inline void imgui_tool_move_set(Imgui* self) { - self->tool = TOOL_MOVE; + self->settings->tool = TOOL_MOVE; } static inline void imgui_tool_rotate_set(Imgui* self) { - self->tool = TOOL_ROTATE; + self->settings->tool = TOOL_ROTATE; } static inline void imgui_tool_scale_set(Imgui* self) { - self->tool = TOOL_SCALE; + self->settings->tool = TOOL_SCALE; } static inline void imgui_tool_crop_set(Imgui* self) { - self->tool = TOOL_CROP; + self->settings->tool = TOOL_CROP; } static inline void imgui_tool_draw_set(Imgui* self) { - self->tool = TOOL_DRAW; + self->settings->tool = TOOL_DRAW; } static inline void imgui_tool_erase_set(Imgui* self) { - self->tool = TOOL_ERASE; + self->settings->tool = TOOL_ERASE; } static inline void imgui_tool_color_picker_set(Imgui* self) { - self->tool = TOOL_COLOR_PICKER; + self->settings->tool = TOOL_COLOR_PICKER; } static inline void imgui_undo(Imgui* self) { + if (self->snapshots->undoStack.top == 0) return; + snapshots_undo(self->snapshots); + imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_UNDO_FORMAT, self->snapshots->action)); } static inline void imgui_redo(Imgui* self) { + if (self->snapshots->redoStack.top == 0) return; + + std::string action = self->snapshots->action; snapshots_redo(self->snapshots); + imgui_message_queue_push(self, std::format(IMGUI_MESSAGE_REDO_FORMAT, action)); } static inline void imgui_cut(Imgui* self) @@ -481,7 +497,7 @@ const inline ImguiItem IMGUI_TASKBAR_FILE = ImguiItem ({ .label = "&File", .tooltip = "Opens the file menu, for reading/writing anm2 files.", - .popup = IMGUI_FILE_POPUP, + .popup = "## File Popup", .isSizeToText = true }); @@ -517,11 +533,34 @@ const inline ImguiItem IMGUI_FILE_SAVE_AS = ImguiItem .chord = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S }); +const inline ImguiItem IMGUI_TASKBAR_WIZARD = ImguiItem +({ + .label = "&Wizard", + .tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", + .popup = "## Wizard Popup", + .isSizeToText = true +}); + +const inline ImguiItem IMGUI_TASKBAR_WIZARD_GENERATE_ANIMATION_FROM_GRID = ImguiItem +({ + .label = "&Generate Animation from Grid", + .tooltip = "Generate a new animation from grid values.", + .popup = "Generate Animation from Grid", + .popupType = IMGUI_POPUP_CENTER_SCREEN +}); + +const inline ImguiItem IMGUI_TASKBAR_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_TASKBAR_PLAYBACK = ImguiItem ({ .label = "&Playback", .tooltip = "Opens the playback menu, for configuring playback settings.", - .popup = IMGUI_PLAYBACK_POPUP, + .popup = "## Playback Popup", .isSizeToText = true }); @@ -531,207 +570,282 @@ const inline ImguiItem IMGUI_PLAYBACK_ALWAYS_LOOP = ImguiItem .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_ANIMATIONS = ImguiItem({"Animations"}); -const inline ImguiItem IMGUI_WIZARD_RECORD_GIF_ANIMATION = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_CHILD = ImguiItem ({ - .label = "G&enerate GIF Animation", - .tooltip = "Generates a GIF animation from the current animation.", - .function = imgui_generate_gif_animation + .label = "## Animations Child", + .flags = true, }); -const inline ImguiItem IMGUI_ANIMATIONS = ImguiItem({IMGUI_ANIMATIONS_LABEL}); - const inline ImguiItem IMGUI_ANIMATION = ImguiItem ({ .label = "## Animation Item", + .action = "Select Animation", .dragDrop = "## Animation Drag Drop", .texture = TEXTURE_ANIMATION, .isUndoable = true, + .isSizeToText = false +}); + +const inline ImguiItem IMGUI_ANIMATIONS_OPTIONS_CHILD = ImguiItem +({ + .label = "## Animations Options Child", + .flags = true, }); const inline ImguiItem IMGUI_ANIMATION_ADD = ImguiItem ({ .label = "Add", .tooltip = "Adds a new animation.", - .isUndoable = true + .action = "Add Animation", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT }); const inline ImguiItem IMGUI_ANIMATION_DUPLICATE = ImguiItem ({ .label = "Duplicate", .tooltip = "Duplicates the selected animation, placing it after.", - .isUndoable = true + .action = "Duplicate Animation", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_ANIMATION_MERGE = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE = ImguiItem ({ .label = "Merge", .tooltip = "Open the animation merge popup, to merge animations together.", - .popup = IMGUI_MERGE_POPUP, - .popupType = POPUP_CENTER_SCREEN, - .isUndoable = true + .popup = "Merge Animations", + .popupType = IMGUI_POPUP_CENTER_SCREEN, + .isSizeToChild = true, + .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_MERGE_ANIMATIONS_CHILD = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CHILD = ImguiItem ({ .label = "## Merge Animations", .size = {300, 250}, .flags = true }); -const inline ImguiItem IMGUI_MERGE_ON_CONFLICT_CHILD = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_ON_CONFLICT_CHILD = ImguiItem ({ - .label = "## Merge On Conflict", + .label = "## Merge On Conflict Child", .size = {300, 75}, .flags = true }); -const inline ImguiItem IMGUI_MERGE_ON_CONFLICT = ImguiItem({"On Conflict"}); +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_ON_CONFLICT = ImguiItem({"On Conflict"}); -const inline ImguiItem IMGUI_MERGE_APPEND_FRAMES = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_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 + .value = ANM2_MERGE_APPEND_FRAMES }); -const inline ImguiItem IMGUI_MERGE_REPLACE_FRAMES = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_REPLACE_FRAMES = ImguiItem ({ .label = "Replace Frames", .tooltip = "On frame conflict, the merged animation will have the latest selected animations' frames.", - .value = 1 + .value = ANM2_MERGE_REPLACE_FRAMES }); -const inline ImguiItem IMGUI_MERGE_PREPEND_FRAMES = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_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 + .value = ANM2_MERGE_PREPEND_FRAMES }); -const inline ImguiItem IMGUI_MERGE_IGNORE = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_IGNORE = ImguiItem ({ .label = "Ignore ", .tooltip = "On frame conflict, the merged animation will ignore the other selected animations' frames.", - .value = 3 + .value = ANM2_MERGE_IGNORE }); -const inline ImguiItem IMGUI_MERGE_OPTIONS_CHILD = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_OPTIONS_CHILD = ImguiItem ({ - .label = "## Merge Options", + .label = "## Merge Options Child", .size = {300, 35}, .flags = true }); -const inline ImguiItem IMGUI_MERGE_DELETE_ANIMATIONS_AFTER = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_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 +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CONFIRM = ImguiItem ({ .label = "Merge", .tooltip = "Merge the selected animations with the options set.", - .size = {150, 25}, + .action = "Merge Animations", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_MERGE_CANCEL = ImguiItem +const inline ImguiItem IMGUI_ANIMATIONS_MERGE_CANCEL = ImguiItem ({ .label = "Cancel", .tooltip = "Cancel merging.", - .size = {150, 25} + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_MERGE_OPTIONS_ROW_ITEM_COUNT }); const inline ImguiItem IMGUI_ANIMATION_DEFAULT = ImguiItem ({ .label = "Default", .tooltip = "Sets the selected animation to be the default.", - .isUndoable = true + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT }); const inline ImguiItem IMGUI_ANIMATION_REMOVE = ImguiItem ({ .label = "Remove", .tooltip = "Removes the selected animation.", - .focusWindow = IMGUI_ANIMATIONS_LABEL, + .action = "Remove Animation", + .focusWindow = IMGUI_ANIMATIONS.label, .chord = ImGuiKey_Delete, - .isUndoable = true + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_ANIMATIONS_OPTIONS_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_EVENTS = ImguiItem({IMGUI_EVENTS_LABEL}); +const inline ImguiItem IMGUI_EVENTS = ImguiItem({"Events"}); + +const inline ImguiItem IMGUI_EVENTS_CHILD = ImguiItem +({ + .label = "## Events Child", + .flags = true +}); const inline ImguiItem IMGUI_EVENT = ImguiItem ({ .label = "## Event Item", - .dragDrop = "## Event Drag Drop", .texture = TEXTURE_EVENT, - .isUndoable = true + .isUndoable = true, + .isSizeToText = false +}); + +const inline ImguiItem IMGUI_EVENTS_OPTIONS_CHILD = ImguiItem +({ + .label = "## Events Options Child", + .flags = true, }); const inline ImguiItem IMGUI_EVENT_ADD = ImguiItem ({ .label = "Add", .tooltip = "Adds a new event.", - .isUndoable = true + .action = "Add Event", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_EVENT_REMOVE = ImguiItem +const inline ImguiItem IMGUI_EVENT_REMOVE_UNUSED = ImguiItem ({ - .label = "Remove", - .tooltip = "Remove the selected event.", - .focusWindow = IMGUI_EVENTS_LABEL, - .chord = ImGuiKey_Delete, - .isUndoable = true + .label = "Remove Unused", + .tooltip = "Removes all unused events (i.e., not being used in any triggers in any animation).", + .action = "Remove Unused Events", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_EVENTS_OPTIONS_ROW_ITEM_COUNT }); const inline ImguiItem IMGUI_SPRITESHEETS = ImguiItem({"Spritesheets"}); +const inline ImguiItem IMGUI_SPRITESHEETS_CHILD = ImguiItem +({ + .label = "## Spritesheets Child", + .flags = true +}); + const inline ImguiItem IMGUI_SPRITESHEET_CHILD = ImguiItem ({ - .label = "## Spritesheet", + .label = "## Spritesheet Child", .size = {0, 175}, .flags = true }); -const inline ImguiItem IMGUI_SPRITESHEET_SELECTABLE = ImguiItem +const inline ImguiItem IMGUI_SPRITESHEET = ImguiItem ({ - .label = "## Spritesheet Selectable", + .label = "## Spritesheet", .dragDrop = "## Spritesheet Drag Drop", .texture = TEXTURE_SPRITESHEET, .flags = true }); -const inline ImguiItem IMGUI_SPRITESHEET_ADD = ImguiItem +const inline ImguiItem IMGUI_SPRITESHEETS_OPTIONS_CHILD = ImguiItem +({ + .label = "## Spritesheets Options Child", + .flags = true, +}); + +const inline ImguiItem IMGUI_SPRITESHEETS_ADD = ImguiItem ({ .label = "Add", .tooltip = "Select an image to add as a spritesheet.", - .function = imgui_png_open + .function = imgui_png_open, + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_SPRITESHEET_REMOVE = ImguiItem -({ - .label = "Remove", - .tooltip = "Remove the selected spritesheet." -}); - -const inline ImguiItem IMGUI_SPRITESHEET_RELOAD = ImguiItem +const inline ImguiItem IMGUI_SPRITESHEETS_RELOAD = ImguiItem ({ .label = "Reload", - .tooltip = "Reload the selected spritesheet." + .tooltip = "Reload the selected spritesheet.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT }); -const inline ImguiItem IMGUI_SPRITESHEET_REPLACE = ImguiItem +const inline ImguiItem IMGUI_SPRITESHEETS_REPLACE = ImguiItem ({ .label = "Replace", - .tooltip = "Replace the selected spritesheet with another." + .tooltip = "Replace the highlighted spritesheet with another.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_SPRITESHEETS_REMOVE_UNUSED = ImguiItem +({ + .label = "Remove Unused", + .tooltip = "Remove all unused spritesheets in the anm2 (i.e., no layer in any animation uses the spritesheet).", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_FIRST_ROW_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_SPRITESHEETS_SELECT_ALL = ImguiItem +({ + .label = "Select All", + .tooltip = "Select all spritesheets.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_SPRITESHEETS_SELECT_NONE = ImguiItem +({ + .label = "Select None", + .tooltip = "Unselect all spritesheets.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_SPRITESHEETS_SAVE = ImguiItem +({ + .label = "Save", + .tooltip = "Save the selected spritesheet(s) to their original path.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_SPRITESHEETS_OPTIONS_SECOND_ROW_ITEM_COUNT }); const inline ImguiItem IMGUI_ANIMATION_PREVIEW = ImguiItem @@ -742,9 +856,9 @@ const inline ImguiItem IMGUI_ANIMATION_PREVIEW = ImguiItem const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_SETTINGS = ImguiItem ({ - .label = "## Grid Settings", - .size = IMGUI_CANVAS_CHILD_SIZE, - .flags = true + .label = "## Grid Settings", + .size = IMGUI_CANVAS_CHILD_SIZE, + .flags = true }); const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID = ImguiItem @@ -762,10 +876,10 @@ const inline ImguiItem IMGUI_ANIMATION_PREVIEW_GRID_COLOR = ImguiItem 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 + .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 @@ -811,6 +925,22 @@ const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BACKGROUND_COLOR = ImguiItem .flags = ImGuiColorEditFlags_NoInputs }); +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_OVERLAY = ImguiItem +({ + .label = "Overlay", + .tooltip = "Choose an animation to overlay over the previewed animation, for reference." +}); + +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_OVERLAY_TRANSPARENCY = ImguiItem +({ + .label = "Alpha", + .tooltip = "Set the transparency of the animation overlay.", + .format = "%.0f", + .isSizeToChild = true, + .min = 0, + .max = 255 +}); + const inline ImguiItem IMGUI_ANIMATION_PREVIEW_HELPER_SETTINGS = ImguiItem ({ .label = "## Helper Settings", @@ -843,6 +973,12 @@ const inline ImguiItem IMGUI_ANIMATION_PREVIEW_SHOW_PIVOT = ImguiItem .tooltip = "Toggles the appearance of an icon for each animation item's pivot." }); +const inline ImguiItem IMGUI_ANIMATION_PREVIEW_BORDER = ImguiItem +({ + .label = "Border", + .tooltip = "Toggles the appearance of a border around each layer." +}); + const inline ImguiItem IMGUI_SPRITESHEET_EDITOR = ImguiItem ({ .label = "Spritesheet Editor", @@ -879,8 +1015,8 @@ 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 + .min = CANVAS_GRID_MIN, + .max = CANVAS_GRID_MAX }); const inline ImguiItem IMGUI_SPRITESHEET_EDITOR_GRID_OFFSET = ImguiItem @@ -938,6 +1074,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_POSITION = ImguiItem ({ .label = "Position", .tooltip = "Change the position of the selected frame.", + .action = "Frame Position", .format = "%.0f", .isUndoable = true, .speed = 0.25f, @@ -947,6 +1084,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_CROP = ImguiItem ({ .label = "Crop", .tooltip = "Change the crop position of the selected frame.", + .action = "Frame Crop", .format = "%.0f", .isUndoable = true, .speed = 0.25f, @@ -956,6 +1094,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_SIZE = ImguiItem ({ .label = "Size", .tooltip = "Change the size of the crop of the selected frame.", + .action = "Frame Size", .format = "%.0f", .isUndoable = true, .speed = 0.25f @@ -965,6 +1104,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_PIVOT = ImguiItem ({ .label = "Pivot", .tooltip = "Change the pivot of the selected frame.", + .action = "Frame Pivot", .format = "%.0f", .isUndoable = true, .speed = 0.25f @@ -974,7 +1114,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_SCALE = ImguiItem ({ .label = "Scale", .tooltip = "Change the scale of the selected frame.", - .format = "%.1f", + .action = "Frame Scale", .isUndoable = true, .speed = 0.25f }); @@ -983,7 +1123,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_ROTATION = ImguiItem ({ .label = "Rotation", .tooltip = "Change the rotation of the selected frame.", - .format = "%.1f", + .action = "Frame Rotation", .isUndoable = true, .speed = 0.25f }); @@ -992,14 +1132,17 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_DURATION = ImguiItem ({ .label = "Duration", .tooltip = "Change the duration of the selected frame.", + .action = "Frame Duration", .isUndoable = true, - .min = 1, + .min = ANM2_FRAME_NUM_MIN, + .max = ANM2_FRAME_NUM_MAX }); const inline ImguiItem IMGUI_FRAME_PROPERTIES_TINT = ImguiItem ({ .label = "Tint", .tooltip = "Change the tint of the selected frame.", + .action = "Frame Tint", .isUndoable = true, }); @@ -1007,6 +1150,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_COLOR_OFFSET = ImguiItem ({ .label = "Color Offset", .tooltip = "Change the color offset of the selected frame.", + .action = "Frame Color Offset", .isUndoable = true, }); @@ -1014,6 +1158,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_VISIBLE = ImguiItem ({ .label = "Visible", .tooltip = "Toggles the visibility of the selected frame.", + .action = "Frame Visibility", .isUndoable = true, }); @@ -1021,6 +1166,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_INTERPOLATED = ImguiItem ({ .label = "Interpolation", .tooltip = "Toggles the interpolation of the selected frame.", + .action = "Frame Interpolation", .isUndoable = true, }); @@ -1028,20 +1174,25 @@ 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 + .action = "Frame Flip X", + .isUndoable = true, + .size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE }); 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 + .action = "Frame Flip Y", + .isUndoable = true, + .size = IMGUI_FRAME_PROPERTIES_FLIP_BUTTON_SIZE }); 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.", + .action = "Trigger Event", .isUndoable = true }); @@ -1049,6 +1200,7 @@ const inline ImguiItem IMGUI_FRAME_PROPERTIES_AT_FRAME = ImguiItem ({ .label = "At Frame", .tooltip = "Change the frame where the trigger occurs.", + .action = "Trigger At Frame", .isUndoable = true }); @@ -1151,6 +1303,12 @@ const inline ImguiItem IMGUI_TOOL_COLOR = ImguiItem .flags = ImGuiColorEditFlags_NoInputs }); +const inline ImguiItem IMGUI_TOOL_COLOR_PICKER_TOOLTIP_COLOR = ImguiItem +({ + .label = "## Color Picker Tooltip Color", + .flags = ImGuiColorEditFlags_NoTooltip +}); + const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = { &IMGUI_TOOL_PAN, @@ -1168,7 +1326,7 @@ const inline ImguiItem* IMGUI_TOOL_ITEMS[TOOL_COUNT] = const inline ImguiItem IMGUI_TIMELINE = ImguiItem ({ - .label = IMGUI_TIMELINE_LABEL, + .label = "Timeline", .flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse }); @@ -1264,7 +1422,9 @@ 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.", + .action = "Root Item Select", .texture = TEXTURE_ROOT, + .isUndoable = true, .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE }); @@ -1272,8 +1432,10 @@ const inline ImguiItem IMGUI_TIMELINE_ITEM_LAYER_SELECTABLE = ImguiItem ({ .label = "## Layer Selectable", .tooltip = "A layer item.\nA graphical item within the animation.", + .action = "Layer Item Select", .dragDrop = "## Layer Drag Drop", .texture = TEXTURE_LAYER, + .isUndoable = true, .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE }); @@ -1281,8 +1443,10 @@ 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.", + .action = "Null Item Select", .dragDrop = "## Null Drag Drop", .texture = TEXTURE_NULL, + .isUndoable = true, .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE }); @@ -1290,7 +1454,9 @@ const inline ImguiItem IMGUI_TIMELINE_ITEM_TRIGGERS_SELECTABLE = ImguiItem ({ .label = "Triggers", .tooltip = "The animation's triggers.\nWill fire based on an event.", + .action = "Triggers Item Select", .texture = TEXTURE_TRIGGERS, + .isUndoable = true, .size = IMGUI_TIMELINE_ITEM_SELECTABLE_SIZE }); @@ -1329,7 +1495,7 @@ const inline ImguiItem IMGUI_TIMELINE_ITEM_SHOW_RECT = ImguiItem const inline ImguiItem IMGUI_TIMELINE_ITEM_HIDE_RECT = ImguiItem ({ - .label = "## Invisible", + .label = "## Hide Rect", .tooltip = "The rect is hidden.\nPress to show rect.", .texture = TEXTURE_HIDE_RECT, .isUndoable = true @@ -1344,17 +1510,13 @@ const inline ImguiItem IMGUI_TIMELINE_SPRITESHEET_ID = ImguiItem .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_FRAME = ImguiItem({"## Frame"}); const inline ImguiItem IMGUI_TIMELINE_ROOT_FRAME = ImguiItem ({ .label = "## Root Frame", + .action = "Root Frame Select", + .isUndoable = true, .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, @@ -1364,7 +1526,9 @@ const inline ImguiItem IMGUI_TIMELINE_ROOT_FRAME = ImguiItem const inline ImguiItem IMGUI_TIMELINE_LAYER_FRAME = ImguiItem ({ .label = "## Layer Frame", + .action = "Layer Frame Select", .dragDrop = "## Layer Frame Drag Drop", + .isUndoable = true, .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, @@ -1374,7 +1538,9 @@ const inline ImguiItem IMGUI_TIMELINE_LAYER_FRAME = ImguiItem const inline ImguiItem IMGUI_TIMELINE_NULL_FRAME = ImguiItem ({ .label = "## Null Frame", + .action = "Null Frame Select", .dragDrop = "## Null Frame Drag Drop", + .isUndoable = true, .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, @@ -1384,6 +1550,8 @@ const inline ImguiItem IMGUI_TIMELINE_NULL_FRAME = ImguiItem const inline ImguiItem IMGUI_TIMELINE_TRIGGERS_FRAME = ImguiItem ({ .label = "## Triggers Frame", + .action = "Trigger Select", + .isUndoable = true, .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, @@ -1399,65 +1567,155 @@ const inline ImguiItem* IMGUI_TIMELINE_FRAMES[ANM2_COUNT] &IMGUI_TIMELINE_TRIGGERS_FRAME }; -const inline ImguiItem IMGUI_TIMELINE_ADD_ELEMENT = ImguiItem +const inline ImguiItem IMGUI_TIMELINE_PICKER = ImguiItem ({ - .label = "Add Element", - .tooltip = "Adds an element (layer or null) to the animation.", - .popup = IMGUI_TIMELINE_ADD_ELEMENT_POPUP + .label = "## Timeline Picker", + .flags = ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoInputs }); -const inline ImguiItem IMGUI_TIMELINE_ADD_ELEMENT_LAYER = ImguiItem +const inline ImguiItem IMGUI_TIMELINE_FOOTER_ITEM_CHILD = ImguiItem +({ + .label = "## Timeline Footer Item Child", + .size = {IMGUI_TIMELINE_ITEM_SIZE.x, 0}, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM = ImguiItem +({ + .label = "Add", + .tooltip = "Adds an item (layer or null) to the animation.", + .popup = "##Add Item Popup", + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM_LAYER = ImguiItem ({ .label = "Layer", - .tooltip = "Adds a layer element.\nA layer element is a primary graphical element, using a spritesheet.", - .isUndoable = true + .tooltip = "Adds a layer item.\nA layer item is a primary graphical item, using a spritesheet.", + .action = "Add Layer", + .isUndoable = true, + }); -const inline ImguiItem IMGUI_TIMELINE_ADD_ELEMENT_NULL = ImguiItem +const inline ImguiItem IMGUI_TIMELINE_ADD_ITEM_NULL = ImguiItem ({ .label = "Null", - .tooltip = "Adds a null element.\nA null element is an invisible element, often accessed by the game engine.", + .tooltip = "Adds a null item.\nA null item is an invisible item, often accessed by the game engine.", + .action = "Add Null", .isUndoable = true }); -const inline ImguiItem IMGUI_TIMELINE_REMOVE_ELEMENT = ImguiItem +const inline ImguiItem IMGUI_TIMELINE_REMOVE_ITEM = ImguiItem ({ - .label = "Remove Element", - .tooltip = "Removes an element (layer or null) from the animation." + .label = "Remove", + .tooltip = "Removes the selected item (layer or null) from the animation.", + .action = "Remove Item", + .focusWindow = IMGUI_TIMELINE.label, + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_FOOTER_ITEM_CHILD_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_TIMELINE_FOOTER_OPTIONS_CHILD = ImguiItem +({ + .label = "## Timeline Footer Options Child", + .flags = true }); const inline ImguiItem IMGUI_TIMELINE_PLAY = ImguiItem ({ .label = "|> Play", - .tooltip = "Play the current animation, if paused." + .tooltip = "Play the current animation, if paused.", + .chord = ImGuiKey_Space }); const inline ImguiItem IMGUI_TIMELINE_PAUSE = ImguiItem ({ .label = "|| Pause", - .tooltip = "Pause the current animation, if playing." + .tooltip = "Pause the current animation, if playing.", + .chord = ImGuiKey_Space }); const inline ImguiItem IMGUI_TIMELINE_ADD_FRAME = ImguiItem ({ - .label = "Add Frame", - .tooltip = "Adds a frame to the selected animation item.", + .label = "+ Insert Frame", + .tooltip = "Inserts a frame in the selected animation item, based on the preview time.", + .action = "Insert Frame", .isUndoable = true }); const inline ImguiItem IMGUI_TIMELINE_REMOVE_FRAME = ImguiItem ({ - .label = "Remove Frame", + .label = "- Delete Frame", .tooltip = "Removes the selected frame from the selected animation item.", - .focusWindow = IMGUI_TIMELINE_LABEL, + .action = "Delete Frame", + .focusWindow = IMGUI_TIMELINE.label, .chord = ImGuiKey_Delete, .isUndoable = true, }); +const inline ImguiItem IMGUI_TIMELINE_BAKE = ImguiItem +({ + .label = "Bake", + .tooltip = "Opens the bake popup menu, if a frame is selected.\nBaking a frame takes the currently interpolated values at the time between it and the next frame and separates them based on the interval.", + .popup = "Bake Frames", + .popupType = IMGUI_POPUP_CENTER_SCREEN +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_CHILD = ImguiItem +({ + .label = "## Bake Child", + .size = {200, 110}, + .flags = true +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_INTERVAL = ImguiItem +({ + .label = "Interval", + .tooltip = "Sets the delay of the baked frames the selected frame will be separated out into.", + .min = ANM2_FRAME_DELAY_MIN +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_ROUND_SCALE = ImguiItem +({ + .label = "Round Scale", + .tooltip = "The scale of the baked frames will be rounded to the nearest integer." +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_ROUND_ROTATION = ImguiItem +({ + .label = "Round Rotation", + .tooltip = "The rotation of the baked frames will be rounded to the nearest integer." +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_CONFIRM = ImguiItem +({ + .label = "Bake", + .tooltip = "Bake the selected frame with the options selected.", + .action = "Baking Frames", + .isUndoable = true, + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT +}); + +const inline ImguiItem IMGUI_TIMELINE_BAKE_CANCEL = ImguiItem +({ + .label = "Cancel", + .tooltip = "Cancel baking the selected frame.", + .isSizeToChild = true, + .childRowItemCount = IMGUI_TIMELINE_BAKE_OPTIONS_CHILD_ROW_ITEM_COUNT +}); + const inline ImguiItem IMGUI_TIMELINE_FIT_ANIMATION_LENGTH = ImguiItem ({ .label = "Fit Animation Length", .tooltip = "Sets the animation's length to the latest frame.", + .action = "Fit Animation Length", .isUndoable = true }); @@ -1465,6 +1723,7 @@ const inline ImguiItem IMGUI_TIMELINE_ANIMATION_LENGTH = ImguiItem ({ .label = "Animation Length", .tooltip = "Sets the animation length.\n(Will not change frames.)", + .action = "Set Animation Length", .isUndoable = true, .size = {100, 0}, .min = ANM2_FRAME_NUM_MIN, @@ -1475,6 +1734,7 @@ const inline ImguiItem IMGUI_TIMELINE_FPS = ImguiItem ({ .label = "FPS", .tooltip = "Sets the animation's frames per second (its speed).", + .action = "Set FPS", .isUndoable = true, .size = {100, 0}, .min = ANM2_FPS_MIN, @@ -1485,6 +1745,7 @@ const inline ImguiItem IMGUI_TIMELINE_LOOP = ImguiItem ({ .label = "Loop", .tooltip = "Toggles the animation looping.", + .action = "Set Loop", .isUndoable = true }); @@ -1492,7 +1753,6 @@ 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 }); @@ -1506,34 +1766,48 @@ const inline ImguiItem IMGUI_RECORDING = ImguiItem .texture = TEXTURE_RECORD }); +const inline ImguiItem IMGUI_CONTEXT_MENU = ImguiItem +({ + .label = "## Context Menu", + .popup = "## Context Menu Popup" +}); + const inline ImguiItem IMGUI_CUT = ImguiItem ({ - .label = "Cut", - .tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", + .label = "Cut", + .tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", + .action = "Cut", .function = imgui_cut, - .chord = ImGuiMod_Ctrl | ImGuiKey_X + .chord = ImGuiMod_Ctrl | ImGuiKey_X, + .isUndoable = true, }); const inline ImguiItem IMGUI_COPY = ImguiItem ({ - .label = "Copy", - .tooltip = "Copies the currently selected contextual element to the clipboard.", + .label = "Copy", + .tooltip = "Copies the currently selected contextual element to the clipboard.", + .action = "Copy", .function = imgui_copy, - .chord = ImGuiMod_Ctrl | ImGuiKey_C + .chord = ImGuiMod_Ctrl | ImGuiKey_C, + .isUndoable = true, }); const inline ImguiItem IMGUI_PASTE = ImguiItem ({ .label = "Paste", .tooltip = "Pastes the currently selection contextual element from the clipboard.", + .action = "Paste", .function = imgui_paste, - .chord = ImGuiMod_Ctrl | ImGuiKey_V + .chord = ImGuiMod_Ctrl | ImGuiKey_V, + .isUndoable = true, }); const inline ImguiItem IMGUI_RENAMABLE = ImguiItem ({ .label = "## Renaming", .tooltip = "Rename the selected item.", + .action = "Rename", + .isUndoable = true, .max = 255, .flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue }); @@ -1542,13 +1816,15 @@ const inline ImguiItem IMGUI_CHANGEABLE = ImguiItem ({ .label = "## Changing", .tooltip = "Change the selected item's value.", + .action = "Change Value", + .isUndoable = true, .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 + .popup = "Exit Confirmation" }); const inline ImguiItem IMGUI_POPUP_YES_BUTTON = ImguiItem @@ -1579,6 +1855,6 @@ void imgui_init SDL_GLContext* glContext ); -void imgui_tick(Imgui* self); +void imgui_update(Imgui* self); void imgui_draw(); void imgui_free(); \ No newline at end of file diff --git a/src/preview.cpp b/src/preview.cpp index f3b3a37..aae1095 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -2,59 +2,6 @@ #include "preview.h" -static void _preview_axis_set(Preview* self) -{ - glBindVertexArray(self->axisVAO); - glBindBuffer(GL_ARRAY_BUFFER, self->axisVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); - - glBindVertexArray(0); -} - -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 - for (s32 i = 0; i <= verticalLineCount; i++) - { - s32 x = i * self->settings->previewGridSizeX - self->settings->previewGridOffsetX; - f32 normX = (2.0f * x) / PREVIEW_SIZE.x - 1.0f; - - vertices.push_back(normX); - vertices.push_back(-1.0f); - vertices.push_back(normX); - vertices.push_back(1.0f); - } - - // Horizontal - for (s32 i = 0; i <= horizontalLineCount; i++) - { - s32 y = i * self->settings->previewGridSizeY - self->settings->previewGridOffsetY; - f32 normY = (2.0f * y) / PREVIEW_SIZE.y - 1.0f; - - vertices.push_back(-1.0f); - vertices.push_back(normY); - vertices.push_back(1.0f); - vertices.push_back(normY); - } - - glBindVertexArray(self->gridVAO); - glBindBuffer(GL_ARRAY_BUFFER, self->gridVBO); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(f32), vertices.data(), GL_DYNAMIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); - - return (s32)vertices.size(); -} - void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings) { self->anm2 = anm2; @@ -62,87 +9,19 @@ void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources self->resources = resources; self->settings = settings; - // Framebuffer + texture - 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)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); - - 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)PREVIEW_SIZE.x, (s32)PREVIEW_SIZE.y); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Axis - glGenVertexArrays(1, &self->axisVAO); - glGenBuffers(1, &self->axisVBO); - - // Grid - glGenVertexArrays(1, &self->gridVAO); - glGenBuffers(1, &self->gridVBO); - - // Rect - glGenVertexArrays(1, &self->rectVAO); - glGenBuffers(1, &self->rectVBO); - - glBindVertexArray(self->rectVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(GL_VERTICES), GL_VERTICES, GL_STATIC_DRAW); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)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); - - _preview_axis_set(self); - _preview_grid_set(self); + canvas_init(&self->canvas); } void preview_tick(Preview* self) { - self->settings->previewZoom = CLAMP(self->settings->previewZoom, PREVIEW_ZOOM_MIN, PREVIEW_ZOOM_MAX); - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - // If animation is valid, manage playback if (animation) { if (self->isPlaying) { 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->settings->playbackIsLoop && !self->isRecording) @@ -152,414 +31,181 @@ void 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 = std::clamp(self->time, 0.0f, (f32)animation->frameNum - 1); } } void preview_draw(Preview* self) { - GLuint shaderLine = self->resources->shaders[SHADER_LINE]; - GLuint shaderTexture = self->resources->shaders[SHADER_TEXTURE]; - static bool isRecordThisFrame = false; - static f32 recordFrameTimeNext = 0.0f; - static s32 recordFrameIndex = 0; - - f32 zoomFactor = PERCENT_TO_UNIT(self->settings->previewZoom); - glm::vec2 ndcPan = glm::vec2(-self->settings->previewPanX / (PREVIEW_SIZE.x / 2.0f), -self->settings->previewPanY / (PREVIEW_SIZE.y / 2.0f)); - glm::mat4 previewTransform = glm::translate(glm::mat4(1.0f), glm::vec3(ndcPan, 0.0f)); - previewTransform = glm::scale(previewTransform, glm::vec3(zoomFactor, zoomFactor, 1.0f)); - - glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); - glViewport(0, 0, (s32)PREVIEW_SIZE.x, (s32)PREVIEW_SIZE.y); - - glClearColor - ( - self->settings->previewBackgroundColorR, - self->settings->previewBackgroundColorG, - self->settings->previewBackgroundColorB, - self->settings->previewBackgroundColorA - ); - glClear(GL_COLOR_BUFFER_BIT); - - // Grid - if (self->settings->previewIsGrid) - { - static ivec2 previousGridSize = {-1, -1}; - static ivec2 previousGridOffset = {-1, -1}; - static s32 gridVertexCount = -1; - ivec2 gridSize = ivec2(self->settings->previewGridSizeX, self->settings->previewGridSizeY); - ivec2 gridOffset = ivec2(self->settings->previewGridOffsetX, self->settings->previewGridOffsetY); - - if (previousGridSize != gridSize || previousGridOffset != gridOffset) - { - gridVertexCount = _preview_grid_set(self); - previousGridSize = gridSize; - previousGridOffset = gridOffset; - } - - glUseProgram(shaderLine); - glBindVertexArray(self->gridVAO); - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(previewTransform)); - - glUniform4f - ( - glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), - self->settings->previewGridColorR, self->settings->previewGridColorG, self->settings->previewGridColorB, self->settings->previewGridColorA - ); - - glDrawArrays(GL_LINES, 0, gridVertexCount); + ivec2& gridSize = self->settings->previewGridSize; + ivec2& gridOffset = self->settings->previewGridOffset; + vec4& gridColor = self->settings->previewGridColor; + f32& zoom = self->settings->previewZoom; + GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; + GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; + mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER); + + canvas_texture_set(&self->canvas); - glBindVertexArray(0); - glUseProgram(0); - } + canvas_bind(&self->canvas); + canvas_viewport_set(&self->canvas); + canvas_clear(self->settings->previewBackgroundColor); + + if (self->settings->previewIsGrid) + canvas_grid_draw(&self->canvas, shaderLine, transform, zoom, gridSize, gridOffset, gridColor); - // Axes if (self->settings->previewIsAxis) - { - glUseProgram(shaderLine); - glBindVertexArray(self->axisVAO); - - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, (f32*)value_ptr(previewTransform)); - - glUniform4f - ( - glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), - self->settings->previewAxisColorR, self->settings->previewAxisColorG, self->settings->previewAxisColorB, self->settings->previewAxisColorA - ); - - glDrawArrays(GL_LINES, 0, 2); - - glUniform4f - ( - glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), - self->settings->previewAxisColorR, self->settings->previewAxisColorG, self->settings->previewAxisColorB, self->settings->previewAxisColorA - ); - - glDrawArrays(GL_LINES, 2, 2); - - glBindVertexArray(0); - glUseProgram(0); - } + canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxisColor); Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); s32& animationID = self->reference->animationID; - // Animation if (animation) { - Anm2Frame rootFrame; - Anm2Frame frame; - anm2_frame_from_time(self->anm2, &rootFrame, Anm2Reference{animationID, ANM2_ROOT, 0, 0}, self->time); + Anm2Frame root; + mat4 rootModel = mat4(1.0f); + + anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time); + + if (self->settings->previewIsRootTransform) + rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale)); + + // Root + if (animation->rootAnimation.isVisible && root.isVisible) + { + mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, root.rotation, PERCENT_TO_UNIT(root.scale)); + mat4 rootTransform = transform * model; + f32 vertices[] = ATLAS_UV_VERTICES(TEXTURE_TARGET); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, PREVIEW_ROOT_COLOR); + } // Layers for (auto [i, id] : self->anm2->layerMap) { + Anm2Frame frame; 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}, self->time); if (!frame.isVisible) continue; - Texture* texture = &self->resources->textures[self->anm2->layers[id].spritesheetID]; + Texture texture = self->resources->textures[self->anm2->layers[id].spritesheetID]; - if (texture->isInvalid) + if (texture.isInvalid) continue; - glm::mat4 layerTransform = previewTransform; - - glm::vec2 position = self->settings->previewIsRootTransform ? - (frame.position + rootFrame.position) : frame.position; - - glm::vec2 scale = self->settings->previewIsRootTransform ? - PERCENT_TO_UNIT(frame.scale) * PERCENT_TO_UNIT(rootFrame.scale) : PERCENT_TO_UNIT(frame.scale); - - glm::vec2 ndcPos = position / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcPivotOffset = (frame.pivot * scale) / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = (frame.size * scale) / (PREVIEW_SIZE / 2.0f); - f32 rotation = frame.rotation; - - layerTransform = glm::translate(layerTransform, glm::vec3(ndcPos - ndcPivotOffset, 0.0f)); - layerTransform = glm::translate(layerTransform, glm::vec3(ndcPivotOffset, 0.0f)); - layerTransform = glm::rotate(layerTransform, glm::radians(rotation), glm::vec3(0, 0, 1)); - layerTransform = glm::translate(layerTransform, glm::vec3(-ndcPivotOffset, 0.0f)); - layerTransform = glm::scale(layerTransform, glm::vec3(ndcScale, 1.0f)); - - glm::vec2 uvMin = frame.crop / glm::vec2(texture->size); - glm::vec2 uvMax = (frame.crop + frame.size) / glm::vec2(texture->size); - + vec2 uvMin = frame.crop / vec2(texture.size); + vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size); f32 vertices[] = UV_VERTICES(uvMin, uvMax); - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture->id); + mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 layerTransform = transform * (rootModel * model); - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, (f32*)value_ptr(frame.tintRGBA)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, (f32*)value_ptr(frame.offsetRGB)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(layerTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - } + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB); - // Root target - if (animation->rootAnimation.isVisible && rootFrame.isVisible) - { - glm::mat4 rootTransform = previewTransform; - glm::vec2 ndcPos = (rootFrame.position - (PREVIEW_TARGET_SIZE / 2.0f)) / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = PREVIEW_TARGET_SIZE / (PREVIEW_SIZE / 2.0f); + if (self->settings->previewIsBorder) + canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR); - rootTransform = glm::translate(rootTransform, glm::vec3(ndcPos, 0.0f)); - rootTransform = glm::scale(rootTransform, glm::vec3(ndcScale, 1.0f)); - - f32 vertices[] = ATLAS_UV_VERTICES(TEXTURE_TARGET); - - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, self->resources->atlas.id); - - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_ROOT_TINT)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(COLOR_OFFSET_NONE)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(rootTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); - } - - // Layer pivots - if (self->settings->previewIsShowPivot) - { - // Layers (Reversed) - for (auto & [id, layerAnimation] : animation->layerAnimations) + if (self->settings->previewIsShowPivot) { - if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) - continue; - - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id, 0}, self->time); - - if (!frame.isVisible) - continue; - - glm::mat4 pivotTransform = previewTransform; - - glm::vec2 position = self->settings->previewIsRootTransform - ? (frame.position + rootFrame.position) : frame.position; - - glm::vec2 scale = self->settings->previewIsRootTransform ? - PERCENT_TO_UNIT(frame.scale) * PERCENT_TO_UNIT(rootFrame.scale) : PERCENT_TO_UNIT(frame.scale); - - glm::vec2 ndcPos = (position - (PREVIEW_PIVOT_SIZE / 2.0f)) / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = PREVIEW_PIVOT_SIZE / (PREVIEW_SIZE / 2.0f); - - pivotTransform = glm::translate(pivotTransform, glm::vec3(ndcPos, 0.0f)); - pivotTransform = glm::scale(pivotTransform, glm::vec3(ndcScale, 1.0f)); - f32 vertices[] = ATLAS_UV_VERTICES(TEXTURE_PIVOT); - - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, self->resources->atlas.id); - - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_PIVOT_TINT)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(COLOR_OFFSET_NONE)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(pivotTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glUseProgram(0); + mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 pivotTransform = transform * (rootModel * pivotModel); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR); } } - // Null target/rect - for (auto & [id, nullAnimation] : animation->nullAnimations) + // Nulls + for (auto& [id, nullAnimation] : animation->nullAnimations) { if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) continue; - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id, 0}, self->time); + Anm2Frame frame; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, self->time); if (!frame.isVisible) continue; - Anm2Null* null = &self->anm2->nulls[id]; + Anm2Null null = self->anm2->nulls[id]; - glm::mat4 nullTransform = previewTransform; + vec4 color = (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? + PREVIEW_NULL_SELECTED_COLOR : + PREVIEW_NULL_COLOR; - 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); + vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; + TextureType texture = null.isShowRect ? TEXTURE_SQUARE : TEXTURE_TARGET; + + mat4 model = quad_model_get(size, frame.position, size * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 nullTransform = transform * (rootModel * model); + + f32 vertices[] = ATLAS_UV_VERTICES(texture); + + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, nullTransform, vertices, color); - glm::vec2 ndcPos = pos / (PREVIEW_SIZE / 2.0f); - glm::vec2 ndcScale = size / (PREVIEW_SIZE / 2.0f); - - nullTransform = glm::translate(nullTransform, glm::vec3(ndcPos, 0.0f)); - nullTransform = glm::scale(nullTransform, glm::vec3(ndcScale, 1.0f)); - - f32 vertices[] = ATLAS_UV_VERTICES(textureType); - - glUseProgram(shaderTexture); - - glBindVertexArray(self->textureVAO); - - glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, self->resources->atlas.id); - - glUniform1i(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TEXTURE), 0); - glUniform4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TINT), 1, value_ptr(PREVIEW_NULL_TINT)); - glUniform3fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_COLOR_OFFSET), 1, value_ptr(COLOR_OFFSET_NONE)); - glUniformMatrix4fv(glGetUniformLocation(shaderTexture, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(nullTransform)); - - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); - - glBindTexture(GL_TEXTURE_2D, 0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glUseProgram(0); - - // Null rect - if (null->isShowRect) + if (null.isShowRect) { - glm::mat4 rectTransform = previewTransform; - - glm::vec2 rectPos = pos - (PREVIEW_NULL_RECT_SIZE / 2.0f); - glm::vec2 rectNDCPos = rectPos / (PREVIEW_SIZE / 2.0f); - glm::vec2 rectNDCScale = PREVIEW_NULL_RECT_SIZE / (PREVIEW_SIZE / 2.0f); - - rectTransform = glm::translate(rectTransform, glm::vec3(rectNDCPos, 0.0f)); - rectTransform = glm::scale(rectTransform, glm::vec3(rectNDCScale, 1.0f)); - - glUseProgram(shaderLine); - - glBindVertexArray(self->rectVAO); - - glUniformMatrix4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, glm::value_ptr(rectTransform)); - - glUniform4fv(glGetUniformLocation(shaderLine, SHADER_UNIFORM_COLOR), 1, glm::value_ptr(PREVIEW_NULL_TINT)); - - glDrawArrays(GL_LINE_LOOP, 0, 4); - - glBindVertexArray(0); - glUseProgram(0); + mat4 rectModel = quad_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 rectTransform = transform * (rootModel * rectModel); + canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); } } } - /* - if (self->isRecording && animation) + s32& animationOverlayID = self->animationOverlayID; + Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID); + + if (animationOverlay) { - if (recordFrameIndex == 0) + Anm2Frame root; + mat4 rootModel = mat4(1.0f); + + anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationOverlayID, ANM2_ROOT}, self->time); + + if (self->settings->previewIsRootTransform) + rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale)); + + for (auto [i, id] : self->anm2->layerMap) { - if - ( - std::filesystem::exists(STRING_PREVIEW_FRAMES_DIRECTORY) && - std::filesystem::is_directory(STRING_PREVIEW_FRAMES_DIRECTORY)) - { - for (const auto & entry : std::filesystem::directory_iterator(STRING_PREVIEW_FRAMES_DIRECTORY)) - std::filesystem::remove(entry); - } - else - std::filesystem::create_directories(STRING_PREVIEW_FRAMES_DIRECTORY); - self->isPlaying = true; - } + Anm2Frame frame; + Anm2Item& layerAnimation = animation->layerAnimations[id]; - if (isRecordThisFrame) - { - size_t frameSize = (self->recordSize.x * self->recordSize.y * 4); - u8* frame = (u8*)calloc(frameSize, 1); - std:: string path; + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) + continue; - vec2 position = - { - self->settings->previewPanX - (PREVIEW_SIZE.x / 2.0f) + (self->recordSize.x / 2.0f), - self->settings->previewPanY - (PREVIEW_SIZE.y / 2.0f) + (self->recordSize.y / 2.0f) - }; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationOverlayID, ANM2_LAYER, id}, self->time); - path = std::format(STRING_PREVIEW_FRAMES_FORMAT, STRING_PREVIEW_FRAMES_DIRECTORY, recordFrameIndex); + if (!frame.isVisible) + continue; - glReadBuffer(GL_FRONT); - glReadPixels - ( - (s32)position.x, - (s32)position.y, - self->recordSize.x, - self->recordSize.y, - GL_RGBA, - GL_UNSIGNED_BYTE, - frame - ); + Texture texture = self->resources->textures[self->anm2->layers[id].spritesheetID]; - texture_from_data_write(path, frame, self->recordSize.x, self->recordSize.y); + if (texture.isInvalid) + continue; - free(frame); + vec2 uvMin = frame.crop / vec2(texture.size); + vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size); + f32 vertices[] = UV_VERTICES(uvMin, uvMax); - isRecordThisFrame = false; - recordFrameIndex++; - } - else - { - if (self->time >= (f32)animation->frameNum - 1) - { - self->isRecording = false; - self->isPlaying = false; - recordFrameIndex = 0; - recordFrameTimeNext = 0; - self->time = 0.0f; - } - else if (self->time >= recordFrameTimeNext) - { - isRecordThisFrame = true; - recordFrameTimeNext = self->time + (f32)self->anm2->fps / TICK_RATE; - } + mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale)); + mat4 layerTransform = transform * (rootModel * model); + + vec4 tint = frame.tintRGBA; + tint.a *= U8_TO_FLOAT(self->settings->previewOverlayTransparency); + + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, tint, frame.offsetRGB); } } - */ - glBindFramebuffer(GL_FRAMEBUFFER, 0); + canvas_unbind(); } void preview_free(Preview* self) { - glDeleteTextures(1, &self->texture); - glDeleteFramebuffers(1, &self->fbo); - glDeleteRenderbuffers(1, &self->rbo); + canvas_free(&self->canvas); } \ No newline at end of file diff --git a/src/preview.h b/src/preview.h index 8156b28..dfd9ca0 100644 --- a/src/preview.h +++ b/src/preview.h @@ -6,6 +6,7 @@ #include "canvas.h" const vec2 PREVIEW_SIZE = {2000, 2000}; +const vec2 PREVIEW_CANVAS_SIZE = {2000, 2000}; const vec2 PREVIEW_CENTER = {0, 0}; #define PREVIEW_ZOOM_MIN 1 @@ -18,11 +19,12 @@ const vec2 PREVIEW_CENTER = {0, 0}; const vec2 PREVIEW_NULL_RECT_SIZE = {100, 100}; const vec2 PREVIEW_POINT_SIZE = {2, 2}; -const vec2 PREVIEW_PIVOT_SIZE = {4, 4}; -const vec4 PREVIEW_ROOT_TINT = COLOR_GREEN; -const vec4 PREVIEW_NULL_TINT = COLOR_BLUE; -const vec4 PREVIEW_PIVOT_TINT = COLOR_RED; const vec2 PREVIEW_TARGET_SIZE = {16, 16}; +const vec4 PREVIEW_BORDER_COLOR = COLOR_RED; +const vec4 PREVIEW_ROOT_COLOR = COLOR_GREEN; +const vec4 PREVIEW_NULL_COLOR = COLOR_BLUE; +const vec4 PREVIEW_NULL_SELECTED_COLOR = COLOR_RED; +const vec4 PREVIEW_PIVOT_COLOR = COLOR_RED; struct Preview { @@ -30,21 +32,10 @@ struct Preview 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; + s32 animationOverlayID = ID_NONE; + Canvas canvas; bool isPlaying = false; bool isRecording = false; - vec2 recordSize{}; f32 time{}; }; diff --git a/src/settings.cpp b/src/settings.cpp index 88e6aa7..c0eac7d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -2,34 +2,59 @@ static void _settings_setting_load(Settings* self, const std::string& line) { - for (s32 i = 0; i < SETTINGS_COUNT; i++) + for (s32 i = 0; i < SETTINGS_COUNT; i++) { - const std::string& key = SETTINGS_ENTRIES[i].key; - size_t keyLength = key.length(); + const auto& entry = SETTINGS_ENTRIES[i]; + const std::string& key = entry.key; + void* target = (u8*)self + entry.offset; - if (line.compare(0, keyLength, key) == 0) + auto match_key = [&](const std::string& full) -> const char* { - const char* value = line.c_str() + keyLength; - void* target = (u8*)self + SETTINGS_ENTRIES[i].offset; + return (line.starts_with(full) && line[full.size()] == '=') ? line.c_str() + full.size() + 1 : nullptr; + }; - switch (SETTINGS_ENTRIES[i].type) + auto* value = match_key(key); + + if (value) + { + switch (entry.type) { - case SETTINGS_TYPE_INT: - *(s32*)target = std::atoi(value); - break; - case SETTINGS_TYPE_BOOL: - *(s32*)target = string_to_bool(std::string(value)); - break; - case SETTINGS_TYPE_FLOAT: - *(f32*)target = std::atof(value); - break; - case SETTINGS_TYPE_STRING: - *(std::string*)target = std::string(value); - break; - default: + case TYPE_INT: + *(s32*)target = std::atoi(value); + return; + case TYPE_BOOL: + *(bool*)target = string_to_bool(value); + return; + case TYPE_FLOAT: + *(f32*)target = std::atof(value); + return; + case TYPE_STRING: + *(std::string*)target = value; + return; + default: break; } - return; + } + + if (entry.type == TYPE_VEC2) + { + vec2* v = (vec2*)target; + if ((value = match_key(key + "X"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "Y"))) { v->y = std::atof(value); return; } + } + else if (entry.type == TYPE_IVEC2) + { + ivec2* v = (ivec2*)target; + if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; } + if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; } + } + else if (entry.type == TYPE_VEC4) + { + vec4* v = (vec4*)target; + if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } + if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } + if ((value = match_key(key + "A"))) { v->w = std::atof(value); return; } } } } @@ -41,23 +66,48 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE switch (entry.type) { - case SETTINGS_TYPE_INT: + case TYPE_INT: value = std::format("{}", *(s32*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; break; - case SETTINGS_TYPE_BOOL: + case TYPE_BOOL: value = std::format("{}", *(bool*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; break; - case SETTINGS_TYPE_FLOAT: + case TYPE_FLOAT: value = std::format("{:.3f}", *(f32*)(selfPointer + entry.offset)); + out << entry.key << "=" << value << "\n"; break; - case SETTINGS_TYPE_STRING: + case TYPE_STRING: value = *(std::string*)(selfPointer + entry.offset); + out << entry.key << "=" << value << "\n"; break; + case TYPE_IVEC2: + { + ivec2* data = (ivec2*)(selfPointer + entry.offset); + out << entry.key << "X=" << data->x << "\n"; + out << entry.key << "Y=" << data->y << "\n"; + break; + } + case TYPE_VEC2: + { + vec2* data = (vec2*)(selfPointer + entry.offset); + out << entry.key << "X=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; + out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; + break; + } + case TYPE_VEC4: + { + vec4* data = (vec4*)(selfPointer + entry.offset); + out << entry.key << "R=" << std::format(SETTINGS_FLOAT_FORMAT, data->r) << "\n"; + out << entry.key << "G=" << std::format(SETTINGS_FLOAT_FORMAT, data->g) << "\n"; + out << entry.key << "B=" << std::format(SETTINGS_FLOAT_FORMAT, data->b) << "\n"; + out << entry.key << "A=" << std::format(SETTINGS_FLOAT_FORMAT, data->a) << "\n"; + break; + } default: break; } - - out << entry.key << value << "\n"; } void settings_save(Settings* self) @@ -75,6 +125,7 @@ void settings_save(Settings* self) input.close(); std::ofstream output(SETTINGS_PATH); + if (!output) { log_error(std::format(SETTINGS_INIT_ERROR, SETTINGS_PATH)); diff --git a/src/settings.h b/src/settings.h index ad20453..46fdff7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,6 +1,6 @@ #pragma once -#include "COMMON.h" +#include "tool.h" #define SETTINGS_BUFFER 0xFFFF #define SETTINGS_BUFFER_ITEM 0xFF @@ -8,124 +8,73 @@ #define SETTINGS_SECTION_IMGUI "# Dear ImGui" #define SETTINGS_INIT_ERROR "Failed to read settings file! ({})" #define SETTINGS_PATH "settings.ini" - -enum SettingsValueType -{ - SETTINGS_TYPE_INT, - SETTINGS_TYPE_FLOAT, - SETTINGS_TYPE_BOOL, - SETTINGS_TYPE_STRING -}; +#define SETTINGS_FLOAT_FORMAT "{:.3f}" struct SettingsEntry { std::string key; - SettingsValueType type; + DataType type; s32 offset; }; struct Settings { - s32 windowW = 1920; - s32 windowH = 1080; + ivec2 windowSize = {1080, 720}; bool playbackIsLoop = true; bool previewIsAxis = true; bool previewIsGrid = true; bool previewIsRootTransform = false; bool previewIsShowPivot = false; - f32 previewPanX = 0.0f; - f32 previewPanY = 0.0f; - f32 previewZoom = 200.0f; - s32 previewGridSizeX = 32; - s32 previewGridSizeY = 32; - s32 previewGridOffsetX = 0; - s32 previewGridOffsetY = 0; - f32 previewGridColorR = 1.0f; - f32 previewGridColorG = 1.0f; - f32 previewGridColorB = 1.0f; - f32 previewGridColorA = 0.125f; - f32 previewAxisColorR = 1.0f; - f32 previewAxisColorG = 1.0f; - f32 previewAxisColorB = 1.0f; - f32 previewAxisColorA = 0.5f; - f32 previewBackgroundColorR = 0.113f; - f32 previewBackgroundColorG = 0.184f; - f32 previewBackgroundColorB = 0.286f; - f32 previewBackgroundColorA = 1.0f; + bool previewIsBorder = false; + f32 previewOverlayTransparency = 255.0f; + f32 previewZoom = 200.0; + vec2 previewPan = {0.0, 0.0}; + ivec2 previewGridSize = {32, 32}; + ivec2 previewGridOffset{}; + vec4 previewGridColor = {1.0, 1.0, 1.0, 0.125}; + vec4 previewAxisColor = {1.0, 1.0, 1.0, 0.125}; + vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0}; bool editorIsGrid = true; bool editorIsGridSnap = true; bool editorIsBorder = true; - f32 editorPanX = 0.0f; - f32 editorPanY = 0.0f; - f32 editorZoom = 200.0f; - s32 editorGridSizeX = 32; - s32 editorGridSizeY = 32; - s32 editorGridOffsetX = 32; - s32 editorGridOffsetY = 32; - f32 editorGridColorR = 1.0f; - f32 editorGridColorG = 1.0f; - f32 editorGridColorB = 1.0f; - f32 editorGridColorA = 0.125f; - f32 editorBackgroundColorR = 0.113f; - 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; + f32 editorZoom = 200.0; + vec2 editorPan = {0.0, 0.0}; + ivec2 editorGridSize = {32, 32}; + ivec2 editorGridOffset = {32, 32}; + vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125}; + vec4 editorBackgroundColor = {0.113, 0.184, 0.286, 1.0}; + ToolType tool = TOOL_PAN; + vec4 toolColor = {1.0, 1.0, 1.0, 1.0}; }; const SettingsEntry SETTINGS_ENTRIES[] = { - {"windowW=", SETTINGS_TYPE_INT, offsetof(Settings, windowW)}, - {"windowH=", SETTINGS_TYPE_INT, offsetof(Settings, windowH)}, - {"playbackIsLoop=", SETTINGS_TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, - {"previewIsAxis=", SETTINGS_TYPE_BOOL, offsetof(Settings, previewIsAxis)}, - {"previewIsGrid=", SETTINGS_TYPE_BOOL, offsetof(Settings, previewIsGrid)}, - {"previewIsRootTransform=", SETTINGS_TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, - {"previewIsShowPivot=", SETTINGS_TYPE_BOOL, offsetof(Settings, previewIsShowPivot)}, - {"previewPanX=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewPanX)}, - {"previewPanY=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewPanY)}, - {"previewZoom=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewZoom)}, - {"previewGridSizeX=", SETTINGS_TYPE_INT, offsetof(Settings, previewGridSizeX)}, - {"previewGridSizeY=", SETTINGS_TYPE_INT, offsetof(Settings, previewGridSizeY)}, - {"previewGridOffsetX=", SETTINGS_TYPE_INT, offsetof(Settings, previewGridOffsetX)}, - {"previewGridOffsetY=", SETTINGS_TYPE_INT, offsetof(Settings, previewGridOffsetY)}, - {"previewGridColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewGridColorR)}, - {"previewGridColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewGridColorG)}, - {"previewGridColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewGridColorB)}, - {"previewGridColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewGridColorA)}, - {"previewAxisColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewAxisColorR)}, - {"previewAxisColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewAxisColorG)}, - {"previewAxisColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewAxisColorB)}, - {"previewAxisColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewAxisColorA)}, - {"previewBackgroundColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorR)}, - {"previewBackgroundColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorG)}, - {"previewBackgroundColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorB)}, - {"previewBackgroundColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, previewBackgroundColorA)}, - {"editorIsGrid=", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsGrid)}, - {"editorIsGridSnap=", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, - {"editorIsBorder=", SETTINGS_TYPE_BOOL, offsetof(Settings, editorIsBorder)}, - {"editorPanX=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorPanX)}, - {"editorPanY=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorPanY)}, - {"editorZoom=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorZoom)}, - {"editorGridSizeX=", SETTINGS_TYPE_INT, offsetof(Settings, editorGridSizeX)}, - {"editorGridSizeY=", SETTINGS_TYPE_INT, offsetof(Settings, editorGridSizeY)}, - {"editorGridOffsetX=", SETTINGS_TYPE_INT, offsetof(Settings, editorGridOffsetX)}, - {"editorGridOffsetY=", SETTINGS_TYPE_INT, offsetof(Settings, editorGridOffsetY)}, - {"editorGridColorR=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorGridColorR)}, - {"editorGridColorG=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorGridColorG)}, - {"editorGridColorB=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorGridColorB)}, - {"editorGridColorA=", SETTINGS_TYPE_FLOAT, offsetof(Settings, editorGridColorA)}, - {"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)}, - {"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)} + {"window", TYPE_IVEC2, offsetof(Settings, windowSize)}, + {"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, + {"previewIsAxis", TYPE_BOOL, offsetof(Settings, previewIsAxis)}, + {"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)}, + {"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, + {"previewIsShowPivot", TYPE_BOOL, offsetof(Settings, previewIsShowPivot)}, + {"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)}, + {"previewOverlayTransparency", TYPE_FLOAT, offsetof(Settings, previewOverlayTransparency)}, + {"previewZoom", TYPE_FLOAT, offsetof(Settings, previewZoom)}, + {"previewPan", TYPE_VEC2, offsetof(Settings, previewPan)}, + {"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)}, + {"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)}, + {"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)}, + {"previewAxisColor", TYPE_VEC4, offsetof(Settings, previewAxisColor)}, + {"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)}, + {"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)}, + {"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)}, + {"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)}, + {"editorZoom", TYPE_FLOAT, offsetof(Settings, editorZoom)}, + {"editorPan", TYPE_VEC2, offsetof(Settings, editorPan)}, + {"editorGridSize", TYPE_IVEC2, offsetof(Settings, editorGridSize)}, + {"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)}, + {"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)}, + {"editorBackgroundColor", TYPE_VEC4, offsetof(Settings, editorBackgroundColor)}, + {"tool", TYPE_INT, offsetof(Settings, tool)}, + {"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)}, }; constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); diff --git a/src/shader.cpp b/src/shader.cpp index 76de08b..e5af92b 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -2,20 +2,18 @@ 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, nullptr); - glCompileShader(*self); glGetShaderiv(*self, GL_COMPILE_STATUS, &isCompile); if (!isCompile) { - glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, nullptr, &compileLog[0]); - log_error(std::format(SHADER_INIT_ERROR, *self, compileLog)); + std::string compileLog(SHADER_INFO_LOG_MAX, '\0'); + glGetShaderInfoLog(*self, SHADER_INFO_LOG_MAX, nullptr, compileLog.data()); + log_error(std::format(SHADER_INIT_ERROR, *self, compileLog.c_str())); return false; } diff --git a/src/snapshots.cpp b/src/snapshots.cpp index 0ebf85c..7453dca 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -24,6 +24,7 @@ static void _snapshot_set(Snapshots* self, const Snapshot& snapshot) *self->anm2 = snapshot.anm2; *self->reference = snapshot.reference; self->preview->time = snapshot.time; + self->action = snapshot.action; } void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview) @@ -44,7 +45,7 @@ void snapshots_undo(Snapshots* self) Snapshot snapshot; if (_snapshot_stack_pop(&self->undoStack, &snapshot)) { - Snapshot current = {*self->anm2, *self->reference, self->preview->time}; + Snapshot current = {*self->anm2, *self->reference, self->preview->time, self->action}; _snapshot_stack_push(&self->redoStack, ¤t); _snapshot_set(self, snapshot); } @@ -55,7 +56,7 @@ void snapshots_redo(Snapshots* self) Snapshot snapshot; if (_snapshot_stack_pop(&self->redoStack, &snapshot)) { - Snapshot current = {*self->anm2, *self->reference, self->preview->time}; + Snapshot current = {*self->anm2, *self->reference, self->preview->time, self->action}; _snapshot_stack_push(&self->undoStack, ¤t); _snapshot_set(self, snapshot); } diff --git a/src/snapshots.h b/src/snapshots.h index 719bc33..a5d93e9 100644 --- a/src/snapshots.h +++ b/src/snapshots.h @@ -3,13 +3,15 @@ #include "anm2.h" #include "preview.h" -#define SNAPSHOT_STACK_MAX 100 +#define SNAPSHOT_STACK_MAX 1000 +#define SNAPSHOT_ACTION "Action" struct Snapshot { Anm2 anm2; Anm2Reference reference; f32 time = 0.0f; + std::string action = SNAPSHOT_ACTION; }; struct SnapshotStack @@ -23,6 +25,7 @@ struct Snapshots Anm2* anm2 = nullptr; Preview* preview = nullptr; Anm2Reference* reference = nullptr; + std::string action = SNAPSHOT_ACTION; SnapshotStack undoStack; SnapshotStack redoStack; }; diff --git a/src/state.cpp b/src/state.cpp index 42b14b0..4c5d976 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -2,14 +2,18 @@ static void _tick(State* self) { - SDL_GetWindowSize(self->window, &self->settings.windowW, &self->settings.windowH); - - editor_tick(&self->editor); preview_tick(&self->preview); - dialog_tick(&self->dialog); - imgui_tick(&self->imgui); +} - if (self->imgui.isQuit) self->isRunning = false; +static void _update(State* self) +{ + SDL_GetWindowSize(self->window, &self->settings.windowSize.x, &self->settings.windowSize.y); + + imgui_update(&self->imgui); + dialog_update(&self->dialog); + + if (self->imgui.isQuit) + self->isRunning = false; } static void _draw(State* self) @@ -35,11 +39,37 @@ void init(State* self) log_info(STATE_SDL_INIT_INFO); + // Todo, when sdl3 mixer is released officially + /* + if ((Mix_Init(STATE_MIX_FLAGS) & mixFlags) != mixFlags) + log_warning(std::format(STATE_MIX_INIT_WARNING, Mix_GetError())); + + if + ( + Mix_OpenAudioDevice + ( + STATE_MIX_SAMPLE_RATE, + STATE_MIX_FORMAT, + STATE_MIX_CHANNELS, + STATE_CHUNK_SIZE, + STATE_MIX_DEVICE, + STATE_MIX_ALLOWED_CHANGES + ) + < 0 + ) + { + log_warning(std::format(STATE_MIX_INIT_WARNING, Mix_GetError())); + Mix_Quit(); + } + else + log_info(STATE_MIX_INIT_INFO); + */ + SDL_CreateWindowAndRenderer ( WINDOW_TITLE, - self->settings.windowW, - self->settings.windowH, + self->settings.windowSize.x, + self->settings.windowSize.y, WINDOW_FLAGS, &self->window, &self->renderer @@ -63,9 +93,11 @@ void init(State* self) glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_DEPTH_TEST); glLineWidth(STATE_GL_LINE_WIDTH); - + glDisable(GL_MULTISAMPLE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LINE_SMOOTH); + resources_init(&self->resources); clipboard_init(&self->clipboard, &self->anm2); dialog_init(&self->dialog, &self->anm2, &self->reference, &self->resources, self->window); @@ -98,6 +130,7 @@ void init(State* self) anm2_new(&self->anm2); } + void loop(State* self) { self->tick = SDL_GetTicks(); @@ -112,22 +145,27 @@ void loop(State* self) _tick(self); self->lastTick = self->tick; - } + _update(self); _draw(self); } void quit(State* self) { imgui_free(); - settings_save(&self->settings); preview_free(&self->preview); editor_free(&self->editor); resources_free(&self->resources); + /* + Mix_CloseAudio(); + Mix_Quit(); + */ + SDL_GL_DestroyContext(self->glContext); SDL_Quit(); + settings_save(&self->settings); log_info(STATE_QUIT_INFO); } \ No newline at end of file diff --git a/src/state.h b/src/state.h index 2c002b6..521376e 100644 --- a/src/state.h +++ b/src/state.h @@ -5,11 +5,22 @@ #define STATE_INIT_INFO "Initializing..." #define STATE_SDL_INIT_ERROR "Failed to initialize SDL! {}" #define STATE_SDL_INIT_INFO "Initialized SDL" +#define STATE_MIX_INIT_WARNING "Unable to initialize SDL_mixer! {}" +#define STATE_MIX_AUDIO_DEVICE_INIT_WARNING "Unable to initialize audio device! {}" +#define STATE_MIX_INIT_INFO "Initialized SDL_mixer" #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 +#define STATE_MIX_FLAGS (MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_WAV) +#define STATE_MIX_SAMPLE_RATE 44100 +#define STATE_MIX_FORMAT MIX_DEFAULT_FORMAT +#define STATE_MIX_CHANNELS 2 +#define STATE_MIX_CHUNK_SIZE 1024 +#define STATE_MIX_DEVICE NULL +#define STATE_MIX_ALLOWED_CHANGES SDL_AUDIO_ALLOW_FORMAT_CHANGE + struct State { SDL_Window* window; @@ -25,11 +36,13 @@ struct State Settings settings; Snapshots snapshots; Clipboard clipboard; - bool isRunning = true; std::string argument{}; - u64 lastTick = 0; - u64 tick = 0; + std::string lastAction{}; + u64 lastTick{}; + u64 tick{}; + bool isRunning = true; + bool is_last_action() const { return !lastAction.empty(); } bool is_argument() const { return !argument.empty(); } }; diff --git a/src/texture.cpp b/src/texture.cpp index be32776..f928229 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -8,6 +8,18 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include + +static std::vector _texture_download(Texture* self) +{ + std::vector pixels(self->size.x * self->size.y * TEXTURE_CHANNELS); + + glBindTexture(GL_TEXTURE_2D, self->id); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + return pixels; +} + void texture_gl_set(Texture* self, void* data) { glGenTextures(1, &self->id); @@ -48,13 +60,37 @@ bool texture_from_data_init(Texture* self, const u8* data, u32 length) return true; } -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, ivec2 size) { - return (bool)stbi_write_png(path.c_str(), width, height, TEXTURE_CHANNELS, data, width * TEXTURE_CHANNELS); + log_info(std::format(TEXTURE_SAVE_INFO, path)); + return (bool)stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS); +} + +bool texture_from_gl_write(Texture* self, const std::string& path) +{ + return texture_from_data_write(path, _texture_download(self).data(), self->size); } void texture_free(Texture* self) { glDeleteTextures(1, &self->id); *self = Texture{}; -} \ No newline at end of file +} + +bool texture_pixel_set(Texture* self, ivec2 position, vec4 color) +{ + if + ( + position.x < 0 || position.y < 0 || + position.x >= self->size.x || position.y >= self->size.y + ) + return false; + + uint8_t rgba8[4] = {FLOAT_TO_U8(color.r), FLOAT_TO_U8(color.g), FLOAT_TO_U8(color.b), FLOAT_TO_U8(color.a)}; + + glBindTexture(GL_TEXTURE_2D, self->id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexSubImage2D(GL_TEXTURE_2D, 0,position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8); + + return true; +} diff --git a/src/texture.h b/src/texture.h index 40e51ce..a6a46b4 100644 --- a/src/texture.h +++ b/src/texture.h @@ -5,6 +5,7 @@ #define TEXTURE_CHANNELS 4 #define TEXTURE_INIT_INFO "Initialized texture from file: {}" #define TEXTURE_INIT_ERROR "Failed to initialize texture from file: {}" +#define TEXTURE_SAVE_INFO "Saved texture to: {}" struct Texture { @@ -18,4 +19,7 @@ void texture_gl_set(Texture* self, void* data); bool texture_from_path_init(Texture* self, const std::string& path); bool texture_from_data_init(Texture* self, const u8* data, u32 length); void texture_free(Texture* self); -bool texture_from_data_write(const std::string& path, const u8* data, s32 width, s32 height); \ No newline at end of file +std::vector texture_download(Texture* self); +bool texture_from_data_write(const std::string& path, const u8* data, ivec2 size); +bool texture_pixel_set(Texture* self, ivec2 position, vec4 color); +bool texture_from_gl_write(Texture* self, const std::string& path); \ No newline at end of file diff --git a/src/tool.h b/src/tool.h new file mode 100644 index 0000000..f4359e7 --- /dev/null +++ b/src/tool.h @@ -0,0 +1,52 @@ +#pragma once + +#include "COMMON.h" + +#define TOOL_STEP 1 +#define TOOL_STEP_MOD 10 + +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 SDL_SystemCursor MOUSE_CURSOR_DEFAULT = SDL_SYSTEM_CURSOR_DEFAULT; +const SDL_SystemCursor 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 +}; + +const bool TOOL_MOUSE_CURSOR_IS_CONSTANT[TOOL_COUNT] = +{ + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false +}; diff --git a/src/window.h b/src/window.h index f53dd95..91adb8b 100644 --- a/src/window.h +++ b/src/window.h @@ -4,8 +4,7 @@ #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 +void window_title_from_path_set(SDL_Window* self, const std::string& path); +bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color); \ No newline at end of file