From 9ad464a74a04dc614509f74e9b3a96a4429ae16f Mon Sep 17 00:00:00 2001 From: shweet Date: Mon, 8 Sep 2025 20:18:28 -0400 Subject: [PATCH] Onionskin, input rebinding, alt icons, settings refactor --- assets/atlas.png | Bin 1132 -> 1161 bytes src/COMMON.h | 34 ++-- src/PACKED.h | 198 +++++++++---------- src/anm2.cpp | 12 +- src/anm2.h | 12 +- src/canvas.h | 5 +- src/clipboard.cpp | 3 - src/imgui.cpp | 206 +++++++++++++++++--- src/imgui.h | 436 +++++++++++++++++++++++++++++++++--------- src/preview.cpp | 238 +++++++++++------------ src/preview.h | 2 + src/render.h | 9 +- src/settings.cpp | 182 ++++++++++-------- src/settings.h | 473 ++++++++++++++++++++-------------------------- 14 files changed, 1087 insertions(+), 723 deletions(-) diff --git a/assets/atlas.png b/assets/atlas.png index 0bceb708fa629fc6f87e2880447e318f947a57e4..79c21ff0298294415a2b38de297f332a6bf6d0db 100644 GIT binary patch delta 1133 zcmV-z1d{vg2#E=h7Yd*P0{{R3jvj#@ks%m=3{Xr|MF0Q*|NsC0|NmfMU|8ndWdHyG z0(4SNQvd*Vlg&5)00b3DL_t(|+U3)+Y8*!#$MG+-#4O`uuS=0?Nwv%!_JJF*HdY=X zb>6^vioumgVO%8rARL(dchN0LV-$h5G(qGUT#B;^0yp7u_>vi2w2^L9}EM3 z_u>9C^WPb+k9J%{StvR*03zttv;Y_n+M)kohH~1LT0AU7iP!*RLy1Pak^mw`#K%(R zCx^zk_(Nuiv$AL-jtXB*VFzUir3O&0Dj~0wWp~l6HqcRN8(~uP?7lx(xo^bgMUi2aixR@ZiC_nX$?ThzAV2QXV)t=`R2gYLONP zJeX1US*y32NY8Ckx5IoXA`~LsSmX?b#4X>2*M-yFr80WGbUBo|AGSy)(%0)n8=%It zLz(0EcCv0x1B7pL0S|xs`R(NW{4`g#tdtv|{d})JT^>*Vf?1{Q06Wv2>BJC!N>LdG zP}T}PLJreaC=o%owFB8B!r_ek?ZYy3 zHe9rlg)ITpqNnPLsw%q5^!Sp0O{u$$#Xo&&L_`dD)Ilu8y#pA)`cm6iio=&Ml-l=3 z$V=27i`QH%UR!C6CkJKK#4>Exi!&ZdjMvX#IG`6B{t>~!(30?vO{vEgh9)B3_-g?WHiz!I`Ur_aGLX`${sWD5%mXGHpcOvLLqkol>H zv2-ZiiHOr8qI)QQzu&@RIU`rSIR`(U&t1Ko_wY;8Q)ZpEoK8?KfRO`Bwm%2^>b0^| z70QlaR>}bs{s=`0=fHTqJ}c!AYMGIbED!)j%w}eO2wHk_1rXLCLUsQoD1kf&RM?e` zfH?)4cca1$5Z0g$uzS*fLx6B{%ZEi-0Co}(sYP2*t?AJn11N{~SHXDD7A-ab2DJDA zHUv$N7O?@eNEb$2BNGYGUIsu(6X58dK+M>qrRf8Vm&a$JSMgfM?aI-m#f|@ayG@^f z8uSqfn8LV+Y_;1nJ{usFxcIeMZ_-1Iatsib*n)*f^I^cp0!Gq*phR2}^i${4pj?%N zN;!Q4kc-?LZnJp$9O;v%dzJ3)|NqTvkv0j1NSB06q))yN77b~rThdT;Nr*pfJ?B(J zNXCf#Kq8+Ch)uxP7L;czpTsgIQRzE?JI}Y^t1kg~u?0`Q0HE4}?}^b|$(?UEv%LzN zRn=Aaj7?kulG}P%!Y0*iVJNL{4GvKDrU18st#ehkh3ng{ulrES4)bMrPWm$7(#L>m zn3CIi0f~#1ywrHo&j6EtIG~27mUx{1{+#~=;@FVE7~%Ar00000NkvXXu0mjf@mu)$tnfn0!_;Q))GR-;ZNG}3z-gR-3MrpB8N+V zBMB*U11SV#h9c{7dyvAC?UaPB9DtBG9Mpi*7MlRcVbR2-ve)e}Q@R3#5EGQF+Z84o z=^5VwUKZ^H94!jS=Gg%qy<-c~Iv~UWC_!Kl<&S3K9az;DMl1jei0&VuWX%XX=X?T< zUIA1!RUwrIZ3266t4sB;6kQj<+#1S%QlOs97yy(pe(>|nhrlnEl5u5=VcFQ)=b~|J zf$VD@0J({yy>N7@gFpv`d4t#lR3TQ#MO_iVr}s&dExDNlfY8 zR+H%8;zD3W*ym&w-JfT-As}H;(lck#WVa2;sJ4;->5_FMV2)bTvjp$V&%MRf6pRzIR>&>?&nt9k*_H&8t^_IxJ?$pF6 z#=Vv@_Ise#UA(ODIHU!fsr?0i{7rUJ#&56*7+wL1jI%pZcga|Xu>+XE%PZhi0MtZ_W>=eUUC=r}F@RF5#^v_@8rcyWLrP4=u*V|Nd?$7!$y`8KEz?#f0iFN>h%-hQ zpAWT7r&jIkiuE2gu=z~u{Vj%Ll0Gt~gG0c&V4&3U{Vf?I0CQ9;0r?g%B>NYXNoK)8 zdQc5xy(i?1RWTa+6o}(IspJ#D!|t}QC9$zCa_1P zPzad?U^EfOY1}oOfJyq=)fj+bNLH6@19iI%4|*yBjkpA|1V9&{84)-nYtn`@c|%22WX!G`*URgw0nRM6QErKCsg-i0kjQ&l #include +#include #include #include #include @@ -24,7 +25,7 @@ #include #include #include -#include +#include typedef uint8_t u8; typedef uint16_t u16; @@ -342,8 +343,6 @@ static inline void map_insert_shift(std::map& map, s32 index, const T& v map[insertIndex] = value; } - - #define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \ static inline std::string function(s32 index) \ { \ @@ -356,19 +355,30 @@ static inline void map_insert_shift(std::map& map, s32 index, const T& v return static_cast(string_to_enum(string, stringArray, count)); \ }; +#define DATATYPE_LIST \ + X(TYPE_INT, s32) \ + X(TYPE_BOOL, bool) \ + X(TYPE_FLOAT, f32) \ + X(TYPE_STRING, std::string) \ + X(TYPE_IVEC2, ivec2) \ + X(TYPE_IVEC2_WH, ivec2) \ + X(TYPE_VEC2, vec2) \ + X(TYPE_VEC2_WH, vec2) \ + X(TYPE_VEC3, vec3) \ + X(TYPE_VEC4, vec4) -enum DataType +enum DataType { - TYPE_INT, - TYPE_BOOL, - TYPE_FLOAT, - TYPE_STRING, - TYPE_IVEC2, - TYPE_VEC2, - TYPE_VEC3, - TYPE_VEC4 + #define X(symbol, ctype) symbol, + DATATYPE_LIST + #undef X }; +#define DATATYPE_TO_CTYPE(dt) DATATYPE_CTYPE_##dt +#define X(symbol, ctype) typedef ctype DATATYPE_CTYPE_##symbol; +DATATYPE_LIST +#undef X + enum OriginType { ORIGIN_TOP_LEFT, diff --git a/src/PACKED.h b/src/PACKED.h index debaf18..de07280 100644 --- a/src/PACKED.h +++ b/src/PACKED.h @@ -7,104 +7,106 @@ 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, 0x78, - 0x04, 0x03, 0x00, 0x00, 0x00, 0xff, 0x33, 0xea, 0xfd, 0x00, 0x00, 0x00, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xa0, + 0x02, 0x03, 0x00, 0x00, 0x00, 0x8e, 0x1e, 0x81, 0x1f, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, - 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c, - 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x60, 0x60, 0x60, 0xff, - 0xff, 0xff, 0x60, 0x60, 0x60, 0x15, 0x68, 0x14, 0xc2, 0x00, 0x00, 0x00, - 0x03, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x00, 0xfa, 0x76, 0xc4, 0xde, - 0x00, 0x00, 0x03, 0xf4, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x98, - 0x6d, 0x6e, 0xde, 0x38, 0x0c, 0x84, 0x27, 0x20, 0x0f, 0xb0, 0xc1, 0x5e, - 0x60, 0x81, 0x5c, 0x80, 0x9b, 0xe1, 0x01, 0x08, 0x88, 0xf7, 0x3f, 0xd3, - 0x42, 0x94, 0x04, 0xa9, 0xad, 0x83, 0xd8, 0x0b, 0x34, 0xed, 0x8f, 0x4e, - 0x90, 0xc8, 0xb4, 0xf9, 0x84, 0x5f, 0xd2, 0xeb, 0x20, 0xf8, 0x3f, 0x22, - 0x71, 0x29, 0x31, 0x94, 0x9a, 0x97, 0xd7, 0xe1, 0x4f, 0xd6, 0x12, 0x42, - 0xc6, 0xe1, 0x4f, 0xda, 0xf1, 0x0b, 0x99, 0x0e, 0x68, 0xdd, 0x07, 0x68, - 0x90, 0x22, 0x87, 0x4b, 0x23, 0x09, 0x29, 0x73, 0x03, 0x29, 0x04, 0x64, - 0x86, 0x22, 0xeb, 0x72, 0x7b, 0x90, 0xc2, 0x92, 0xed, 0x94, 0x84, 0xae, - 0x1c, 0x80, 0x90, 0x38, 0x1c, 0xd4, 0x80, 0xd3, 0x16, 0x9b, 0x00, 0xc9, - 0x61, 0xd1, 0xc4, 0xa4, 0xb2, 0xd7, 0xed, 0x30, 0x53, 0xba, 0x02, 0x84, - 0x10, 0x13, 0x94, 0xac, 0xdb, 0x15, 0x26, 0x1b, 0xe9, 0x19, 0xdf, 0x02, - 0x5e, 0x16, 0xed, 0x04, 0x1c, 0x2d, 0x0a, 0xc8, 0xe6, 0xd9, 0x01, 0x1d, - 0xbd, 0x63, 0x0b, 0xd2, 0x3a, 0x20, 0xc4, 0x01, 0x28, 0x41, 0x60, 0x10, - 0xe5, 0x8f, 0x66, 0xe2, 0x1d, 0x58, 0xd5, 0x17, 0x46, 0x2c, 0x00, 0x0b, - 0x88, 0xee, 0x1f, 0xa1, 0x64, 0xcd, 0x08, 0x3c, 0xe7, 0x7c, 0x02, 0x8d, - 0x5e, 0x01, 0x54, 0x35, 0x55, 0x21, 0x95, 0x06, 0x6d, 0x02, 0x7b, 0x70, - 0xab, 0x4b, 0xf5, 0xb0, 0x14, 0x5d, 0x17, 0xc0, 0xdc, 0x1a, 0xca, 0x52, - 0xa0, 0x9e, 0xcc, 0x18, 0x00, 0x94, 0x31, 0x7e, 0xf0, 0xf3, 0xcd, 0x87, - 0xc0, 0x2f, 0x96, 0x92, 0x71, 0x65, 0x8b, 0x61, 0xd9, 0xd8, 0xda, 0xe7, - 0xa2, 0x71, 0x6c, 0x81, 0xec, 0x6b, 0x1e, 0x00, 0xb9, 0x89, 0xa3, 0xbd, - 0x70, 0x74, 0xaa, 0x83, 0x40, 0x07, 0x85, 0x7c, 0x07, 0x80, 0x37, 0x66, - 0xd0, 0x8e, 0xd0, 0xcc, 0x4c, 0x12, 0xb0, 0xa2, 0x01, 0xc6, 0xe8, 0x9f, - 0x8b, 0xb5, 0x0a, 0xa0, 0x91, 0xe4, 0xf0, 0xef, 0x84, 0x32, 0xb3, 0x91, - 0xb1, 0x00, 0x25, 0x20, 0x65, 0x8b, 0xc9, 0x04, 0xa4, 0x00, 0xf1, 0xcc, - 0x66, 0xb5, 0xd0, 0x9b, 0x2d, 0x60, 0x9d, 0x13, 0x96, 0x80, 0x56, 0xcf, - 0xc5, 0xa0, 0xde, 0x9a, 0x7b, 0x0b, 0xf1, 0x6c, 0xcc, 0x13, 0x80, 0x10, - 0x07, 0x50, 0xcf, 0xc5, 0xc0, 0x10, 0x31, 0x53, 0x36, 0x4b, 0x7a, 0x8a, - 0x0f, 0x40, 0xb1, 0xce, 0xc9, 0x4a, 0x49, 0x4c, 0xe9, 0xdd, 0x56, 0x93, - 0xe8, 0xdf, 0xe2, 0x42, 0x60, 0x45, 0xd8, 0xe7, 0x64, 0x15, 0xdd, 0x9f, - 0x67, 0xb7, 0x21, 0x20, 0x61, 0x50, 0xd2, 0x33, 0x67, 0xd1, 0x64, 0xb7, - 0x0d, 0xc9, 0xa8, 0xb6, 0x92, 0x00, 0xe9, 0x91, 0xd5, 0x25, 0x47, 0x1d, - 0x4a, 0x56, 0x5f, 0xd1, 0x01, 0x69, 0x6e, 0x05, 0x75, 0x00, 0x62, 0xd3, - 0x8e, 0xb2, 0x6b, 0xb4, 0x5e, 0x83, 0xcb, 0x9c, 0x83, 0xab, 0x2c, 0x1a, - 0x91, 0xf4, 0xec, 0x40, 0xd9, 0xc2, 0x08, 0xba, 0x5e, 0x6d, 0x8d, 0xe8, - 0x55, 0x21, 0xcb, 0x0e, 0xe0, 0xf5, 0x9f, 0xb2, 0x31, 0xec, 0xeb, 0xcd, - 0xb7, 0x26, 0x9a, 0x79, 0xd8, 0x58, 0xf6, 0x73, 0x89, 0xed, 0xf5, 0x96, - 0xc8, 0xbd, 0xee, 0xd4, 0xc4, 0x28, 0xc6, 0xbd, 0x96, 0x32, 0xf6, 0x7b, - 0xa0, 0xd6, 0x5d, 0xbc, 0xac, 0xf0, 0x38, 0xa4, 0x05, 0x9c, 0xa9, 0xfd, - 0x05, 0xfc, 0x4d, 0x76, 0x52, 0xc6, 0x6f, 0xb0, 0x09, 0x18, 0x5e, 0x01, - 0x89, 0x8c, 0xb3, 0x3b, 0x52, 0x77, 0xc9, 0x58, 0x87, 0xb1, 0x03, 0x98, - 0xc0, 0xcb, 0x2b, 0xe0, 0x54, 0x00, 0xd6, 0x42, 0x5d, 0x00, 0xeb, 0x80, - 0x60, 0x7d, 0x70, 0x49, 0x8d, 0xc4, 0xce, 0x94, 0xa4, 0x8d, 0xc1, 0x3b, - 0xd0, 0xc4, 0xc4, 0xd0, 0x1c, 0x62, 0x52, 0xe1, 0x06, 0x50, 0x29, 0xed, - 0x08, 0x4e, 0x37, 0xa0, 0xdf, 0x33, 0x11, 0x2e, 0x09, 0x51, 0x40, 0x75, - 0xa1, 0xed, 0x94, 0xf0, 0x2a, 0x8d, 0x0d, 0xd7, 0x80, 0x81, 0x26, 0x45, - 0x9d, 0x29, 0x55, 0x80, 0xeb, 0x94, 0x28, 0x34, 0x61, 0x55, 0x6e, 0x05, - 0x88, 0x19, 0x50, 0x01, 0x7e, 0x2c, 0x5a, 0x69, 0x20, 0x19, 0x73, 0x2a, - 0xb6, 0xfa, 0x07, 0x91, 0x0a, 0x70, 0xd5, 0x56, 0x23, 0xb9, 0xc7, 0xb9, - 0x46, 0x4a, 0xab, 0x00, 0x57, 0x83, 0x33, 0xd2, 0x16, 0x60, 0x1b, 0x20, - 0x4a, 0x17, 0x5b, 0x63, 0x47, 0x38, 0x52, 0xd2, 0x66, 0x57, 0x9b, 0x6f, - 0x5d, 0xd0, 0x3a, 0x40, 0x31, 0x80, 0x52, 0xab, 0xc6, 0xe5, 0xf6, 0xfe, - 0x1a, 0xd9, 0x11, 0x1b, 0x43, 0x4a, 0x4c, 0xc5, 0xb0, 0xc7, 0x22, 0xd1, - 0x7c, 0x27, 0x76, 0x24, 0xc9, 0x5c, 0x40, 0x58, 0x2d, 0xd3, 0x56, 0x27, - 0x01, 0x9e, 0x00, 0x01, 0x88, 0x33, 0x46, 0x18, 0x9f, 0x0f, 0xb5, 0x4d, - 0xa7, 0x56, 0xed, 0xec, 0x8a, 0xf5, 0x1e, 0x36, 0xb0, 0xf9, 0x4c, 0xeb, - 0xfd, 0x2d, 0x86, 0x63, 0x92, 0x3e, 0x32, 0xa6, 0x83, 0x43, 0xc0, 0xba, - 0x60, 0xa0, 0xd4, 0xf2, 0xfd, 0x2d, 0x19, 0x23, 0x00, 0x73, 0x54, 0x2b, - 0x01, 0xf9, 0x16, 0x30, 0x24, 0xfb, 0x17, 0x94, 0x49, 0x66, 0xc1, 0x41, - 0x7a, 0x54, 0x00, 0xa8, 0x03, 0xb4, 0xfd, 0x17, 0x8c, 0x10, 0x03, 0x98, - 0xa9, 0xd6, 0xb4, 0x95, 0xb2, 0x02, 0x84, 0xb5, 0xd9, 0x19, 0xb1, 0xb9, - 0xcc, 0x46, 0x2a, 0x04, 0x1d, 0x18, 0x95, 0xda, 0x0a, 0x20, 0xe8, 0x3a, - 0x01, 0x00, 0x59, 0x2a, 0xc0, 0xd7, 0x3b, 0x51, 0x57, 0x00, 0x94, 0x26, - 0xb0, 0x47, 0xa5, 0x50, 0x08, 0x99, 0x05, 0x60, 0x34, 0x11, 0xc7, 0x69, - 0xdc, 0x35, 0x9c, 0x80, 0x49, 0xfa, 0xda, 0xd5, 0x18, 0x80, 0x61, 0x48, - 0x56, 0x97, 0x6c, 0x03, 0x75, 0xbb, 0x0d, 0x0f, 0x7a, 0x2a, 0x02, 0x8d, - 0xb8, 0x04, 0xb2, 0x04, 0x40, 0x17, 0xa0, 0x19, 0x11, 0x38, 0x24, 0x6b, - 0xd2, 0x86, 0x2f, 0x95, 0x1c, 0xf1, 0xd4, 0x6e, 0x01, 0x1b, 0x62, 0x00, - 0x2f, 0x9c, 0x86, 0x30, 0x84, 0x31, 0xba, 0xf1, 0x01, 0x20, 0x44, 0x07, - 0x68, 0x9f, 0x01, 0xfb, 0x73, 0x65, 0x00, 0xb4, 0x7b, 0x80, 0x10, 0x13, - 0xa0, 0x5d, 0x03, 0x27, 0x54, 0xf7, 0x63, 0x01, 0xb4, 0x0d, 0x94, 0xed, - 0x6b, 0x3d, 0x6a, 0x50, 0x92, 0x5c, 0x00, 0xe3, 0x63, 0xe0, 0xdc, 0x39, - 0xbe, 0x00, 0xff, 0x10, 0x38, 0x95, 0xb4, 0x09, 0x78, 0xec, 0x94, 0x32, - 0xe9, 0x99, 0x40, 0x96, 0xf6, 0x07, 0xf2, 0x6e, 0x6b, 0xf9, 0x7f, 0x54, - 0x74, 0x01, 0x67, 0x5b, 0x81, 0xf2, 0xbf, 0x05, 0x28, 0xb1, 0x9a, 0x75, - 0x0f, 0x90, 0xe1, 0x98, 0x81, 0xb5, 0xdb, 0x75, 0x5c, 0x67, 0x5e, 0x01, - 0x37, 0xb5, 0x8b, 0x7e, 0xac, 0xfc, 0x4e, 0x81, 0xef, 0x44, 0xbb, 0x06, - 0x56, 0x05, 0x9f, 0x02, 0x8d, 0x25, 0xff, 0x3a, 0x00, 0x7f, 0x80, 0xdf, - 0x12, 0x20, 0x69, 0x4f, 0x00, 0x76, 0xd9, 0x7d, 0x40, 0x08, 0x9a, 0xf0, - 0x01, 0x60, 0xa0, 0xbd, 0x3c, 0x00, 0x58, 0x45, 0x8b, 0xfd, 0x0c, 0x80, - 0x3f, 0xea, 0xdf, 0x2f, 0x05, 0xbe, 0xbe, 0xe8, 0xe7, 0x73, 0x78, 0x3e, - 0xe9, 0xe7, 0x7b, 0x69, 0x13, 0xf6, 0xe7, 0x4c, 0x3f, 0x06, 0x94, 0xa5, - 0xb8, 0x05, 0xec, 0x10, 0x8e, 0xfb, 0x80, 0x56, 0x00, 0xdc, 0x7e, 0x3f, - 0x54, 0x08, 0xc7, 0x13, 0x40, 0xc9, 0x78, 0x04, 0xa0, 0x39, 0x9e, 0x01, - 0xe5, 0x94, 0x53, 0x9f, 0xfd, 0x33, 0xf0, 0x3f, 0x53, 0x26, 0x87, 0x23, - 0x59, 0xac, 0x2b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, - 0xae, 0x42, 0x60, 0x82 + 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0c, 0x50, 0x4c, + 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60, + 0x60, 0x60, 0x58, 0xe6, 0xdc, 0x65, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, + 0x4e, 0x53, 0x00, 0x00, 0x76, 0x93, 0xcd, 0x38, 0x00, 0x00, 0x04, 0x15, + 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xe5, 0xd3, 0xb1, 0x6a, 0x1c, 0x47, + 0x1c, 0xc7, 0xf1, 0x2f, 0xb3, 0xc4, 0x2c, 0xe3, 0x63, 0xaf, 0x4b, 0x91, + 0x6a, 0x49, 0xb5, 0xcc, 0x1d, 0xf6, 0x81, 0x1b, 0xb1, 0x36, 0x56, 0x1e, + 0x21, 0x75, 0xde, 0xc0, 0x79, 0x8a, 0xc1, 0x95, 0x48, 0x61, 0x5c, 0x24, + 0xfd, 0x20, 0x1c, 0x98, 0xfc, 0x77, 0xd1, 0x2d, 0x49, 0x63, 0x14, 0x81, + 0xb6, 0x34, 0x41, 0xe4, 0x19, 0x5c, 0x8a, 0xb3, 0x09, 0x02, 0x37, 0xe1, + 0x72, 0xf8, 0x92, 0x19, 0x5d, 0xb4, 0x91, 0x6e, 0x94, 0x20, 0xab, 0x48, + 0xe1, 0x1f, 0x0c, 0x03, 0xf7, 0xe1, 0xfe, 0x33, 0xf3, 0xdf, 0x19, 0xae, + 0x8f, 0x76, 0x5c, 0x44, 0x59, 0x28, 0x3a, 0x34, 0x00, 0x22, 0xe8, 0xd6, + 0xb4, 0x00, 0x18, 0x0f, 0xda, 0xa1, 0xff, 0x60, 0x86, 0x72, 0xda, 0x96, + 0x5a, 0x3c, 0x2c, 0x44, 0x89, 0xd8, 0x00, 0x63, 0x43, 0x89, 0x46, 0xbb, + 0x92, 0x00, 0x22, 0x46, 0xc4, 0xc7, 0x52, 0xe6, 0x27, 0x87, 0xc6, 0xb8, + 0xf8, 0x43, 0x66, 0x89, 0xb3, 0xb2, 0x68, 0x23, 0x8e, 0x0a, 0x5f, 0x4d, + 0x61, 0x07, 0x65, 0x09, 0xa5, 0x06, 0x50, 0xae, 0x2a, 0x21, 0xaf, 0x94, + 0x65, 0x77, 0xd1, 0xac, 0x36, 0xd0, 0x51, 0x69, 0x1b, 0x61, 0x52, 0xf4, + 0xec, 0xbe, 0x3f, 0x5e, 0x51, 0xb7, 0xe8, 0xb7, 0x62, 0xa9, 0x0c, 0x11, + 0x6a, 0x0d, 0xd9, 0x7a, 0xdd, 0x73, 0x3a, 0x41, 0xf3, 0x06, 0xb4, 0xc7, + 0x05, 0xd0, 0x1a, 0x46, 0xeb, 0xd5, 0x49, 0xed, 0x74, 0xab, 0x37, 0x07, + 0x8f, 0x50, 0x34, 0xf0, 0xe0, 0xc1, 0xbb, 0x99, 0xb1, 0xca, 0x07, 0x88, + 0x07, 0x0c, 0xbb, 0x52, 0x1e, 0x38, 0x39, 0xe9, 0x2f, 0x00, 0x11, 0x6a, + 0x91, 0x16, 0x07, 0x3c, 0x98, 0x51, 0xf7, 0x59, 0xab, 0xb7, 0x9a, 0x48, + 0xcf, 0x6d, 0x53, 0xb7, 0xc3, 0x7c, 0x29, 0x22, 0x14, 0x22, 0xdd, 0x58, + 0xe4, 0x0c, 0x86, 0xc4, 0x2d, 0xdf, 0x85, 0xd7, 0x85, 0xd3, 0xdd, 0xa5, + 0x32, 0x7a, 0xbd, 0x74, 0x39, 0x94, 0xba, 0x1f, 0xb6, 0x48, 0x26, 0xd2, + 0xd7, 0xeb, 0x45, 0x1b, 0xa0, 0xc6, 0xb4, 0x43, 0x99, 0xe3, 0xf7, 0x76, + 0xb2, 0x6e, 0x4e, 0x03, 0x84, 0x6f, 0x73, 0x01, 0x0f, 0x7f, 0xfb, 0xf9, + 0xed, 0xe4, 0xfd, 0xfc, 0x34, 0x57, 0xb6, 0xac, 0x94, 0x1b, 0xa0, 0xfd, + 0x7c, 0xaf, 0x3e, 0x5d, 0x1e, 0x4f, 0xfe, 0x82, 0x59, 0xa5, 0xed, 0x00, + 0x3b, 0xd3, 0x9d, 0xe9, 0xc4, 0x10, 0x4a, 0x51, 0x19, 0x06, 0x50, 0xda, + 0xd9, 0xba, 0x59, 0xb7, 0x39, 0xda, 0xd5, 0x7e, 0x3c, 0x2c, 0x9e, 0x75, + 0x45, 0xaf, 0xe7, 0x6b, 0x97, 0x73, 0xef, 0xa5, 0x76, 0xbb, 0x03, 0xb0, + 0x68, 0x50, 0xf3, 0xa5, 0xbd, 0xcb, 0x8f, 0xcf, 0x0a, 0xb7, 0x3c, 0xde, + 0x6e, 0x49, 0x3b, 0xdd, 0x19, 0x8b, 0x8c, 0x12, 0x4d, 0xd4, 0x8e, 0x7a, + 0x7e, 0xc6, 0x7f, 0x47, 0xd9, 0x30, 0x92, 0xef, 0x23, 0x8c, 0x58, 0x4e, + 0x7d, 0x53, 0xb9, 0x30, 0x80, 0x15, 0x18, 0x4f, 0x1c, 0x22, 0x94, 0x10, + 0x07, 0x64, 0x2b, 0x62, 0x36, 0x17, 0xba, 0x54, 0xb2, 0x57, 0x42, 0x0e, + 0xd3, 0x55, 0x28, 0x11, 0x41, 0xb7, 0xb5, 0x03, 0xd9, 0x23, 0xc2, 0xe1, + 0x8c, 0xfc, 0xed, 0xc3, 0x32, 0x74, 0x36, 0x5c, 0xb4, 0x92, 0x85, 0x2d, + 0x01, 0xd4, 0xa2, 0xa7, 0xea, 0x8a, 0xaa, 0x2a, 0xba, 0xca, 0xf4, 0xf8, + 0x92, 0x4d, 0xa9, 0xbb, 0x8d, 0xc5, 0x3f, 0x7d, 0x6a, 0x44, 0x44, 0x0c, + 0x78, 0xd4, 0x41, 0x2c, 0xc5, 0xbd, 0x03, 0x18, 0xc0, 0xfa, 0x52, 0xdb, + 0x58, 0x8a, 0xc3, 0x97, 0x30, 0x94, 0xda, 0xf7, 0x46, 0xc8, 0x4b, 0xd4, + 0x1e, 0x8b, 0xd7, 0x5c, 0x2c, 0x5e, 0x5b, 0x69, 0x8d, 0x27, 0x07, 0x65, + 0xd5, 0xc4, 0x32, 0x6c, 0xd7, 0x8b, 0x33, 0x1e, 0x4a, 0x8c, 0xd7, 0xcf, + 0x60, 0x38, 0xa0, 0x17, 0x1b, 0xfe, 0x11, 0xc1, 0xc1, 0xd0, 0x92, 0xf0, + 0x8f, 0x4d, 0xa9, 0xc7, 0x16, 0x86, 0x26, 0x22, 0xde, 0xf8, 0x7d, 0xab, + 0xab, 0x7d, 0xfb, 0x28, 0xd1, 0xf6, 0x9b, 0xc4, 0x02, 0x28, 0x20, 0x73, + 0xc0, 0x08, 0x78, 0x04, 0xea, 0xd7, 0x4e, 0x9d, 0x83, 0x85, 0x79, 0x80, + 0x57, 0xc0, 0x12, 0xb2, 0x43, 0xa7, 0xcf, 0xc1, 0x31, 0x69, 0xa1, 0xee, + 0x3c, 0x64, 0x0b, 0x0b, 0x0b, 0x67, 0x44, 0xfa, 0x4c, 0xc4, 0xeb, 0x37, + 0x90, 0xf9, 0xa9, 0x87, 0xb1, 0x74, 0x28, 0xdd, 0x89, 0x88, 0xd3, 0x22, + 0xa2, 0x7b, 0x28, 0x7e, 0xbf, 0xdf, 0xc2, 0x63, 0x39, 0x23, 0x57, 0xbd, + 0x39, 0x07, 0x3f, 0x9e, 0xcf, 0x5d, 0xbd, 0x9c, 0xf7, 0xf0, 0x4b, 0xd3, + 0x53, 0x66, 0x9d, 0xb6, 0x9c, 0x4e, 0x50, 0x2e, 0x80, 0x91, 0x03, 0x4b, + 0xb6, 0x3f, 0x07, 0xfb, 0xea, 0xb5, 0xb2, 0x54, 0x15, 0xca, 0x8e, 0x60, + 0x56, 0xca, 0x01, 0x14, 0xfe, 0x08, 0x45, 0x09, 0xe7, 0xc0, 0x78, 0xbd, + 0x3e, 0x2b, 0xe5, 0x10, 0x6a, 0x99, 0x91, 0x8f, 0x2c, 0x10, 0x00, 0x46, + 0xcc, 0x66, 0x66, 0x7e, 0x08, 0x5a, 0x7a, 0x72, 0x05, 0x10, 0xd6, 0x20, + 0x42, 0x75, 0xff, 0x25, 0x28, 0x81, 0x3c, 0x07, 0x54, 0xd8, 0x95, 0x8d, + 0x80, 0x39, 0x05, 0x9a, 0x77, 0xa3, 0xc2, 0x0d, 0x10, 0xd6, 0xa0, 0x0e, + 0xb0, 0x7b, 0xd2, 0x43, 0x80, 0x70, 0x72, 0xcb, 0x87, 0x45, 0x59, 0x00, + 0x76, 0x12, 0x10, 0xa9, 0x45, 0x5b, 0x50, 0xad, 0xe9, 0xd1, 0x1d, 0x03, + 0x28, 0x87, 0xf6, 0x57, 0xc1, 0x78, 0xd0, 0x16, 0x2d, 0x36, 0x01, 0x06, + 0xb4, 0xf8, 0x01, 0x36, 0x04, 0x4d, 0x8f, 0x16, 0xb1, 0x01, 0xb4, 0x48, + 0x17, 0x46, 0x5c, 0x23, 0x13, 0x09, 0xd0, 0x5e, 0x06, 0x40, 0x49, 0x13, + 0xe0, 0xe8, 0x9f, 0x40, 0xcc, 0xd8, 0xa3, 0xa5, 0xe9, 0x03, 0x8c, 0x97, + 0xc7, 0x67, 0xa1, 0x57, 0xf1, 0x5a, 0xc7, 0xed, 0xca, 0xd1, 0xa5, 0xc5, + 0x8d, 0xff, 0x7b, 0xbb, 0x4d, 0x9f, 0x80, 0x1a, 0xf4, 0x11, 0x09, 0x98, + 0xc2, 0xb8, 0x87, 0x6c, 0xb5, 0xdb, 0x33, 0x3e, 0x1b, 0x20, 0x95, 0xb8, + 0xf8, 0xb5, 0x59, 0x6f, 0xd2, 0x43, 0x8c, 0x72, 0x0c, 0x10, 0x96, 0xd8, + 0x82, 0x85, 0x48, 0xf3, 0x61, 0xc0, 0xc7, 0x02, 0x46, 0xd2, 0xa0, 0x44, + 0x5c, 0x12, 0xf4, 0x53, 0xe7, 0xd3, 0xa0, 0x5c, 0x95, 0x84, 0x4a, 0x39, + 0x7d, 0x03, 0x90, 0x8b, 0xdc, 0x1c, 0x6e, 0xb3, 0x78, 0xfa, 0x1c, 0xe9, + 0x93, 0xa7, 0x7b, 0x95, 0xee, 0xee, 0xff, 0xff, 0xcd, 0x6b, 0x91, 0x36, + 0x09, 0x85, 0x48, 0x97, 0x84, 0x4c, 0xa4, 0x4f, 0xbe, 0x0f, 0x16, 0x0d, + 0x69, 0xa8, 0x5b, 0xd2, 0x50, 0x74, 0x49, 0x88, 0x3f, 0x6d, 0x3d, 0xe7, + 0x54, 0x44, 0x48, 0xc6, 0x88, 0xfc, 0x40, 0x22, 0x9f, 0x0a, 0x88, 0x4d, + 0xc0, 0xd7, 0x16, 0x94, 0x67, 0x2b, 0x9f, 0xc4, 0x32, 0x26, 0x51, 0xe9, + 0x3b, 0x80, 0x3b, 0xcf, 0xb7, 0xe0, 0xab, 0x2f, 0x01, 0x78, 0xb1, 0x05, + 0x4f, 0xbe, 0x00, 0xa0, 0xda, 0x82, 0xef, 0x89, 0xd1, 0x5c, 0xc9, 0x9d, + 0x6f, 0x37, 0xb3, 0xbd, 0x0a, 0x9b, 0x55, 0xd5, 0x55, 0xf8, 0x8c, 0x4d, + 0x5c, 0x02, 0x92, 0xdb, 0x7a, 0xc2, 0x26, 0xd5, 0x6d, 0x61, 0x28, 0xad, + 0x6f, 0x0d, 0x0e, 0x50, 0xf6, 0xa6, 0x00, 0xb7, 0x82, 0xad, 0x73, 0x55, + 0xb7, 0x85, 0xeb, 0xdb, 0xae, 0xaf, 0xfb, 0x50, 0xca, 0x0e, 0xf3, 0x65, + 0x78, 0x4e, 0xfa, 0x32, 0xe0, 0xd2, 0xc7, 0x80, 0x6a, 0x98, 0x92, 0xdb, + 0x7a, 0x01, 0x89, 0x8b, 0x95, 0xbc, 0xd4, 0x78, 0xd2, 0xcf, 0x00, 0x93, + 0x7e, 0x38, 0xa0, 0x86, 0xa7, 0x96, 0x78, 0x9c, 0xff, 0xfe, 0x9c, 0xff, + 0x04, 0xe2, 0xd8, 0x90, 0xc1, 0x18, 0xe1, 0xf4, 0x9b, 0x00, 0x00, 0x00, + 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; const u32 TEXTURE_ATLAS_LENGTH = (u32)std::size(TEXTURE_ATLAS); -const vec2 TEXTURE_ATLAS_SIZE = {96, 120}; +const vec2 TEXTURE_ATLAS_SIZE = {96, 160}; enum AtlasType { @@ -118,8 +120,8 @@ enum AtlasType ATLAS_INVISIBLE, ATLAS_SHOW_RECT, ATLAS_HIDE_RECT, - ATLAS_SHOW_TARGETS, - ATLAS_HIDE_TARGETS, + ATLAS_PLACEHOLDER, + ATLAS_PLACEHOLDER2, ATLAS_PAN, ATLAS_MOVE, ATLAS_ROTATE, @@ -145,6 +147,7 @@ enum AtlasType ATLAS_FRAME, ATLAS_FRAME_ALT, ATLAS_TARGET, + ATLAS_TARGET_ALT, ATLAS_COUNT }; @@ -197,7 +200,8 @@ const inline AtlasEntry ATLAS_ENTRIES[ATLAS_COUNT] = {{ 0, 80}, ATLAS_SIZE_OBLONG}, {{16, 80}, ATLAS_SIZE_OBLONG}, {{32, 80}, ATLAS_SIZE_OBLONG}, - {{48, 80}, ATLAS_SIZE_BIG} + {{48, 80}, ATLAS_SIZE_BIG}, + {{48, 120}, ATLAS_SIZE_BIG} }; #define ATLAS_POSITION(type) ATLAS_ENTRIES[type].position diff --git a/src/anm2.cpp b/src/anm2.cpp index a1669de..b8699b6 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -54,8 +54,6 @@ bool anm2_serialize(Anm2* self, const std::string& path) for (auto& [id, spritesheet] : self->spritesheets) { - if (id == ID_NONE) continue; - XMLElement* spritesheetElement; // Spritesheet @@ -72,8 +70,6 @@ bool anm2_serialize(Anm2* self, const std::string& path) for (auto& [id, layer] : self->layers) { - if (id == ID_NONE) continue; - XMLElement* layerElement; // Layer @@ -92,8 +88,6 @@ bool anm2_serialize(Anm2* self, const std::string& path) for (auto& [id, null] : self->nulls) { - if (id == ID_NONE) continue; - XMLElement* nullElement; // Null @@ -101,9 +95,9 @@ bool anm2_serialize(Anm2* self, const std::string& path) nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_NAME], null.name.c_str()); // Name nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_ID], id); // ID - // special case; only serialize if this is true if (null.isShowRect) nullElement->SetAttribute(ANM2_ATTRIBUTE_STRINGS[ANM2_ATTRIBUTE_SHOW_RECT], null.isShowRect); // ShowRect + nullsElement->InsertEndChild(nullElement); } @@ -114,8 +108,6 @@ bool anm2_serialize(Anm2* self, const std::string& path) for (auto& [id, event] : self->events) { - if (id == ID_NONE) continue; - XMLElement* eventElement; // Event @@ -137,8 +129,6 @@ bool anm2_serialize(Anm2* self, const std::string& path) for (auto& [id, animation] : self->animations) { - if (id == ID_NONE) continue; - XMLElement* animationElement; XMLElement* rootAnimationElement; XMLElement* layerAnimationsElement; diff --git a/src/anm2.h b/src/anm2.h index 16819f1..22cc17f 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -45,14 +45,16 @@ X(TRIGGERS, "Triggers") \ X(TRIGGER, "Trigger") -typedef enum { +typedef enum +{ #define X(name, str) ANM2_ELEMENT_##name, ANM2_ELEMENT_LIST #undef X ANM2_ELEMENT_COUNT } Anm2Element; -static const char* ANM2_ELEMENT_STRINGS[] = { +const inline char* ANM2_ELEMENT_STRINGS[] = +{ #define X(name, str) str, ANM2_ELEMENT_LIST #undef X @@ -259,6 +261,12 @@ enum Anm2ChangeType ANM2_CHANGE_SET }; +enum OnionskinDrawOrder +{ + ONIONSKIN_BELOW, + ONIONSKIN_ABOVE +}; + void anm2_layer_add(Anm2* self); void anm2_layer_remove(Anm2* self, s32 id); void anm2_null_add(Anm2* self); diff --git a/src/canvas.h b/src/canvas.h index 0fc78a1..e104288 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -5,13 +5,12 @@ #define CANVAS_ZOOM_MIN 1.0f #define CANVAS_ZOOM_MAX 2000.0f #define CANVAS_ZOOM_DEFAULT 100.0f -#define CANVAS_ZOOM_STEP 10.0f -#define CANVAS_ZOOM_MOD 10.0f +#define CANVAS_ZOOM_STEP 100.0f #define CANVAS_GRID_MIN 1 #define CANVAS_GRID_MAX 1000 #define CANVAS_GRID_DEFAULT 32 -const inline vec2 CANVAS_PIVOT_SIZE = {8, 8}; +const inline vec2 CANVAS_PIVOT_SIZE = {4, 4}; const inline vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f}; const inline f32 CANVAS_AXIS_VERTICES[] = {-1.0f, 1.0f}; diff --git a/src/clipboard.cpp b/src/clipboard.cpp index 973145c..5214103 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -7,7 +7,6 @@ static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2) case CLIPBOARD_FRAME: { Anm2FrameWithReference* frameWithReference = std::get_if(&self->data); - if (!frameWithReference) break; anm2_frame_erase(anm2, &frameWithReference->reference); @@ -16,7 +15,6 @@ static void _clipboard_item_remove(ClipboardItem* self, Anm2* anm2) case CLIPBOARD_ANIMATION: { Anm2AnimationWithID* animationWithID = std::get_if(&self->data); - if (!animationWithID) break; for (auto & [id, animation] : anm2->animations) @@ -58,7 +56,6 @@ static void _clipboard_item_paste(ClipboardItem* self, ClipboardLocation* locati case CLIPBOARD_ANIMATION: { Anm2AnimationWithID* animationWithID = std::get_if(&self->data); - if (!animationWithID) break; s32 index = 0; diff --git a/src/imgui.cpp b/src/imgui.cpp index 96724b8..cef28c4 100644 --- a/src/imgui.cpp +++ b/src/imgui.cpp @@ -1,5 +1,15 @@ #include "imgui.h" +static bool _imgui_chord_pressed(ImGuiKeyChord chord) +{ + if (chord == IMGUI_CHORD_NONE) return false; + ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); + if (key == ImGuiKey_None) return false; + if (key < ImGuiKey_NamedKey_BEGIN || key >= ImGuiKey_NamedKey_END) return false; + + return ImGui::IsKeyChordPressed(chord); +} + static const char* _imgui_f32_format_get(const ImguiItem& item, f32& value) { if (item.isEmptyFormat) return ""; @@ -38,7 +48,6 @@ static void _imgui_anm2_open(Imgui* self, const std::string& path) if (anm2_deserialize(self->anm2, path)) { window_title_from_path_set(self->window, path); - snapshots_reset(self->snapshots); imgui_log_push(self, std::format(IMGUI_LOG_FILE_OPEN_FORMAT, path)); } else @@ -152,6 +161,7 @@ static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) break; case IMGUI_INPUT_INT: case IMGUI_INPUT_TEXT: + case IMGUI_INPUT_FLOAT: case IMGUI_DRAG_FLOAT: case IMGUI_SLIDER_FLOAT: case IMGUI_COLOR_EDIT: @@ -178,6 +188,16 @@ static void _imgui_item_pre(const ImguiItem& self, ImguiItemType type) default: break; } + + /* + if (self.is_hotkey()) + { + std::string chordString = imgui_string_from_chord_get(imgui->hotkeys[self.hotkey]); + if (isShortcutInLabel) + label += std::format(IMGUI_LABEL_SHORTCUT_FORMAT, chordString); + tooltip += std::format(IMGUI_TOOLTIP_SHORTCUT_FORMAT, chordString); + } + */ } static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType type, bool& isActivated) @@ -201,7 +221,7 @@ static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType ImU32 color = ImGui::GetColorU32(ImGuiCol_Text); ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, color, 1.0f); - if (ImGui::IsKeyChordPressed(ImGuiMod_Alt | self.mnemonicKey)) + if (_imgui_chord_pressed(ImGuiMod_Alt | self.mnemonicKey)) { if (!self.isDisabled) isActivated = true; imgui_close_current_popup(imgui); @@ -210,20 +230,20 @@ static void _imgui_item_post(const ImguiItem& self, Imgui* imgui, ImguiItemType if (self.isUseItemActivated && !self.isDisabled) isActivated = ImGui::IsItemActivated(); - if (imgui->isContextualActionsEnabled && (self.is_chord() && ImGui::IsKeyChordPressed(self.chord))) - if (self.is_focus_window() && (imgui_window_get() == self.focusWindow)) - if (!self.isDisabled) isActivated = true; + if + ( + imgui->isContextualActionsEnabled && _imgui_chord_pressed(self.chord_get()) && + self.is_focus_window() && (imgui_window_get() == self.focusWindow) + ) + if (!self.isDisabled) isActivated = true; if (self.is_tooltip() && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) ImGui::SetTooltip(self.tooltip.c_str()); if (isActivated) { - if (self.is_undoable()) - imgui_snapshot(imgui, self.snapshotAction); - - if (self.function) - self.function(imgui); + if (self.is_undoable()) imgui_snapshot(imgui, self.snapshotAction); + if (self.is_function()) self.function(imgui); if (self.is_popup()) { @@ -372,6 +392,8 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, bool& boolValue) ImguiItem checkboxItem = self.copy \ ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ checkboxItem.isDisabled = false; \ + checkboxItem.isSeparator = false; \ + checkboxItem.value = 0; \ bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ ImGui::SameLine(); \ bool isActivated = ([&] { return FUNCTION; })(); \ @@ -386,6 +408,18 @@ static bool NAME(const ImguiItem& self, Imgui* imgui, VALUE& value, bool& boolVa ImguiItem checkboxItem = self.copy \ ({.label = std::format(IMGUI_INVISIBLE_FORMAT, self.label), .isMnemonicDisabled = true}); \ checkboxItem.isDisabled = false; \ + checkboxItem.isSeparator = false; \ + checkboxItem.value = 0; \ + bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ + ImGui::SameLine(); \ + bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ + if (isCheckboxActivated) isActivated = true; \ + return isActivated; \ +} + +#define IMGUI_ITEM_AND_CHECKBOX_FUNCTION(NAME, VALUE, FUNCTION) \ +static bool NAME(const ImguiItem& self, const ImguiItem& checkboxItem, Imgui* imgui, VALUE& value, bool& boolValue) \ +{ \ bool isCheckboxActivated = _imgui_checkbox(checkboxItem, imgui, boolValue); \ ImGui::SameLine(); \ bool isActivated = ([&](VALUE& value) { return FUNCTION; })(value); \ @@ -403,6 +437,12 @@ IMGUI_ITEM_FUNCTION(_imgui_begin_child, IMGUI_CHILD, ImGui::BeginChild(self.labe static void _imgui_end_child(void) {ImGui::EndChild(); } IMGUI_ITEM_VOID_FUNCTION(_imgui_text, IMGUI_TEXT, ImGui::Text(self.label_get())); IMGUI_ITEM_FUNCTION(_imgui_button, IMGUI_BUTTON, ImGui::Button(self.label_get(), _imgui_item_size_get(self, type))); +IMGUI_ITEM_FUNCTION(_imgui_begin_table, IMGUI_TABLE, ImGui::BeginTable(self.label_get(), self.value, self.flags)); +static void _imgui_end_table(void) {ImGui::EndTable(); } +static void _imgui_table_setup_column(const char* text) {ImGui::TableSetupColumn(text); } +static void _imgui_table_headers_row(void) {ImGui::TableHeadersRow(); } +static void _imgui_table_next_row(void) {ImGui::TableNextRow(); } +static void _imgui_table_set_column_index(s32 index) {ImGui::TableSetColumnIndex(index); } IMGUI_ITEM_FUNCTION(_imgui_selectable, IMGUI_SELECTABLE, ImGui::Selectable(self.label_get(), self.isSelected, self.flags, _imgui_item_size_get(self, type))); IMGUI_ITEM_VALUE_FUNCTION(_imgui_radio_button, IMGUI_RADIO_BUTTON, s32, ImGui::RadioButton(self.label_get(), &value, self.value)); IMGUI_ITEM_VALUE_FUNCTION(_imgui_color_button, IMGUI_COLOR_BUTTON, vec4, ImGui::ColorButton(self.label_get(), ImVec4(value), self.flags)); @@ -1264,6 +1304,40 @@ static void _imgui_timeline(Imgui* self) _imgui_end(); // IMGUI_TIMELINE } +static void _imgui_onionskin(Imgui* self) +{ + IMGUI_BEGIN_OR_RETURN(IMGUI_ONIONSKIN, self); + + static auto& isEnabled = self->settings->onionskinIsEnabled; + static auto& beforeCount = self->settings->onionskinBeforeCount; + static auto& afterCount = self->settings->onionskinAfterCount; + static auto& beforeColorOffset = self->settings->onionskinBeforeColorOffset; + static auto& afterColorOffset = self->settings->onionskinAfterColorOffset; + static auto& drawOrder = self->settings->onionskinDrawOrder; + + _imgui_checkbox(IMGUI_ONIONSKIN_ENABLED, self, isEnabled); + + auto onionskin_section = [&](auto& text, auto& count, auto& colorOffset) + { + ImGui::PushID(text.label.c_str()); + _imgui_text(text, self); + _imgui_input_int(IMGUI_ONIONSKIN_COUNT.copy({!isEnabled}), self, count); + _imgui_color_edit3(IMGUI_ONIONSKIN_COLOR_OFFSET.copy({!isEnabled}), self, colorOffset); + ImGui::PopID(); + }; + + onionskin_section(IMGUI_ONIONSKIN_BEFORE, beforeCount, beforeColorOffset); + onionskin_section(IMGUI_ONIONSKIN_AFTER, afterCount, afterColorOffset); + + ImGui::Separator(); + + _imgui_text(IMGUI_ONIONSKIN_DRAW_ORDER, self); + _imgui_radio_button(IMGUI_ONIONSKIN_BELOW.copy({!isEnabled}), self, drawOrder); + _imgui_radio_button(IMGUI_ONIONSKIN_ABOVE.copy({!isEnabled}), self, drawOrder); + + _imgui_end(); // IMGUI_ONIONSKIN +} + static void _imgui_taskbar(Imgui* self) { static ImguiPopupState exitConfirmState = IMGUI_POPUP_STATE_CLOSED; @@ -1277,15 +1351,15 @@ static void _imgui_taskbar(Imgui* self) Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); - _imgui_selectable(IMGUI_FILE.copy({}), self); + _imgui_selectable(IMGUI_FILE, self); if (imgui_begin_popup(IMGUI_FILE.popup, self)) { - _imgui_selectable(IMGUI_NEW, self); + _imgui_selectable(IMGUI_NEW.copy({self->anm2->path.empty()}), self); _imgui_selectable(IMGUI_OPEN, self); - _imgui_selectable(IMGUI_SAVE, self); - _imgui_selectable(IMGUI_SAVE_AS, self); - _imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION, self); + _imgui_selectable(IMGUI_SAVE.copy({self->anm2->path.empty()}), self); + _imgui_selectable(IMGUI_SAVE_AS.copy({self->anm2->path.empty()}), self); + _imgui_selectable(IMGUI_EXPLORE_ANM2_LOCATION.copy({self->anm2->path.empty()}), self); _imgui_selectable(IMGUI_EXIT, self); imgui_end_popup(self); } @@ -1440,6 +1514,7 @@ static void _imgui_taskbar(Imgui* self) _imgui_checkbox_color_edit4(IMGUI_FRAME_PROPERTIES_TINT.copy({!isTint}), self, tint, isTint); _imgui_checkbox_color_edit3(IMGUI_FRAME_PROPERTIES_COLOR_OFFSET.copy({!isColorOffset}), self, colorOffset, isColorOffset); _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_VISIBLE.copy({!isVisibleSet}), self, isVisible, isVisibleSet); + ImGui::NewLine(); _imgui_checkbox_checkbox(IMGUI_FRAME_PROPERTIES_INTERPOLATED.copy({!isInterpolatedSet}), self, isInterpolated, isInterpolatedSet); _imgui_end_child(); // IMGUI_FOOTER_CHILD @@ -1658,9 +1733,75 @@ static void _imgui_taskbar(Imgui* self) if (imgui_begin_popup(IMGUI_SETTINGS.popup, self, IMGUI_SETTINGS.popupSize)) { if (_imgui_checkbox_selectable(IMGUI_VSYNC, self, self->settings->isVsync)) window_vsync_set(self->settings->isVsync); + _imgui_selectable(IMGUI_HOTKEYS, self); if (_imgui_selectable(IMGUI_DEFAULT_SETTINGS, self)) *self->settings = Settings(); imgui_end_popup(self); } + + if (imgui_begin_popup_modal(IMGUI_HOTKEYS.popup, self, IMGUI_HOTKEYS.popupSize)) + { + _imgui_begin_child(IMGUI_HOTKEYS_CHILD, self); + + if (_imgui_begin_table(IMGUI_HOTKEYS_TABLE, self)) + { + static s32 selectedIndex = INDEX_NONE; + + _imgui_table_setup_column(IMGUI_HOTKEYS_FUNCTION); + _imgui_table_setup_column(IMGUI_HOTKEYS_HOTKEY); + _imgui_table_headers_row(); + + for (s32 i = 0; i < HOTKEY_COUNT; i++) + { + if (!SETTINGS_HOTKEY_MEMBERS[i]) continue; + + bool isSelected = selectedIndex == i; + const char* string = HOTKEY_STRINGS[i]; + std::string* settingString = &(self->settings->*SETTINGS_HOTKEY_MEMBERS[i]); + std::string chordString = isSelected ? IMGUI_HOTKEY_CHANGE : *settingString; + + ImGui::PushID(i); + _imgui_table_next_row(); + _imgui_table_set_column_index(0); + ImGui::TextUnformatted(string); + _imgui_table_set_column_index(1); + + if (ImGui::Selectable(chordString.c_str(), isSelected)) selectedIndex = i; + ImGui::PopID(); + + if (isSelected) + { + ImGuiKeyChord chord = IMGUI_CHORD_NONE; + + if (ImGui::IsKeyDown(ImGuiMod_Ctrl)) chord |= ImGuiMod_Ctrl; + if (ImGui::IsKeyDown(ImGuiMod_Shift)) chord |= ImGuiMod_Shift; + if (ImGui::IsKeyDown(ImGuiMod_Alt)) chord |= ImGuiMod_Alt; + if (ImGui::IsKeyDown(ImGuiMod_Super)) chord |= ImGuiMod_Super; + + for (auto& [_, key] : IMGUI_KEY_MAP) + { + if (ImGui::IsKeyPressed(key)) + { + chord |= key; + imgui_hotkey_chord_registry()[i] = chord; + *settingString = imgui_string_from_chord_get(chord); + selectedIndex = INDEX_NONE; + break; + } + } + } + } + + _imgui_end_table(); + } + + _imgui_end_child(); // IMGUI_HOTKEYS_CHILD; + + _imgui_begin_child(IMGUI_HOTKEYS_OPTIONS_CHILD, self); + if (_imgui_button(IMGUI_HOTKEYS_CONFIRM, self)) imgui_close_current_popup(self); + _imgui_end_child(); // IMGUI_HOTKEYS_OPTIONS_CHILD + + imgui_end_popup(self); + } _imgui_end(); } @@ -2156,7 +2297,7 @@ static void _imgui_animation_preview(Imgui* self) _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); - if (_imgui_button(IMGUI_CANVAS_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); + if (_imgui_button(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); _imgui_end_child(); //IMGUI_CANVAS_VIEW_CHILD @@ -2194,6 +2335,8 @@ static void _imgui_animation_preview(Imgui* self) _imgui_checkbox(IMGUI_CANVAS_AXES, self, self->settings->previewIsAxes); ImGui::SameLine(); _imgui_color_edit4(IMGUI_CANVAS_AXES_COLOR, self, self->settings->previewAxesColor); + ImGui::SameLine(); + _imgui_checkbox(IMGUI_CANVAS_ALT_ICONS, self, self->settings->previewIsAltIcons); _imgui_checkbox(IMGUI_CANVAS_ROOT_TRANSFORM, self, self->settings->previewIsRootTransform); ImGui::SameLine(); _imgui_checkbox(IMGUI_CANVAS_TRIGGERS, self, self->settings->previewIsTriggers); @@ -2241,8 +2384,8 @@ static void _imgui_animation_preview(Imgui* self) const bool isUp = ImGui::IsKeyPressed(IMGUI_INPUT_UP); const bool isDown = ImGui::IsKeyPressed(IMGUI_INPUT_DOWN); const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); - const bool isZoomIn = ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN); - const bool isZoomOut = ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT); + const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); + const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); @@ -2306,7 +2449,6 @@ static void _imgui_animation_preview(Imgui* self) if (mouseWheel != 0 || isZoomIn || isZoomOut) { f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - delta = isMod ? delta * CANVAS_ZOOM_MOD : delta; zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } } @@ -2340,7 +2482,7 @@ static void _imgui_spritesheet_editor(Imgui* self) _imgui_begin_child(IMGUI_CANVAS_VIEW_CHILD, self); _imgui_drag_float(IMGUI_CANVAS_ZOOM, self, zoom); - if (_imgui_button(IMGUI_CANVAS_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); + if (_imgui_button(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW.copy({pan == vec2()}), self)) pan = vec2(); ImGui::Text(mousePositionString.c_str()); _imgui_end_child(); // IMGUI_CANVAS_VIEW_CHILD @@ -2371,9 +2513,8 @@ static void _imgui_spritesheet_editor(Imgui* self) const bool isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left); const bool isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left); const bool isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle); - const bool isZoomIn = ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_IN); - const bool isZoomOut = ImGui::IsKeyPressed(IMGUI_INPUT_ZOOM_OUT); - const bool isMod = ImGui::IsKeyDown(IMGUI_INPUT_SHIFT); + const bool isZoomIn = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_IN]); + const bool isZoomOut = _imgui_chord_pressed(imgui_hotkey_chord_registry()[HOTKEY_ZOOM_OUT]); const f32 mouseWheel = ImGui::GetIO().MouseWheel; const ImVec2 mouseDelta = ImGui::GetIO().MouseDelta; @@ -2442,7 +2583,6 @@ static void _imgui_spritesheet_editor(Imgui* self) if (mouseWheel != 0 || isZoomIn || isZoomOut) { f32 delta = (mouseWheel > 0 || isZoomIn) ? CANVAS_ZOOM_STEP : -CANVAS_ZOOM_STEP; - delta = isMod ? delta * CANVAS_ZOOM_MOD : delta; zoom = std::clamp(ROUND_NEAREST_MULTIPLE(zoom + delta, CANVAS_ZOOM_STEP), CANVAS_ZOOM_MIN, CANVAS_ZOOM_MAX); } } @@ -2538,7 +2678,6 @@ static void _imgui_log(Imgui* self) } } - static void _imgui_dock(Imgui* self) { ImguiItem window = IMGUI_WINDOW_MAIN; @@ -2558,6 +2697,7 @@ static void _imgui_dock(Imgui* self) _imgui_animation_preview(self); _imgui_spritesheet_editor(self); _imgui_timeline(self); + _imgui_onionskin(self); _imgui_frame_properties(self); _imgui_end(); // IMGUI_WINDOW_MAIN @@ -2608,6 +2748,12 @@ void imgui_init io.ConfigWindowsMoveFromTitleBarOnly = true; ImGui::LoadIniSettingsFromDisk(settings_path_get().c_str()); + + for (s32 i = 0; i < HOTKEY_COUNT; i++) + { + if (!SETTINGS_HOTKEY_MEMBERS[i]) continue; + imgui_hotkey_chord_registry()[i] = imgui_chord_from_string_get(*&(self->settings->*SETTINGS_HOTKEY_MEMBERS[i])); + } } void imgui_update(Imgui* self) @@ -2622,13 +2768,13 @@ void imgui_update(Imgui* self) if (self->isContextualActionsEnabled) { - for (const auto& hotkey : imgui_hotkey_registry()) + for (const auto& item : imgui_item_registry()) { - if (ImGui::IsKeyChordPressed(hotkey.chord)) + if (item->is_chord() && _imgui_chord_pressed(item->chord_get())) { - if (hotkey.is_undoable()) imgui_snapshot(self, hotkey.snapshotAction); - if (hotkey.is_focus_window()) continue; - hotkey.function(self); + if (item->is_undoable()) imgui_snapshot(self, item->snapshotAction); + if (item->is_focus_window()) continue; + if (item->is_function()) item->function(self); } } } diff --git a/src/imgui.h b/src/imgui.h index 74fab2c..004e9c6 100644 --- a/src/imgui.h +++ b/src/imgui.h @@ -52,14 +52,15 @@ #define IMGUI_FRAME_BORDER 2.0f #define IMGUI_LOG_DURATION 3.0f #define IMGUI_LOG_PADDING 10.0f -#define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(255, 255, 255, 255) -#define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(255, 255, 255, 128) +#define IMGUI_PLAYHEAD_LINE_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, UCHAR_MAX) +#define IMGUI_TRIGGERS_EVENT_COLOR IM_COL32(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX, 128) #define IMGUI_PLAYHEAD_LINE_WIDTH 2.0f #define IMGUI_SPRITESHEETS_FOOTER_HEIGHT 65 #define IMGUI_TIMELINE_FRAME_MULTIPLE 5 #define IMGUI_TIMELINE_MERGE #define IMGUI_TOOL_COLOR_PICKER_DURATION 0.25f #define IMGUI_OPTION_POPUP_ROW_COUNT 2 +#define IMGUI_CHORD_REPEAT_TIME 0.25f #define IMGUI_ACTION_FRAME_CROP "Frame Crop" #define IMGUI_ACTION_FRAME_SWAP "Frame Swap" @@ -72,6 +73,7 @@ #define IMGUI_ACTION_ERASE "Erase" #define IMGUI_ACTION_RELOAD_SPRITESHEET "Reload Spritesheet(s)" #define IMGUI_ACTION_REPLACE_SPRITESHEET "Replace Spritesheet" +#define IMGUI_ACTION_OPEN_FILE "Open File" #define IMGUI_POPUP_FLAGS ImGuiWindowFlags_NoMove #define IMGUI_POPUP_MODAL_FLAGS ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize @@ -93,7 +95,7 @@ #define IMGUI_NONE "None" #define IMGUI_ANIMATION_DEFAULT_FORMAT "(*) {}" -#define IMGUI_BUFFER_MAX 255 +#define IMGUI_BUFFER_MAX UCHAR_MAX #define IMGUI_INVISIBLE_LABEL_MARKER "##" #define IMGUI_ITEM_SELECTABLE_EDITABLE_LABEL "## Editing" #define IMGUI_LOG_FORMAT "## Log {}" @@ -107,7 +109,7 @@ #define IMGUI_TIMELINE_FRAME_LABEL_FORMAT "## {}" #define IMGUI_SELECTABLE_INPUT_INT_FORMAT "#{}" #define IMGUI_TIMELINE_ANIMATION_NONE "Select an animation to show timeline..." - +#define IMGUI_HOTKEY_CHANGE "Input new hotkey..." #define IMGUI_LABEL_SHORTCUT_FORMAT "({})" #define IMGUI_TOOLTIP_SHORTCUT_FORMAT "\n(Shortcut: {})" #define IMGUI_INVISIBLE_FORMAT "## {}" @@ -206,18 +208,13 @@ struct Imgui bool isTryQuit = false; }; -typedef void(*ImguiFunction)(Imgui*); -struct ImguiHotkey +static inline void imgui_snapshot(Imgui* self, const std::string& action = SNAPSHOT_ACTION) { - ImGuiKeyChord chord; - ImguiFunction function; - std::string focusWindow{}; - std::string snapshotAction{}; - - bool is_focus_window() const { return !focusWindow.empty(); } - bool is_undoable() const { return !snapshotAction.empty(); } -}; + self->snapshots->action = action; + Snapshot snapshot = snapshot_get(self->snapshots); + snapshots_undo_push(self->snapshots, &snapshot); +} static void imgui_log_push(Imgui* self, const std::string& text) { @@ -225,12 +222,6 @@ static void imgui_log_push(Imgui* self, const std::string& text) log_imgui(text); } -static std::vector& imgui_hotkey_registry() -{ - static std::vector registry; - return registry; -} - static inline void imgui_file_new(Imgui* self) { anm2_reference_clear(self->reference); @@ -274,13 +265,6 @@ static inline void imgui_explore(Imgui* self) dialog_explorer_open(parentPath.string()); } -static inline void imgui_snapshot(Imgui* self, const std::string& action = SNAPSHOT_ACTION) -{ - self->snapshots->action = action; - Snapshot snapshot = snapshot_get(self->snapshots); - snapshots_undo_push(self->snapshots, &snapshot); -} - static inline void imgui_tool_pan_set(Imgui* self) { self->settings->tool = TOOL_PAN; @@ -353,6 +337,125 @@ static inline void imgui_paste(Imgui* self) clipboard_paste(self->clipboard); } +static inline void imgui_onionskin_toggle(Imgui* self) +{ + self->settings->onionskinIsEnabled = !self->settings->onionskinIsEnabled; +} + +static const std::unordered_map IMGUI_KEY_MAP = +{ + { "A", ImGuiKey_A }, + { "B", ImGuiKey_B }, + { "C", ImGuiKey_C }, + { "D", ImGuiKey_D }, + { "E", ImGuiKey_E }, + { "F", ImGuiKey_F }, + { "G", ImGuiKey_G }, + { "H", ImGuiKey_H }, + { "I", ImGuiKey_I }, + { "J", ImGuiKey_J }, + { "K", ImGuiKey_K }, + { "L", ImGuiKey_L }, + { "M", ImGuiKey_M }, + { "N", ImGuiKey_N }, + { "O", ImGuiKey_O }, + { "P", ImGuiKey_P }, + { "Q", ImGuiKey_Q }, + { "R", ImGuiKey_R }, + { "S", ImGuiKey_S }, + { "T", ImGuiKey_T }, + { "U", ImGuiKey_U }, + { "V", ImGuiKey_V }, + { "W", ImGuiKey_W }, + { "X", ImGuiKey_X }, + { "Y", ImGuiKey_Y }, + { "Z", ImGuiKey_Z }, + + { "0", ImGuiKey_0 }, + { "1", ImGuiKey_1 }, + { "2", ImGuiKey_2 }, + { "3", ImGuiKey_3 }, + { "4", ImGuiKey_4 }, + { "5", ImGuiKey_5 }, + { "6", ImGuiKey_6 }, + { "7", ImGuiKey_7 }, + { "8", ImGuiKey_8 }, + { "9", ImGuiKey_9 }, + + { "Num0", ImGuiKey_Keypad0 }, + { "Num1", ImGuiKey_Keypad1 }, + { "Num2", ImGuiKey_Keypad2 }, + { "Num3", ImGuiKey_Keypad3 }, + { "Num4", ImGuiKey_Keypad4 }, + { "Num5", ImGuiKey_Keypad5 }, + { "Num6", ImGuiKey_Keypad6 }, + { "Num7", ImGuiKey_Keypad7 }, + { "Num8", ImGuiKey_Keypad8 }, + { "Num9", ImGuiKey_Keypad9 }, + { "NumAdd", ImGuiKey_KeypadAdd }, + { "NumSubtract", ImGuiKey_KeypadSubtract }, + { "NumMultiply", ImGuiKey_KeypadMultiply }, + { "NumDivide", ImGuiKey_KeypadDivide }, + { "NumEnter", ImGuiKey_KeypadEnter }, + { "NumDecimal", ImGuiKey_KeypadDecimal }, + + { "F1", ImGuiKey_F1 }, + { "F2", ImGuiKey_F2 }, + { "F3", ImGuiKey_F3 }, + { "F4", ImGuiKey_F4 }, + { "F5", ImGuiKey_F5 }, + { "F6", ImGuiKey_F6 }, + { "F7", ImGuiKey_F7 }, + { "F8", ImGuiKey_F8 }, + { "F9", ImGuiKey_F9 }, + { "F10", ImGuiKey_F10 }, + { "F11", ImGuiKey_F11 }, + { "F12", ImGuiKey_F12 }, + + { "Up", ImGuiKey_UpArrow }, + { "Down", ImGuiKey_DownArrow }, + { "Left", ImGuiKey_LeftArrow }, + { "Right", ImGuiKey_RightArrow }, + + { "Space", ImGuiKey_Space }, + { "Enter", ImGuiKey_Enter }, + { "Escape", ImGuiKey_Escape }, + { "Tab", ImGuiKey_Tab }, + { "Backspace", ImGuiKey_Backspace }, + { "Delete", ImGuiKey_Delete }, + { "Insert", ImGuiKey_Insert }, + { "Home", ImGuiKey_Home }, + { "End", ImGuiKey_End }, + { "PageUp", ImGuiKey_PageUp }, + { "PageDown", ImGuiKey_PageDown }, + + { "Minus", ImGuiKey_Minus }, + { "Equal", ImGuiKey_Equal }, + { "LeftBracket", ImGuiKey_LeftBracket }, + { "RightBracket", ImGuiKey_RightBracket }, + { "Semicolon", ImGuiKey_Semicolon }, + { "Apostrophe", ImGuiKey_Apostrophe }, + { "Comma", ImGuiKey_Comma }, + { "Period", ImGuiKey_Period }, + { "Slash", ImGuiKey_Slash }, + { "Backslash", ImGuiKey_Backslash }, + { "GraveAccent", ImGuiKey_GraveAccent }, + + { "MouseLeft", ImGuiKey_MouseLeft }, + { "MouseRight", ImGuiKey_MouseRight }, + { "MouseMiddle", ImGuiKey_MouseMiddle }, + { "MouseX1", ImGuiKey_MouseX1 }, + { "MouseX2", ImGuiKey_MouseX2 } +}; + +static std::unordered_map IMGUI_MOD_MAP = +{ + { "Ctrl", ImGuiMod_Ctrl }, + { "Shift", ImGuiMod_Shift }, + { "Alt", ImGuiMod_Alt }, + { "Super", ImGuiMod_Super }, +}; + static inline ImGuiKey imgui_key_from_char_get(char c) { if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; @@ -367,16 +470,12 @@ static inline std::string imgui_string_from_chord_get(ImGuiKeyChord chord) if (chord & ImGuiMod_Ctrl) result += "Ctrl+"; if (chord & ImGuiMod_Shift) result += "Shift+"; if (chord & ImGuiMod_Alt) result += "Alt+"; + if (chord & ImGuiMod_Super) result += "Super+"; ImGuiKey key = (ImGuiKey)(chord & ~ImGuiMod_Mask_); - if (key >= ImGuiKey_A && key <= ImGuiKey_Z) - result.push_back('A' + (key - ImGuiKey_A)); - else if (key >= ImGuiKey_0 && key <= ImGuiKey_9) - result.push_back('0' + (key - ImGuiKey_0)); - else + if (key != ImGuiKey_None) { - // Fallback to ImGui's built-in name for non-alphanumerics const char* name = ImGui::GetKeyName(key); if (name && *name) result += name; @@ -384,9 +483,44 @@ static inline std::string imgui_string_from_chord_get(ImGuiKeyChord chord) result += "Unknown"; } + if (!result.empty() && result.back() == '+') + result.pop_back(); + return result; } +static inline ImGuiKeyChord imgui_chord_from_string_get(const std::string& str) +{ + ImGuiKeyChord chord = 0; + ImGuiKey baseKey = ImGuiKey_None; + + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, '+')) + { + // trim + token.erase(0, token.find_first_not_of(" \t\r\n")); + token.erase(token.find_last_not_of(" \t\r\n") + 1); + + if (token.empty()) + continue; + + if (auto it = IMGUI_MOD_MAP.find(token); it != IMGUI_MOD_MAP.end()) { + chord |= it->second; + } + else if (baseKey == ImGuiKey_None) { + if (auto it2 = IMGUI_KEY_MAP.find(token); it2 != IMGUI_KEY_MAP.end()) + baseKey = it2->second; + } + } + + if (baseKey != ImGuiKey_None) + chord |= baseKey; + + return chord; +} + + static void imgui_contextual_actions_enable(Imgui* self) { self->isContextualActionsEnabled = true; } static void imgui_contextual_actions_disable(Imgui* self){ self->isContextualActionsEnabled = false; } static inline bool imgui_is_popup_open(const std::string& label) { return ImGui::IsPopupOpen(label.c_str()); } @@ -449,6 +583,7 @@ enum ImguiItemType IMGUI_WINDOW, IMGUI_DOCKSPACE, IMGUI_CHILD, + IMGUI_TABLE, IMGUI_OPTION_POPUP, IMGUI_SELECTABLE, IMGUI_BUTTON, @@ -478,6 +613,22 @@ struct ImguiItemOverride s32 value{}; }; +struct ImguiItem; + +static std::vector& imgui_item_registry(void) +{ + static std::vector registry; + return registry; +} + +static ImGuiKeyChord* imgui_hotkey_chord_registry(void) +{ + static ImGuiKeyChord registry[HOTKEY_COUNT]; + return registry; +} + +typedef void(*ImguiFunction)(Imgui*); + // Item struct ImguiItem { @@ -491,6 +642,7 @@ struct ImguiItem std::vector items{}; AtlasType atlas = ATLAS_NONE; ImGuiKeyChord chord = IMGUI_CHORD_NONE; + HotkeyType hotkey = HOTKEY_NONE; ImGuiKey mnemonicKey = ImGuiKey_None; s32 mnemonicIndex = INDEX_NONE; ImVec2 size{}; @@ -525,17 +677,9 @@ struct ImguiItem { static s32 idNew = 0; id = idNew++; - - if (is_chord()) - { - std::string chordString = imgui_string_from_chord_get(chord); - if (isShortcutInLabel) - label += std::format(IMGUI_LABEL_SHORTCUT_FORMAT, chordString); - tooltip += std::format(IMGUI_TOOLTIP_SHORTCUT_FORMAT, chordString); - if (function) - imgui_hotkey_registry().push_back({chord, function, focusWindow, snapshotAction}); - } + imgui_item_registry().push_back(this); + std::string labelNew{}; for (s32 i = 0; i < (s32)label.size(); i++) @@ -578,12 +722,20 @@ struct ImguiItem return out; } + ImGuiKeyChord chord_get() const + { + if (is_hotkey()) return imgui_hotkey_chord_registry()[hotkey]; + return chord; + } + bool is_border() const { return border != 0; } bool is_row() const { return rowCount != 0; } - bool is_chord() const { return chord != IMGUI_CHORD_NONE; } + bool is_hotkey() const { return hotkey != HOTKEY_NONE; } + bool is_chord() const { return chord != IMGUI_CHORD_NONE || is_hotkey(); } bool is_drag_drop() const { return !dragDrop.empty(); } bool is_focus_window() const { return !focusWindow.empty(); } bool is_popup() const { return !popup.empty(); } + bool is_function() const { return function; } bool is_size() const { return size != ImVec2(); } bool is_popup_size() const { return popupSize != ImVec2(); } bool is_tooltip() const { return !tooltip.empty(); } @@ -643,7 +795,7 @@ IMGUI_ITEM(IMGUI_NEW, self.label = "&New ", self.tooltip = "Load a blank .anm2 file to edit.", self.function = imgui_file_new, - self.chord = ImGuiMod_Ctrl | ImGuiKey_N, + self.hotkey = HOTKEY_NEW, self.isSizeToText = true, self.isShortcutInLabel = true ); @@ -652,7 +804,7 @@ IMGUI_ITEM(IMGUI_OPEN, self.label = "&Open ", self.tooltip = "Open an existing .anm2 file to edit.", self.function = imgui_file_open, - self.chord = ImGuiMod_Ctrl | ImGuiKey_O, + self.hotkey = HOTKEY_OPEN, self.isSizeToText = true, self.isShortcutInLabel = true ); @@ -661,7 +813,7 @@ IMGUI_ITEM(IMGUI_SAVE, self.label = "&Save ", self.tooltip = "Saves the current .anm2 file to its path.\nIf no path exists, one can be chosen.", self.function = imgui_file_save, - self.chord = ImGuiMod_Ctrl | ImGuiKey_S, + self.hotkey = HOTKEY_SAVE, self.isSizeToText = true, self.isShortcutInLabel = true ); @@ -670,7 +822,7 @@ IMGUI_ITEM(IMGUI_SAVE_AS, self.label = "S&ave As ", self.tooltip = "Saves the current .anm2 file to a chosen path.", self.function = imgui_file_save_as, - self.chord = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_S, + self.hotkey = HOTKEY_SAVE_AS, self.isSizeToText = true, self.isShortcutInLabel = true ); @@ -687,7 +839,7 @@ IMGUI_ITEM(IMGUI_EXIT, self.label = "&Exit ", self.tooltip = "Exits the program.", self.function = imgui_quit, - self.chord = ImGuiMod_Alt | ImGuiKey_F4, + self.hotkey = HOTKEY_EXIT, self.isSizeToText = true, self.isShortcutInLabel = true ); @@ -697,6 +849,11 @@ IMGUI_ITEM(IMGUI_EXIT_CONFIRMATION, self.text = "Unsaved changes will be lost!\nAre you sure you want to exit?" ); +IMGUI_ITEM(IMGUI_OPEN_CONFIRMATION, + self.label = "Open Confirmation", + self.text = "Unsaved changes will be lost!\nAre you sure you open a new file?" +); + IMGUI_ITEM(IMGUI_WIZARD, self.label = "&Wizard", self.tooltip = "Opens the wizard menu, for neat functions related to the .anm2.", @@ -747,12 +904,14 @@ IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_PIVOT, IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_ROWS, self.label = "Rows", self.tooltip = "Set how many rows will be used in the generated animation.", + self.min = 1, self.max = 1000 ); IMGUI_ITEM(IMGUI_GENERATE_ANIMATION_FROM_GRID_COLUMNS, self.label = "Columns", self.tooltip = "Set how many columns will be used in the generated animation.", + self.min = 1, self.max = 1000 ); @@ -812,7 +971,7 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES, self.tooltip = "Change all frame properties in the selected animation item (or selected frame).", self.popup = "Change All Frame Properties", self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = {500, 380} + self.popupSize = {500, 405} ); IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, @@ -820,7 +979,7 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_CHILD, self.size = { IMGUI_CHANGE_ALL_FRAME_PROPERTIES.popupSize.x, - 250 + 275 }, self.flags = true ); @@ -862,7 +1021,7 @@ IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_ADD, IMGUI_ITEM(IMGUI_CHANGE_ALL_FRAME_PROPERTIES_SUBTRACT, self.label = "Subtract", - self.tooltip = "The specified values will be added to all selected frames.", + self.tooltip = "The specified values will be subtracted from all selected frames.", self.snapshotAction = "Subtract Frame Properties", self.rowCount = IMGUI_CHANGE_ALL_FRAME_PROPERTIES_OPTIONS_ROW_COUNT, self.isSameLine = true @@ -887,7 +1046,7 @@ IMGUI_ITEM(IMGUI_SCALE_ANM2, self.tooltip = "Scale up all size and position-related frame properties in the anm2.", self.popup = "Scale Anm2", self.popupType = IMGUI_POPUP_CENTER_WINDOW, - self.popupSize = {260, 72}, + self.popupSize = {260, 75}, self.isSizeToText = true, self.isSeparator = true ); @@ -963,7 +1122,7 @@ IMGUI_ITEM(IMGUI_RENDER_ANIMATION_OUTPUT, IMGUI_ITEM(IMGUI_RENDER_ANIMATION_FORMAT, self.label = "Format", self.tooltip = "(PNG images only).\nSet the format of each output frame; i.e., its filename.\nThe format will only take one argument; that being the frame's index.\nFor example, a format like \"{}.png\" will export a frame of index 0 as \"0.png\".", - self.max = 255 + self.max = UCHAR_MAX ); IMGUI_ITEM(IMGUI_RENDER_ANIMATION_CONFIRM, @@ -1014,7 +1173,43 @@ IMGUI_ITEM(IMGUI_SETTINGS, IMGUI_ITEM(IMGUI_VSYNC, self.label = "&Vsync", self.tooltip = "Toggle vertical sync; synchronizes program framerate with your monitor's refresh rate.", - self.isSizeToText = true + self.isSizeToText = true, + self.isSeparator = true +); + +IMGUI_ITEM(IMGUI_HOTKEYS, + self.label = "&Hotkeys", + self.tooltip = "Change the program's hotkeys.", + self.popup = "Hotkeys", + self.popupSize = {500, 405}, + self.isSizeToText = true, + self.isSeparator = true +); + +IMGUI_ITEM(IMGUI_HOTKEYS_CHILD, + self.label = "## Hotkeys Child", + self.size = {IMGUI_HOTKEYS.popupSize.x, IMGUI_HOTKEYS.popupSize.y - 35}, + self.flags = true +); + +#define IMGUI_HOTKEYS_FUNCTION "Function" +#define IMGUI_HOTKEYS_HOTKEY "Hotkey" +IMGUI_ITEM(IMGUI_HOTKEYS_TABLE, + self.label = "## Hotkeys Table", + self.value = 2, + self.flags = ImGuiTableFlags_Borders +); + +IMGUI_ITEM(IMGUI_HOTKEYS_OPTIONS_CHILD, + self.label = "## Merge Options Child", + self.size = {IMGUI_HOTKEYS.popupSize.x, 35}, + self.flags = true +); + +IMGUI_ITEM(IMGUI_HOTKEYS_CONFIRM, + self.label = "Confirm", + self.tooltip = "Use these hotkeys.", + self.rowCount = 1 ); IMGUI_ITEM(IMGUI_DEFAULT_SETTINGS, @@ -1298,14 +1493,11 @@ IMGUI_ITEM(IMGUI_CANVAS_ZOOM, self.tooltip = "Change the zoom of the canvas.", self.min = CANVAS_ZOOM_MIN, self.max = CANVAS_ZOOM_MAX, + self.speed = 1.0f, self.value = CANVAS_ZOOM_DEFAULT ); -IMGUI_ITEM(IMGUI_CANVAS_CENTER_VIEW, - self.label = "Center View", - self.tooltip = "Centers the current view on the canvas.", - self.size = {-FLT_MIN, 0} -); + IMGUI_ITEM(IMGUI_CANVAS_VISUAL_CHILD, self.label = "## Animation Preview Visual Child", @@ -1327,8 +1519,8 @@ IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY, IMGUI_ITEM(IMGUI_CANVAS_ANIMATION_OVERLAY_TRANSPARENCY, self.label = "Alpha", self.tooltip = "Set the transparency of the animation overlay.", - self.value = 255, - self.max = 255 + self.value = SETTINGS_PREVIEW_OVERLAY_TRANSPARENCY_DEFAULT, + self.max = UCHAR_MAX ); IMGUI_ITEM(IMGUI_CANVAS_HELPER_CHILD, @@ -1351,28 +1543,37 @@ IMGUI_ITEM(IMGUI_CANVAS_AXES_COLOR, IMGUI_ITEM(IMGUI_CANVAS_ROOT_TRANSFORM, self.label = "Root Transform", self.tooltip = "Toggles the root frames's attributes transforming the other items in an animation.", - self.value = true + self.value = SETTINGS_PREVIEW_IS_ROOT_TRANSFORM_DEFAULT ); IMGUI_ITEM(IMGUI_CANVAS_TRIGGERS, self.label = "Triggers", self.tooltip = "Toggles activated triggers drawing their event name.", - self.value = true + self.value = SETTINGS_PREVIEW_IS_TRIGGERS_DEFAULT ); IMGUI_ITEM(IMGUI_CANVAS_PIVOTS, self.label = "Pivots", - self.tooltip = "Toggles drawing each layer's pivot." + self.tooltip = "Toggles drawing each layer's pivot.", + self.value = SETTINGS_PREVIEW_IS_PIVOTS_DEFAULT ); IMGUI_ITEM(IMGUI_CANVAS_TARGETS, self.label = "Targets", - self.tooltip = "Toggles drawing the targets (i.e., the colored root/null icons)." + self.tooltip = "Toggles drawing the targets (the colored root/null icons).", + self.value = SETTINGS_PREVIEW_IS_TARGETS_DEFAULT +); + +IMGUI_ITEM(IMGUI_CANVAS_ALT_ICONS, + self.label = "Alt Icons", + self.tooltip = "Toggles the use of alternate icons for the targets (the colored root/null icons).", + self.value = SETTINGS_PREVIEW_IS_ALT_ICONS_DEFAULT ); IMGUI_ITEM(IMGUI_CANVAS_BORDER, self.label = "Border", - self.tooltip = "Toggles the appearance of a border around the items." + self.tooltip = "Toggles the appearance of a border around the items.", + self.value = SETTINGS_PREVIEW_IS_BORDER_DEFAULT ); IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW, @@ -1380,11 +1581,27 @@ IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW, self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); +IMGUI_ITEM(IMGUI_ANIMATION_PREVIEW_CENTER_VIEW, + self.label = "Center View", + self.tooltip = "Centers the current view on the animation preview.", + self.hotkey = HOTKEY_CENTER_VIEW, + self.focusWindow = IMGUI_ANIMATION_PREVIEW.label, + self.size = {-FLT_MIN, 0} +); + IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR, self.label = "Spritesheet Editor", self.flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse ); +IMGUI_ITEM(IMGUI_SPRITESHEET_EDITOR_CENTER_VIEW, + self.label = "Center View", + self.tooltip = "Centers the current view on the spritesheet editor.", + self.hotkey = HOTKEY_CENTER_VIEW, + self.focusWindow = IMGUI_SPRITESHEET_EDITOR.label, + self.size = {-FLT_MIN, 0} +); + IMGUI_ITEM(IMGUI_FRAME_PROPERTIES, self.label = "Frame Properties"); IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_POSITION, @@ -1419,8 +1636,7 @@ IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_SCALE, self.label = "Scale", self.tooltip = "Change the scale of the selected frame.", self.snapshotAction = "Frame Scale", - self.isUseItemActivated = true, - self.value = 100 + self.isUseItemActivated = true ); IMGUI_ITEM(IMGUI_FRAME_PROPERTIES_ROTATION, @@ -1505,7 +1721,7 @@ IMGUI_ITEM(IMGUI_TOOL_PAN, self.label = "## Pan", self.tooltip = "Use the pan tool.\nWill shift the view as the cursor is dragged.\nYou can also use the middle mouse button to pan at any time.", self.function = imgui_tool_pan_set, - self.chord = ImGuiKey_P, + self.hotkey = HOTKEY_PAN, self.atlas = ATLAS_PAN ); @@ -1513,7 +1729,7 @@ IMGUI_ITEM(IMGUI_TOOL_MOVE, self.label = "## Move", self.tooltip = "Use the move tool.\nWill move the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", self.function = imgui_tool_move_set, - self.chord = ImGuiKey_M, + self.hotkey = HOTKEY_MOVE, self.atlas = ATLAS_MOVE ); @@ -1521,7 +1737,7 @@ IMGUI_ITEM(IMGUI_TOOL_ROTATE, self.label = "## Rotate", self.tooltip = "Use the rotate tool.\nWill rotate the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", self.function = imgui_tool_rotate_set, - self.chord = ImGuiKey_R, + self.hotkey = HOTKEY_ROTATE, self.atlas = ATLAS_ROTATE ); @@ -1529,7 +1745,7 @@ IMGUI_ITEM(IMGUI_TOOL_SCALE, self.label = "## Scale", self.tooltip = "Use the scale tool.\nWill scale the selected item as the cursor is dragged, or directional keys are pressed.\n(Animation Preview only.)", self.function = imgui_tool_scale_set, - self.chord = ImGuiKey_S, + self.hotkey = HOTKEY_SCALE, self.atlas = ATLAS_SCALE ); @@ -1537,7 +1753,7 @@ IMGUI_ITEM(IMGUI_TOOL_CROP, self.label = "## Crop", self.tooltip = "Use the crop tool.\nWill produce a crop rectangle based on how the cursor is dragged.\n(Spritesheet Editor only.)", self.function = imgui_tool_crop_set, - self.chord = ImGuiKey_C, + self.hotkey = HOTKEY_CROP, self.atlas = ATLAS_CROP ); @@ -1545,7 +1761,7 @@ IMGUI_ITEM(IMGUI_TOOL_DRAW, self.label = "## Draw", self.tooltip = "Draws pixels onto the selected spritesheet, with the current color.\n(Spritesheet Editor only.)", self.function = imgui_tool_draw_set, - self.chord = ImGuiKey_B, + self.hotkey = HOTKEY_DRAW, self.atlas = ATLAS_DRAW ); @@ -1553,7 +1769,7 @@ IMGUI_ITEM(IMGUI_TOOL_ERASE, self.label = "## Erase", self.tooltip = "Erases pixels from the selected spritesheet.\n(Spritesheet Editor only.)", self.function = imgui_tool_erase_set, - self.chord = ImGuiKey_E, + self.hotkey = HOTKEY_ERASE, self.atlas = ATLAS_ERASE ); @@ -1561,7 +1777,7 @@ IMGUI_ITEM(IMGUI_TOOL_COLOR_PICKER, self.label = "## Color Picker", self.tooltip = "Selects a color from the canvas, to be used for drawing.\n(Spritesheet Editor only).", self.function = imgui_tool_color_picker_set, - self.chord = ImGuiKey_W, + self.hotkey = HOTKEY_COLOR_PICKER, self.atlas = ATLAS_COLOR_PICKER ); @@ -1569,7 +1785,7 @@ IMGUI_ITEM(IMGUI_TOOL_UNDO, self.label = "## Undo", self.tooltip = "Undo the last action.", self.function = imgui_undo, - self.chord = ImGuiMod_Ctrl | ImGuiKey_Z, + self.hotkey = HOTKEY_UNDO, self.atlas = ATLAS_UNDO ); @@ -1577,7 +1793,7 @@ IMGUI_ITEM(IMGUI_TOOL_REDO, self.label = "## Redo", self.tooltip = "Redo the last action.", self.function = imgui_redo, - self.chord = ImGuiMod_Ctrl | ImGuiKey_Y, + self.hotkey = HOTKEY_REDO, self.atlas = ATLAS_REDO ); @@ -1875,7 +2091,7 @@ IMGUI_ITEM(IMGUI_PLAY, self.label = "|> Play", self.tooltip = "Play the current animation, if paused.", self.focusWindow = IMGUI_TIMELINE.label, - self.chord = ImGuiKey_Space, + self.hotkey = HOTKEY_PLAY_PAUSE, self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true ); @@ -1884,7 +2100,7 @@ IMGUI_ITEM(IMGUI_PAUSE, self.label = "|| Pause", self.tooltip = "Pause the current animation, if playing.", self.focusWindow = IMGUI_TIMELINE.label, - self.chord = ImGuiKey_Space, + self.hotkey = HOTKEY_PLAY_PAUSE, self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, self.isSameLine = true ); @@ -1992,7 +2208,55 @@ IMGUI_ITEM(IMGUI_CREATED_BY, self.label = "Author", self.tooltip = "Sets the author of the animation.", self.rowCount = IMGUI_TIMELINE_OPTIONS_ROW_COUNT, - self.max = 255 + self.max = UCHAR_MAX +); + +#define IMGUI_ONIONSKIN_ROW_COUNT 3 +IMGUI_ITEM(IMGUI_ONIONSKIN, self.label = "Onionskin"); +IMGUI_ITEM(IMGUI_ONIONSKIN_ENABLED, + self.label = "Enabled", + self.tooltip = "Toggle onionskin (previews of frames before/after the current one.)", + self.function = imgui_onionskin_toggle, + self.hotkey = HOTKEY_ONIONSKIN, + self.isSeparator = true +); + +IMGUI_ITEM(IMGUI_ONIONSKIN_BEFORE, self.label = "-- Before-- "); +IMGUI_ITEM(IMGUI_ONIONSKIN_AFTER, self.label = "-- After -- "); + +IMGUI_ITEM(IMGUI_ONIONSKIN_COUNT, + self.label = "Count", + self.tooltip = "Set the number of previewed frames appearing.", + self.min = 1, + self.max = 100, + self.value = SETTINGS_ONIONSKIN_BEFORE_COUNT_DEFAULT, + self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ONIONSKIN_COLOR_OFFSET, + self.label = "Color Offset", + self.tooltip = "Set the color offset of the previewed frames.", + self.flags = ImGuiColorEditFlags_NoInputs, + self.rowCount = IMGUI_ONIONSKIN_ROW_COUNT +); + +IMGUI_ITEM(IMGUI_ONIONSKIN_DRAW_ORDER, + self.label = "Draw Order", + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ONIONSKIN_BELOW, + self.label = "Below", + self.tooltip = "The onionskin frames will draw below the base frame.", + self.value = ONIONSKIN_BELOW, + self.isSameLine = true +); + +IMGUI_ITEM(IMGUI_ONIONSKIN_ABOVE, + self.label = "Above", + self.tooltip = "The onionskin frames will draw above the base frame.", + self.value = ONIONSKIN_ABOVE ); IMGUI_ITEM(IMGUI_CONTEXT_MENU, self.label = "## Context Menu"); @@ -2002,7 +2266,7 @@ IMGUI_ITEM(IMGUI_CUT, self.tooltip = "Cuts the currently selected contextual element; removing it and putting it to the clipboard.", self.snapshotAction = "Cut", self.function = imgui_cut, - self.chord = ImGuiMod_Ctrl | ImGuiKey_X, + self.hotkey = HOTKEY_CUT, self.isSizeToText = true ); @@ -2011,7 +2275,7 @@ IMGUI_ITEM(IMGUI_COPY, self.tooltip = "Copies the currently selected contextual element to the clipboard.", self.snapshotAction = "Copy", self.function = imgui_copy, - self.chord = ImGuiMod_Ctrl | ImGuiKey_C, + self.hotkey = HOTKEY_COPY, self.isSizeToText = true ); @@ -2020,7 +2284,7 @@ IMGUI_ITEM(IMGUI_PASTE, self.tooltip = "Pastes the currently selection contextual element from the clipboard.", self.snapshotAction = "Paste", self.function = imgui_paste, - self.chord = ImGuiMod_Ctrl | ImGuiKey_V, + self.hotkey = HOTKEY_PASTE, self.isSizeToText = true ); @@ -2029,7 +2293,7 @@ IMGUI_ITEM(IMGUI_CHANGE_INPUT_TEXT, self.tooltip = "Rename the selected item.", self.snapshotAction = "Rename Item", self.flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue, - self.max = 255 + self.max = UCHAR_MAX ); IMGUI_ITEM(IMGUI_CHANGE_INPUT_INT, diff --git a/src/preview.cpp b/src/preview.cpp index ec069a0..0d78d6f 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -98,155 +98,139 @@ void preview_draw(Preview* self) if (self->settings->previewIsAxes) canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor); - Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); - s32& animationID = self->reference->animationID; - - if (animation) + auto animation_draw = [&](s32 animationID) { - Anm2Frame root; - mat4 rootModel = mat4(1.0f); - - anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time); + Anm2Animation* animation = map_find(self->anm2->animations, animationID); + if (!animation) return; - if (self->settings->previewIsRootTransform) - rootModel = canvas_parent_model_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation); - - // Root - if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible) + auto root_draw = [&](Anm2Frame root, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) { mat4 model = canvas_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, PERCENT_TO_UNIT(root.scale), root.rotation); mat4 rootTransform = transform * model; - f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_TARGET); - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, PREVIEW_ROOT_COLOR); - } + vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_ROOT_COLOR; + AtlasType atlas = self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; + f32 vertices[] = ATLAS_UV_VERTICES(atlas); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, color); + }; - // Layers - for (auto [i, id] : self->anm2->layerMap) + auto layer_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) { - Anm2Frame frame; Anm2Item& layerAnimation = animation->layerAnimations[id]; + if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) return; - if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) - continue; - - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, self->time); - - if (!frame.isVisible) - continue; + Anm2Frame frame; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time); + if (!frame.isVisible) return; mat4 model = canvas_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); mat4 layerTransform = transform * (rootModel * model); - - Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[id].spritesheetID); + vec3 frameColorOffset = frame.offsetRGB + colorOffset; + vec4 frameTint = frame.tintRGBA; + frameTint.a = std::max(0.0f, frameTint.a - alphaOffset); - if (!spritesheet) continue; + Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[id].spritesheetID); + if (!spritesheet) return; Texture& texture = spritesheet->texture; - - if (!texture.isInvalid) - { - vec2 uvMin = frame.crop / vec2(texture.size); - vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size); - f32 vertices[] = UV_VERTICES(uvMin, uvMax); - - canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB); - } - - if (self->settings->previewIsBorder) - canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR); - - if (self->settings->previewIsPivots) - { - f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); - mat4 pivotModel = canvas_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 pivotTransform = transform * (rootModel * pivotModel); - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR); - } - } - - // Nulls - if (self->settings->previewIsTargets) - { - for (auto& [id, nullAnimation] : animation->nullAnimations) - { - if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) - continue; - - 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]; - - vec4 color = (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? - PREVIEW_NULL_SELECTED_COLOR : - PREVIEW_NULL_COLOR; - - vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; - AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : ATLAS_TARGET; - - mat4 model = canvas_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 nullTransform = transform * (rootModel * model); - - f32 vertices[] = ATLAS_UV_VERTICES(atlas); - - canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, nullTransform, vertices, color); - - if (null.isShowRect) - { - mat4 rectModel = canvas_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 rectTransform = transform * (rootModel * rectModel); - canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); - } - } - } - } - - s32& animationOverlayID = self->animationOverlayID; - Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID); - - if (animationOverlay) - { - 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 = canvas_parent_model_get(root.position, {}, PERCENT_TO_UNIT(root.scale)); - - for (auto [i, id] : self->anm2->layerMap) - { - Anm2Frame frame; - Anm2Item& layerAnimation = animationOverlay->layerAnimations[id]; - - if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) - continue; - - anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationOverlayID, ANM2_LAYER, id}, self->time); - - if (!frame.isVisible) - continue; - - Texture& texture = self->anm2->spritesheets[self->anm2->layers[id].spritesheetID].texture; + if (texture.isInvalid) return; - if (texture.isInvalid) continue; - vec2 uvMin = frame.crop / vec2(texture.size); vec2 uvMax = (frame.crop + frame.size) / vec2(texture.size); f32 vertices[] = UV_VERTICES(uvMin, uvMax); + canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frameTint, frameColorOffset); - mat4 model = canvas_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation); - mat4 layerTransform = transform * (rootModel * model); + if (self->settings->previewIsBorder) + { + vec4 borderColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_BORDER_COLOR; + canvas_rect_draw(&self->canvas, shaderLine, layerTransform, borderColor); + } - vec4 tint = frame.tintRGBA; - tint.a *= U8_TO_FLOAT(self->settings->previewOverlayTransparency); + if (self->settings->previewIsPivots) + { + vec4 pivotColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_PIVOT_COLOR; + f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); + mat4 pivotModel = canvas_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 pivotTransform = transform * (rootModel * pivotModel); + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, pivotColor); + } + }; - canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, tint, frame.offsetRGB); - } - } + auto null_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) + { + Anm2Item& nullAnimation = animation->nullAnimations[id]; + if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) return; + + Anm2Frame frame; + anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, time); + if (!frame.isVisible) return; + + Anm2Null null = self->anm2->nulls[id]; + + vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : + (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? + PREVIEW_NULL_SELECTED_COLOR : PREVIEW_NULL_COLOR; + + vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; + AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET; + + mat4 model = canvas_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 nullTransform = transform * (rootModel * model); + + f32 vertices[] = ATLAS_UV_VERTICES(atlas); + + canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, nullTransform, vertices, color); + + if (null.isShowRect) + { + mat4 rectModel = canvas_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation); + mat4 rectTransform = transform * (rootModel * rectModel); + canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); + } + }; + + auto base_draw = [&](f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {}) + { + Anm2Frame root; + anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time); + + mat4 rootModel = self->settings->previewIsRootTransform ? + canvas_parent_model_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f); + + if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible) + root_draw(root, colorOffset, alphaOffset, isOnionskin); + + for (auto [i, id] : self->anm2->layerMap) + layer_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); + + if (self->settings->previewIsTargets) + for (auto& [id, _] : animation->nullAnimations) + null_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin); + }; + + auto onionskin_draw = [&](s32 count, s32 direction, vec3 colorOffset) + { + for (s32 i = 1; i <= count; i++) + { + f32 time = self->time + (f32)(direction * i); + f32 alphaOffset = (1.0f / (count + 1)) * i; + base_draw(time, colorOffset, alphaOffset, true); + } + }; + + auto onionskins_draw = [&]() + { + if (!self->settings->onionskinIsEnabled) return; + onionskin_draw(self->settings->onionskinBeforeCount, -1, self->settings->onionskinBeforeColorOffset); + onionskin_draw(self->settings->onionskinAfterCount, 1, self->settings->onionskinAfterColorOffset); + }; + + if (self->settings->onionskinDrawOrder == ONIONSKIN_BELOW) onionskins_draw(); + base_draw(self->time); + if (self->settings->onionskinDrawOrder == ONIONSKIN_ABOVE) onionskins_draw(); + }; + + animation_draw(self->reference->animationID); + animation_draw(self->animationOverlayID); canvas_unbind(); } diff --git a/src/preview.h b/src/preview.h index 926e694..4f34494 100644 --- a/src/preview.h +++ b/src/preview.h @@ -42,6 +42,8 @@ struct Preview f32 time{}; }; + + void preview_init(Preview* self, Anm2* anm2, Anm2Reference* reference, Resources* resources, Settings* settings); void preview_draw(Preview* self); void preview_tick(Preview* self); diff --git a/src/render.h b/src/render.h index 2664b3c..3cb8a87 100644 --- a/src/render.h +++ b/src/render.h @@ -7,17 +7,16 @@ enum RenderType RENDER_PNG, RENDER_GIF, RENDER_WEBM, - RENDER_MP4 + RENDER_MP4, + RENDER_COUNT }; -constexpr inline s32 RENDER_COUNT = RENDER_MP4 + 1; - const inline std::string RENDER_TYPE_STRINGS[] = { "PNG Images", "GIF image", "WebM video", - "MP4 video" + "MP4 video", }; const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = @@ -25,5 +24,5 @@ const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = ".png", ".gif", ".webm", - ".mp4" + ".mp4", }; \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index 1077d3a..81347d9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -6,70 +6,87 @@ static void _settings_setting_load(Settings* self, const std::string& line) { const auto& entry = SETTINGS_ENTRIES[i]; const std::string& key = entry.key; - void* target = (u8*)self + entry.offset; auto match_key = [&](const std::string& full) -> const char* { - if (!line.starts_with(full)) + if (!line.starts_with(full)) return nullptr; + size_t p = full.size(); while (p < line.size() && std::isspace((u8)line[p])) ++p; - if (p < line.size() && line[p] == '=') return line.c_str() + p + 1; + if (p < line.size() && line[p] == '=') + return line.c_str() + p + 1; return nullptr; }; - auto* value = match_key(key); + const char* value = nullptr; - if (value) + switch (entry.type) { - switch (entry.type) + case TYPE_INT: + if ((value = match_key(key))) { *(s32*)target = std::atoi(value); return; } + break; + case TYPE_BOOL: + if ((value = match_key(key))) { *(bool*)target = string_to_bool(value); return; } + break; + case TYPE_FLOAT: + if ((value = match_key(key))) { *(f32*)target = std::atof(value); return; } + break; + case TYPE_STRING: + if ((value = match_key(key))) { *(std::string*)target = value; return; } + break; + case TYPE_IVEC2: { - 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; + ivec2* v = (ivec2*)target; + if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; } + if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; } + break; } - } - - if (entry.type == TYPE_VEC2) - { - vec2* v = (vec2*)target; - if ((value = match_key(key + (entry.isWidthHeight ? "W" : "X")))) { v->x = std::atof(value); return; } - if ((value = match_key(key + (entry.isWidthHeight ? "H" : "Y")))) { v->y = std::atof(value); return; } - } - else if (entry.type == TYPE_IVEC2) - { - ivec2* v = (ivec2*)target; - if ((value = match_key(key + (entry.isWidthHeight ? "W" : "X")))) { v->x = std::atoi(value); return; } - if ((value = match_key(key + (entry.isWidthHeight ? "H" : "Y")))) { v->y = std::atoi(value); return; } - } - else if (entry.type == TYPE_VEC3) - { - vec3* v = (vec3*)target; - if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } - if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } - if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } - } - 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; } + case TYPE_IVEC2_WH: + { + ivec2* v = (ivec2*)target; + if ((value = match_key(key + "W"))) { v->x = std::atoi(value); return; } + if ((value = match_key(key + "H"))) { v->y = std::atoi(value); return; } + break; + }; + case TYPE_VEC2: + { + vec2* v = (vec2*)target; + if ((value = match_key(key + "X"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "Y"))) { v->y = std::atof(value); return; } + break; + } + case TYPE_VEC2_WH: + { + vec2* v = (vec2*)target; + if ((value = match_key(key + "W"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "H"))) { v->y = std::atof(value); return; } + break; + }; + case TYPE_VEC3: + { + vec3* v = (vec3*)target; + if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } + if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } + break; + } + case TYPE_VEC4: + { + vec4* v = (vec4*)target; + if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } + if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } + if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } + if ((value = match_key(key + "A"))) { v->w = std::atof(value); return; } + break; + } + default: + break; } } + + log_warning(std::format(SETTINGS_VALUE_INIT_WARNING, line)); } std::string settings_path_get(void) @@ -107,15 +124,29 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE case TYPE_IVEC2: { ivec2* data = (ivec2*)(selfPointer + entry.offset); - out << entry.key << (entry.isWidthHeight ? "W=" : "X=") << data->x << "\n"; - out << entry.key << (entry.isWidthHeight ? "H=" : "Y=") << data->y << "\n"; + out << entry.key << "X=" << data->x << "\n"; + out << entry.key << "Y=" << data->y << "\n"; + break; + } + case TYPE_IVEC2_WH: + { + ivec2* data = (ivec2*)(selfPointer + entry.offset); + out << entry.key << "W=" << data->x << "\n"; + out << entry.key << "H=" << data->y << "\n"; break; } case TYPE_VEC2: { vec2* data = (vec2*)(selfPointer + entry.offset); - out << entry.key << (entry.isWidthHeight ? "W=" : "X=") << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; - out << entry.key << (entry.isWidthHeight ? "H=" : "Y=") << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; + out << entry.key << "X=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; + out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; + break; + } + case TYPE_VEC2_WH: + { + vec2* data = (vec2*)(selfPointer + entry.offset); + out << entry.key << "W=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n"; + out << entry.key << "H=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; break; } case TYPE_VEC3: @@ -214,47 +245,32 @@ void settings_init(Settings* self) { const std::string path = settings_path_get(); std::ifstream file(path, std::ios::binary); - std::istream* in = nullptr; - std::istringstream defaultSettings; - + if (file) - { log_info(std::format(SETTINGS_INIT_INFO, path)); - in = &file; - } else { - log_error(std::format(SETTINGS_INIT_ERROR, path)); - log_info(SETTINGS_DEFAULT_INFO); - defaultSettings.str(SETTINGS_DEFAULT); - in = &defaultSettings; + log_warning(std::format(SETTINGS_INIT_WARNING, path)); + settings_save(self); + std::ofstream out(path, std::ios::binary | std::ios::app); + out << SETTINGS_IMGUI_DEFAULT; + out.flush(); + out.close(); + file.open(path, std::ios::binary); } std::string line; bool inSettingsSection = false; - while (std::getline(*in, line)) - { - if (line == SETTINGS_SECTION) - { - inSettingsSection = true; - continue; - } - if (line == SETTINGS_SECTION_IMGUI) break; - if (inSettingsSection) _settings_setting_load(self, line); - } - - // Save default settings - if (!file) + while (std::getline(file, line)) { - std::ofstream out(path, std::ios::binary | std::ios::trunc); - if (out) + if (line == SETTINGS_SECTION) { - out << SETTINGS_DEFAULT; - out.flush(); - log_info(std::format(SETTINGS_SAVE_INFO, path)); - } - else - log_error(std::format(SETTINGS_DEFAULT_ERROR, path)); + inSettingsSection = true; + continue; + } + if (line.empty()) continue; + if (line == SETTINGS_SECTION_IMGUI) break; + if (inSettingsSection) _settings_setting_load(self, line); } } \ No newline at end of file diff --git a/src/settings.h b/src/settings.h index c0255fa..8dc2fde 100644 --- a/src/settings.h +++ b/src/settings.h @@ -4,17 +4,15 @@ #include "render.h" #include "tool.h" -#define SETTINGS_BUFFER 0xFFFF -#define SETTINGS_BUFFER_ITEM 0xFF #define SETTINGS_SECTION "[Settings]" #define SETTINGS_SECTION_IMGUI "# Dear ImGui" -#define SETTINGS_INIT_ERROR "Failed to read settings file: {}" -#define SETTINGS_DEFAULT_ERROR "Failed to write default settings file: {}" +#define SETTINGS_INIT_WARNING "Unable to read settings file: {}; using default settings" +#define SETTINGS_INIT_ERROR "Unable to read settings file: {}" #define SETTINGS_SAVE_ERROR "Failed to write settings file: {}" #define SETTINGS_SAVE_FINALIZE_ERROR "Failed to write settings file: {} ({})" +#define SETTINGS_VALUE_INIT_WARNING "Unknown setting: {}" #define SETTINGS_FLOAT_FORMAT "{:.3f}" #define SETTINGS_INIT_INFO "Initialized settings from: {}" -#define SETTINGS_DEFAULT_INFO "Using default settings" #define SETTINGS_DIRECTORY_ERROR "Failed to create settings directory: {} ({})" #define SETTINGS_SAVE_INFO "Saved settings to: {}" @@ -22,277 +20,224 @@ #define SETTINGS_PATH "settings.ini" #define SETTINGS_TEMPORARY_EXTENSION ".tmp" +#define SETTINGS_LIST \ + /* name, symbol, type, defaultValue */ \ + X(windowSize, WINDOW_SIZE, TYPE_IVEC2_WH, {1280, 720}) \ + X(isVsync, IS_VSYNC, TYPE_BOOL, true) \ + \ + X(hotkeyCenterView, HOTKEY_CENTER_VIEW, TYPE_STRING, "Home") \ + X(hotkeyZoomIn, HOTKEY_ZOOM_IN, TYPE_STRING, "Ctrl++") \ + X(hotkeyZoomOut, HOTKEY_ZOOM_OUT, TYPE_STRING, "Ctrl+-") \ + X(hotkeyPlayPause, HOTKEY_PLAY_PAUSE, TYPE_STRING, "Space") \ + X(hotkeyOnionskin, HOTKEY_ONIONSKIN, TYPE_STRING, "O") \ + X(hotkeyNew, HOTKEY_NEW, TYPE_STRING, "Ctrl+N") \ + X(hotkeyOpen, HOTKEY_OPEN, TYPE_STRING, "Ctrl+O") \ + X(hotkeySave, HOTKEY_SAVE, TYPE_STRING, "Ctrl+S") \ + X(hotkeySaveAs, HOTKEY_SAVE_AS, TYPE_STRING, "Ctrl+Shift+S") \ + X(hotkeyExit, HOTKEY_EXIT, TYPE_STRING, "Alt+F4") \ + X(hotkeyPan, HOTKEY_PAN, TYPE_STRING, "P") \ + X(hotkeyMove, HOTKEY_MOVE, TYPE_STRING, "V") \ + X(hotkeyRotate, HOTKEY_ROTATE, TYPE_STRING, "R") \ + X(hotkeyScale, HOTKEY_SCALE, TYPE_STRING, "S") \ + X(hotkeyCrop, HOTKEY_CROP, TYPE_STRING, "C") \ + X(hotkeyDraw, HOTKEY_DRAW, TYPE_STRING, "B") \ + X(hotkeyErase, HOTKEY_ERASE, TYPE_STRING, "E") \ + X(hotkeyColorPicker, HOTKEY_COLOR_PICKER, TYPE_STRING, "I") \ + X(hotkeyUndo, HOTKEY_UNDO, TYPE_STRING, "Ctrl+Z") \ + X(hotkeyRedo, HOTKEY_REDO, TYPE_STRING, "Ctrl+Shift+Z") \ + X(hotkeyCopy, HOTKEY_COPY, TYPE_STRING, "Ctrl+C") \ + X(hotkeyCut, HOTKEY_CUT, TYPE_STRING, "Ctrl+X") \ + X(hotkeyPaste, HOTKEY_PASTE, TYPE_STRING, "Ctrl+V") \ + \ + X(playbackIsLoop, PLAYBACK_IS_LOOP, TYPE_BOOL, true) \ + X(playbackIsClampPlayhead,PLAYBACK_IS_CLAMP_PLAYHEAD, TYPE_BOOL, true) \ + \ + X(changeIsCrop, CHANGE_IS_CROP, TYPE_BOOL, false) \ + X(changeIsSize, CHANGE_IS_SIZE, TYPE_BOOL, false) \ + X(changeIsPosition, CHANGE_IS_POSITION, TYPE_BOOL, false) \ + X(changeIsPivot, CHANGE_IS_PIVOT, TYPE_BOOL, false) \ + X(changeIsScale, CHANGE_IS_SCALE, TYPE_BOOL, false) \ + X(changeIsRotation, CHANGE_IS_ROTATION, TYPE_BOOL, false) \ + X(changeIsDelay, CHANGE_IS_DELAY, TYPE_BOOL, false) \ + X(changeIsTint, CHANGE_IS_TINT, TYPE_BOOL, false) \ + X(changeIsColorOffset, CHANGE_IS_COLOR_OFFSET, TYPE_BOOL, false) \ + X(changeIsVisibleSet, CHANGE_IS_VISIBLE_SET, TYPE_BOOL, false) \ + X(changeIsInterpolatedSet,CHANGE_IS_INTERPOLATED_SET, TYPE_BOOL, false) \ + X(changeIsFromSelectedFrame,CHANGE_IS_FROM_SELECTED_FRAME,TYPE_BOOL, false) \ + X(changeCrop, CHANGE_CROP, TYPE_VEC2, {}) \ + X(changeSize, CHANGE_SIZE, TYPE_VEC2, {}) \ + X(changePosition, CHANGE_POSITION, TYPE_VEC2, {}) \ + X(changePivot, CHANGE_PIVOT, TYPE_VEC2, {}) \ + X(changeScale, CHANGE_SCALE, TYPE_VEC2, {}) \ + X(changeRotation, CHANGE_ROTATION, TYPE_FLOAT, 0.0f) \ + X(changeDelay, CHANGE_DELAY, TYPE_INT, 0) \ + X(changeTint, CHANGE_TINT, TYPE_VEC4, {}) \ + X(changeColorOffset, CHANGE_COLOR_OFFSET, TYPE_VEC3, {}) \ + X(changeIsVisible, CHANGE_IS_VISIBLE, TYPE_BOOL, false) \ + X(changeIsInterpolated, CHANGE_IS_INTERPOLATED, TYPE_BOOL, false) \ + X(changeNumberFrames, CHANGE_NUMBER_FRAMES, TYPE_INT, 1) \ + \ + X(scaleValue, SCALE_VALUE, TYPE_FLOAT, 1.0f) \ + \ + X(previewIsAxes, PREVIEW_IS_AXES, TYPE_BOOL, true) \ + X(previewIsGrid, PREVIEW_IS_GRID, TYPE_BOOL, true) \ + X(previewIsRootTransform, PREVIEW_IS_ROOT_TRANSFORM, TYPE_BOOL, false) \ + X(previewIsTriggers, PREVIEW_IS_TRIGGERS, TYPE_BOOL, true) \ + X(previewIsPivots, PREVIEW_IS_PIVOTS, TYPE_BOOL, false) \ + X(previewIsTargets, PREVIEW_IS_TARGETS, TYPE_BOOL, true) \ + X(previewIsBorder, PREVIEW_IS_BORDER, TYPE_BOOL, false) \ + X(previewIsAltIcons, PREVIEW_IS_ALT_ICONS, TYPE_BOOL, false) \ + X(previewOverlayTransparency,PREVIEW_OVERLAY_TRANSPARENCY,TYPE_FLOAT, 255.0f) \ + X(previewZoom, PREVIEW_ZOOM, TYPE_FLOAT, 200.0f) \ + X(previewPan, PREVIEW_PAN, TYPE_VEC2, {}) \ + X(previewGridSize, PREVIEW_GRID_SIZE, TYPE_IVEC2, {32,32}) \ + X(previewGridOffset, PREVIEW_GRID_OFFSET, TYPE_IVEC2, {}) \ + X(previewGridColor, PREVIEW_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ + X(previewAxesColor, PREVIEW_AXES_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ + X(previewBackgroundColor, PREVIEW_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \ + \ + X(generateStartPosition, GENERATE_START_POSITION, TYPE_IVEC2, {}) \ + X(generateSize, GENERATE_SIZE, TYPE_IVEC2, {64,64}) \ + X(generatePivot, GENERATE_PIVOT, TYPE_IVEC2, {32,32}) \ + X(generateRows, GENERATE_ROWS, TYPE_INT, 4) \ + X(generateColumns, GENERATE_COLUMNS, TYPE_INT, 4) \ + X(generateCount, GENERATE_COUNT, TYPE_INT, 16) \ + X(generateDelay, GENERATE_DELAY, TYPE_INT, 1) \ + \ + X(editorIsGrid, EDITOR_IS_GRID, TYPE_BOOL, true) \ + X(editorIsGridSnap, EDITOR_IS_GRID_SNAP, TYPE_BOOL, true) \ + X(editorIsBorder, EDITOR_IS_BORDER, TYPE_BOOL, true) \ + X(editorZoom, EDITOR_ZOOM, TYPE_FLOAT, 200.0f) \ + X(editorPan, EDITOR_PAN, TYPE_VEC2, {0.0,0.0}) \ + X(editorGridSize, EDITOR_GRID_SIZE, TYPE_IVEC2, {32,32}) \ + X(editorGridOffset, EDITOR_GRID_OFFSET, TYPE_IVEC2, {32,32}) \ + X(editorGridColor, EDITOR_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \ + X(editorBackgroundColor, EDITOR_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \ + \ + X(mergeType, MERGE_TYPE, TYPE_INT, ANM2_MERGE_APPEND_FRAMES) \ + X(mergeIsDeleteAnimationsAfter,MERGE_IS_DELETE_ANIMATIONS_AFTER,TYPE_BOOL, false) \ + \ + X(bakeInterval, BAKE_INTERVAL, TYPE_INT, 1) \ + X(bakeIsRoundScale, BAKE_IS_ROUND_SCALE, TYPE_BOOL, true) \ + X(bakeIsRoundRotation, BAKE_IS_ROUND_ROTATION, TYPE_BOOL, true) \ + \ + X(onionskinIsEnabled, ONIONSKIN_IS_ENABLED, TYPE_BOOL, false) \ + X(onionskinDrawOrder, ONIONSKIN_DRAW_ORDER, TYPE_INT, ONIONSKIN_BELOW) \ + X(onionskinBeforeCount, ONIONSKIN_BEFORE_COUNT, TYPE_INT, 1) \ + X(onionskinAfterCount, ONIONSKIN_AFTER_COUNT, TYPE_INT, 1) \ + X(onionskinBeforeColorOffset,ONIONSKIN_BEFORE_COLOR_OFFSET,TYPE_VEC3, COLOR_RED) \ + X(onionskinAfterColorOffset, ONIONSKIN_AFTER_COLOR_OFFSET,TYPE_VEC3, COLOR_BLUE) \ + \ + X(tool, TOOL, TYPE_INT, TOOL_PAN) \ + X(toolColor, TOOL_COLOR, TYPE_VEC4, {1.0,1.0,1.0,1.0}) \ + \ + X(renderType, RENDER_TYPE, TYPE_INT, RENDER_PNG) \ + X(renderPath, RENDER_PATH, TYPE_STRING, ".") \ + X(renderFormat, RENDER_FORMAT, TYPE_STRING, "{}.png") \ + X(ffmpegPath, FFMPEG_PATH, TYPE_STRING, "") + +#define X(name, symbol, type, ...) \ +const inline DATATYPE_TO_CTYPE(type) SETTINGS_##symbol##_DEFAULT = __VA_ARGS__; +SETTINGS_LIST +#undef X + +struct Settings +{ + #define X(name, symbol, type, ...) \ + DATATYPE_TO_CTYPE(type) name = SETTINGS_##symbol##_DEFAULT; + SETTINGS_LIST + #undef X +}; + struct SettingsEntry { std::string key; DataType type; s32 offset; - bool isWidthHeight = false; }; -struct Settings +const inline SettingsEntry SETTINGS_ENTRIES[] = { - ivec2 windowSize = {1600, 900}; - bool isVsync = true; - bool playbackIsLoop = true; - bool playbackIsClampPlayhead = true; - bool changeIsCrop = false; - bool changeIsSize = false; - bool changeIsPosition = false; - bool changeIsPivot = false; - bool changeIsScale = false; - bool changeIsRotation = false; - bool changeIsDelay = false; - bool changeIsTint = false; - bool changeIsColorOffset = false; - bool changeIsVisibleSet = false; - bool changeIsInterpolatedSet = false; - bool changeIsFromSelectedFrame = false; - vec2 changeCrop{}; - vec2 changeSize{}; - vec2 changePosition{}; - vec2 changePivot{}; - vec2 changeScale{}; - f32 changeRotation{}; - s32 changeDelay{}; - vec4 changeTint{}; - vec3 changeColorOffset{}; - bool changeIsVisible{}; - bool changeIsInterpolated{}; - s32 changeNumberFrames = 1; - f32 scaleValue = 1.0f; - bool previewIsAxes = true; - bool previewIsGrid = true; - bool previewIsRootTransform = false; - bool previewIsTriggers = true; - bool previewIsPivots = false; - bool previewIsTargets = true; - 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 previewAxesColor = {1.0, 1.0, 1.0, 0.125}; - vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0}; - ivec2 generateStartPosition = {0, 0}; - ivec2 generateSize = {64, 64}; - ivec2 generatePivot = {32, 32}; - s32 generateRows = 4; - s32 generateColumns = 4; - s32 generateCount = 16; - s32 generateDelay = 1; - bool editorIsGrid = true; - bool editorIsGridSnap = true; - bool editorIsBorder = true; - 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}; - s32 mergeType = ANM2_MERGE_APPEND_FRAMES; - bool mergeIsDeleteAnimationsAfter = false; - s32 bakeInterval = 1; - bool bakeIsRoundScale = true; - bool bakeIsRoundRotation = true; - s32 tool = TOOL_PAN; - vec4 toolColor = {1.0, 1.0, 1.0, 1.0}; - s32 renderType = RENDER_PNG; - std::string renderPath = "."; - std::string renderFormat = "{}.png"; - std::string ffmpegPath{}; -}; - -const SettingsEntry SETTINGS_ENTRIES[] = -{ - {"window", TYPE_IVEC2, offsetof(Settings, windowSize), true}, - {"isVsync", TYPE_BOOL, offsetof(Settings, isVsync)}, - {"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)}, - {"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)}, - {"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)}, - {"changeIsSize", TYPE_BOOL, offsetof(Settings, changeIsSize)}, - {"changeIsPosition", TYPE_BOOL, offsetof(Settings, changeIsPosition)}, - {"changeIsPivot", TYPE_BOOL, offsetof(Settings, changeIsPivot)}, - {"changeIsScale", TYPE_BOOL, offsetof(Settings, changeIsScale)}, - {"changeIsRotation", TYPE_BOOL, offsetof(Settings, changeIsRotation)}, - {"changeIsDelay", TYPE_BOOL, offsetof(Settings, changeIsDelay)}, - {"changeIsTint", TYPE_BOOL, offsetof(Settings, changeIsTint)}, - {"changeIsColorOffset", TYPE_BOOL, offsetof(Settings, changeIsColorOffset)}, - {"changeIsVisibleSet", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)}, - {"changeIsInterpolatedSet", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)}, - {"changeIsFromSelectedFrame", TYPE_BOOL, offsetof(Settings, changeIsFromSelectedFrame)}, - {"changeCrop", TYPE_VEC2, offsetof(Settings, changeCrop)}, - {"changeSize", TYPE_VEC2, offsetof(Settings, changeSize)}, - {"changePosition", TYPE_VEC2, offsetof(Settings, changePosition)}, - {"changePivot", TYPE_VEC2, offsetof(Settings, changePivot)}, - {"changeScale", TYPE_VEC2, offsetof(Settings, changeScale)}, - {"changeRotation", TYPE_FLOAT, offsetof(Settings, changeRotation)}, - {"changeDelay", TYPE_INT, offsetof(Settings, changeDelay)}, - {"changeTint", TYPE_VEC4, offsetof(Settings, changeTint)}, - {"changeColorOffset", TYPE_VEC3, offsetof(Settings, changeColorOffset)}, - {"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)}, - {"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)}, - {"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)}, - {"scaleValue", TYPE_FLOAT, offsetof(Settings, scaleValue)}, - {"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)}, - {"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)}, - {"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)}, - {"previewIsTriggers", TYPE_BOOL, offsetof(Settings, previewIsTriggers)}, - {"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)}, - {"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)}, - {"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)}, - {"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)}, - {"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)}, - {"generateStartPosition", TYPE_IVEC2, offsetof(Settings, generateStartPosition)}, - {"generateSize", TYPE_IVEC2, offsetof(Settings, generateSize)}, - {"generatePivot", TYPE_IVEC2, offsetof(Settings, generatePivot)}, - {"generateRows", TYPE_INT, offsetof(Settings, generateRows)}, - {"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)}, - {"generateCount", TYPE_INT, offsetof(Settings, generateCount)}, - {"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)}, - {"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)}, - {"mergeType", TYPE_INT, offsetof(Settings, mergeType)}, - {"mergeIsDeleteAnimationsAfter", TYPE_BOOL, offsetof(Settings, mergeIsDeleteAnimationsAfter)}, - {"bakeInterval", TYPE_INT, offsetof(Settings, bakeInterval)}, - {"bakeRoundScale", TYPE_BOOL, offsetof(Settings, bakeIsRoundScale)}, - {"bakeRoundRotation", TYPE_BOOL, offsetof(Settings, bakeIsRoundRotation)}, - {"tool", TYPE_INT, offsetof(Settings, tool)}, - {"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)}, - {"renderType", TYPE_INT, offsetof(Settings, renderType)}, - {"renderPath", TYPE_STRING, offsetof(Settings, renderPath)}, - {"renderFormat", TYPE_STRING, offsetof(Settings, renderFormat)}, - {"ffmpegPath", TYPE_STRING, offsetof(Settings, ffmpegPath)} + #define X(name, symbol, type, ...) \ + { #name, type, offsetof(Settings, name) }, + SETTINGS_LIST + #undef X }; + constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); -const std::string SETTINGS_DEFAULT = R"( -[Settings] -windowW=1600 -windowH=900 -isVsync=true -playbackIsLoop=true -playbackIsClampPlayhead=false -changeIsCrop=false -changeIsSize=false -changeIsPosition=false -changeIsPivot=false -changeIsScale=false -changeIsRotation=false -changeIsDelay=false -changeIsTint=false -changeIsColorOffset=false -changeIsVisibleSet=false -changeIsInterpolatedSet=false -changeIsFromSelectedFrame=false -changeCropX=0.000 -changeCropY=0.000 -changeSizeX=0.000 -changeSizeY=0.000 -changePositionX=0.000 -changePositionY=0.000 -changePivotX=0.000 -changePivotY=0.000 -changeScaleX=0.000 -changeScaleY=0.000 -changeRotation=0.000 -changeDelay=1 -changeTintR=0.000 -changeTintG=0.000 -changeTintB=0.000 -changeTintA=0.000 -changeColorOffsetR=0.000 -changeColorOffsetG=0.000 -changeColorOffsetB=0.000 -changeIsVisible=false -changeIsInterpolated=false -changeNumberFrames=1 -scaleValue=1.000 -previewIsAxes=true -previewIsGrid=false -previewIsRootTransform=true -previewIsTriggers=false -previewIsPivots=false -previewIsTargets=true -previewIsBorder=false -previewOverlayTransparency=255.000 -previewZoom=400.000 -previewPanX=0.000 -previewPanY=0.000 -previewGridSizeX=32 -previewGridSizeY=32 -previewGridOffsetX=16 -previewGridOffsetY=16 -previewGridColorR=1.000 -previewGridColorG=1.000 -previewGridColorB=1.000 -previewGridColorA=0.125 -previewAxesColorR=1.000 -previewAxesColorG=1.000 -previewAxesColorB=1.000 -previewAxesColorA=0.125 -previewBackgroundColorR=0.114 -previewBackgroundColorG=0.184 -previewBackgroundColorB=0.286 -previewBackgroundColorA=1.000 -generateStartPositionX=0 -generateStartPositionY=0 -generateSizeX=0 -generateSizeY=0 -generatePivotX=0 -generatePivotY=0 -generateRows=4 -generateColumns=4 -generateCount=16 -generateDelay=1 -editorIsGrid=true -editorIsGridSnap=true -editorIsBorder=true -editorZoom=400.000 -editorPanX=0.000 -editorPanY=0.000 -editorGridSizeX=32 -editorGridSizeY=32 -editorGridOffsetX=16 -editorGridOffsetY=16 -editorGridColorR=1.000 -editorGridColorG=1.000 -editorGridColorB=1.000 -editorGridColorA=0.125 -editorBackgroundColorR=0.113 -editorBackgroundColorG=0.183 -editorBackgroundColorB=0.286 -editorBackgroundColorA=1.000 -mergeType=1 -mergeIsDeleteAnimationsAfter=false -bakeInterval=1 -bakeRoundScale=true -bakeRoundRotation=true -tool=0 -toolColorR=0.000 -toolColorG=0.000 -toolColorB=0.000 -toolColorA=1.000 -renderType=0 -renderPath=. -renderFormat={}.png -ffmpegPath= +#define HOTKEY_LIST \ + X(NONE, "None") \ + X(CENTER_VIEW, "Center View") \ + X(ZOOM_IN, "Zoom In") \ + X(ZOOM_OUT, "Zoom Out") \ + X(PLAY_PAUSE, "Play/Pause") \ + X(ONIONSKIN, "Onionskin") \ + X(NEW, "New") \ + X(OPEN, "Open") \ + X(SAVE, "Save") \ + X(SAVE_AS, "Save As") \ + X(EXIT, "Exit") \ + X(PAN, "Pan") \ + X(MOVE, "Move") \ + X(ROTATE, "Rotate") \ + X(SCALE, "Scale") \ + X(CROP, "Crop") \ + X(DRAW, "Draw") \ + X(ERASE, "Erase") \ + X(COLOR_PICKER, "Color Picker") \ + X(UNDO, "Undo") \ + X(REDO, "Redo") \ + X(COPY, "Copy") \ + X(CUT, "Cut") \ + X(PASTE, "Paste") \ + +typedef enum +{ + #define X(name, str) HOTKEY_##name, + HOTKEY_LIST + #undef X + HOTKEY_COUNT +} HotkeyType; +const inline char* HOTKEY_STRINGS[] = +{ + #define X(name, str) str, + HOTKEY_LIST + #undef X +}; + +using HotkeyMember = std::string Settings::*; + +const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] = +{ + nullptr, + &Settings::hotkeyCenterView, + &Settings::hotkeyZoomIn, + &Settings::hotkeyZoomOut, + &Settings::hotkeyPlayPause, + &Settings::hotkeyOnionskin, + &Settings::hotkeyNew, + &Settings::hotkeyOpen, + &Settings::hotkeySave, + &Settings::hotkeySaveAs, + &Settings::hotkeyExit, + &Settings::hotkeyPan, + &Settings::hotkeyMove, + &Settings::hotkeyRotate, + &Settings::hotkeyScale, + &Settings::hotkeyCrop, + &Settings::hotkeyDraw, + &Settings::hotkeyErase, + &Settings::hotkeyColorPicker, + &Settings::hotkeyUndo, + &Settings::hotkeyRedo, + &Settings::hotkeyCopy, + &Settings::hotkeyCut, + &Settings::hotkeyPaste +}; + +const std::string SETTINGS_IMGUI_DEFAULT = R"( # Dear ImGui [Window][## Window] Pos=0,32