From b6d6c878e2795fddd4a358a0714b6abaf182652d Mon Sep 17 00:00:00 2001 From: b-guild Date: Thu, 28 Nov 2024 03:38:48 -0800 Subject: [PATCH 01/39] Rough working version of new tile set editor. --- editor/resources/die.png | Bin 0 -> 4883 bytes editor/resources/flip_x.png | Bin 0 -> 796 bytes editor/resources/flip_y.png | Bin 0 -> 647 bytes editor/resources/palette.png | Bin 0 -> 2004 bytes editor/resources/turn_left.png | Bin 0 -> 852 bytes editor/resources/turn_right.png | Bin 0 -> 842 bytes editor/src/asset/item.rs | 39 +- editor/src/interaction/mod.rs | 27 + editor/src/message.rs | 3 +- editor/src/plugins/collider/mod.rs | 2 +- editor/src/plugins/tilemap/colliders_tab.rs | 454 +++++ editor/src/plugins/tilemap/commands.rs | 958 +++++++++- editor/src/plugins/tilemap/handle_editor.rs | 209 +++ editor/src/plugins/tilemap/mod.rs | 1061 +++++++---- editor/src/plugins/tilemap/palette.rs | 1595 +++++++++++----- editor/src/plugins/tilemap/panel.rs | 882 +++++---- editor/src/plugins/tilemap/panel_preview.rs | 240 +++ editor/src/plugins/tilemap/preview.rs | 50 +- editor/src/plugins/tilemap/properties_tab.rs | 949 ++++++++++ .../src/plugins/tilemap/tile_bounds_editor.rs | 308 ++++ editor/src/plugins/tilemap/tile_inspector.rs | 961 ++++++++++ .../src/plugins/tilemap/tile_prop_editor.rs | 668 +++++++ editor/src/plugins/tilemap/tile_set_import.rs | 4 +- editor/src/plugins/tilemap/tileset.rs | 824 +++++---- editor/src/scene/container.rs | 47 +- editor/src/scene_viewer/mod.rs | 5 +- fyrox-impl/src/material/mod.rs | 7 +- fyrox-impl/src/material/shader/mod.rs | 22 +- .../src/material/shader/standard/tile.shader | 121 ++ fyrox-impl/src/scene/dim2/collider.rs | 16 +- fyrox-impl/src/scene/dim2/physics.rs | 18 +- fyrox-impl/src/scene/tilemap/brush.rs | 330 +++- fyrox-impl/src/scene/tilemap/mod.rs | 1049 +++++++---- fyrox-impl/src/scene/tilemap/resource_grid.rs | 270 +++ fyrox-impl/src/scene/tilemap/tile_rect.rs | 701 +++++++ fyrox-impl/src/scene/tilemap/tile_source.rs | 441 +++++ fyrox-impl/src/scene/tilemap/tileset.rs | 1634 ++++++++++++++++- fyrox-impl/src/scene/tilemap/transform.rs | 479 +++++ fyrox-impl/src/scene/tilemap/update.rs | 760 ++++++++ fyrox-ui/src/draw.rs | 24 +- fyrox-ui/src/text_box.rs | 1 + 41 files changed, 12900 insertions(+), 2259 deletions(-) create mode 100644 editor/resources/die.png create mode 100644 editor/resources/flip_x.png create mode 100644 editor/resources/flip_y.png create mode 100644 editor/resources/palette.png create mode 100644 editor/resources/turn_left.png create mode 100644 editor/resources/turn_right.png create mode 100644 editor/src/plugins/tilemap/colliders_tab.rs create mode 100644 editor/src/plugins/tilemap/handle_editor.rs create mode 100644 editor/src/plugins/tilemap/panel_preview.rs create mode 100644 editor/src/plugins/tilemap/properties_tab.rs create mode 100644 editor/src/plugins/tilemap/tile_bounds_editor.rs create mode 100644 editor/src/plugins/tilemap/tile_inspector.rs create mode 100644 editor/src/plugins/tilemap/tile_prop_editor.rs create mode 100644 fyrox-impl/src/material/shader/standard/tile.shader create mode 100644 fyrox-impl/src/scene/tilemap/resource_grid.rs create mode 100644 fyrox-impl/src/scene/tilemap/tile_rect.rs create mode 100644 fyrox-impl/src/scene/tilemap/tile_source.rs create mode 100644 fyrox-impl/src/scene/tilemap/transform.rs create mode 100644 fyrox-impl/src/scene/tilemap/update.rs diff --git a/editor/resources/die.png b/editor/resources/die.png new file mode 100644 index 0000000000000000000000000000000000000000..b10b1790aa200022d20483982b9df762a6653dbd GIT binary patch literal 4883 zcmV+u6YT7XP)t8HCsX>F_3TD5hl8z}D9 z6{U(66cI&`HHa*-$d(8sgq@J&zCUc0AqfOU-|IWyH{X|;x%bXJ=brmJzjJ27z>5`w zpSEn9659VTzdz(U_juiZ&$6@u2eKP;h!!WHvT;R z%T=pICZ?t&GAfG5nwp4$5RFH*w4Q9L0iR=eivWh|UwFIsGN-9L;%kcFC0GT!Kt*STPTQ=gq zuK2ODvDWL>txxy$^(8A;ts<2oQMazWX3f`H4X|?c>Tdhr$-YOzUVEF^@tnx)1@C0N z@xe;#|BCY4ckDbcb?Q`QSXdaz77BIV+nK@~(x*=!HRXMMeMm;OZsW8nl;lM8MKX9q z2oM% z{N(VS%{!mVim%`B)41^P@G<}-A|irFWwNJ2_pXiy6%}Ok=nysUU@|EZbLq0ye)%O2 z$OoUTA^^yQ8LyU2d-d(%y{7obpRRG*uyNz9fPg@3}skY7)eV{e{PCDUdj7K z#D8FbMgtr>enO`)TIB5{;^-;jH*g4XboWtBm_2`gk1^oQx8CD%Z7kBFqN2Ea_Uyr^ zF=L>wVV_GRQsn37p}edNR#sMUadw7_n;RI6r=u5y08*(0DJdz)$reH`Z-cXoD+Ui4 z0(W;es0cwxQ4vZ?N+FTdg2(5>$J-km4hN*uST&M5Y}>UD8`gde{~;qpP_gFTQlO~bU2M@Hk8zHC9o+Av3Qd_CzUwla#T3UMDI~rSB$nq~&s67t= zIeF#`Y1h(kXXFuL%Xc9IhK;EOV9>1rmMvR;htKDe(9lqln3SY((5jkR!eX(s?$?CG z8@+CTq+7{apT}ach`72&>$b9YbR~u+=48Z#8P^$daC37rG&MD$p`ih>mo8yMa4>ARTza-cQTTT4x0o<-0+Mgu zL}_seEI5`33=G8RkTI~au||8l0;Q!TkV+(AQcT$M9AIy6|I`Msv$I2V%pbTKACEh! zDUe7caCUJ)(9mHRF=99=k_f<0defOz7$9fo))j#zxsL03jMdaOTXJ zO_`aQMni)~!^h7bHR1~BGgKHqaUuo>1;N<`rQ1(j|zC`;&J4lTSaxvM;`X znW@QhW2g#+0vQ>Z*tvTjgt-N7=KKKxp zPQEZQHib&5M2)BnMY-wFV>0lse@#Yze}Ax8`dx;VaiO6|x_({b{R0ONV)pFWYN-9J zLI^==X(^%(9YIX&RXDnNgJa8s9_z`LZ<5yF{ILikt*)drZh`FVYz!C_goll)N3PCf zg3EIOhs%Swya>OaIEA?Q1WcVW89v^gU@|H7i2w)*8LQC%gb?s}yk1vcB9-D?^aVs7 zJPr$MTL^}Xfrc8vh z6JIUv%jNBei;Kg#b7!$@#}4ovow4Y>MVK^c5(NGI!2lqW$siVs0RXnPwlFt0@3G>o zt!+q5OvH}e5onSt!FTh8fw7s^_RaM&9N6+TP46BV;PF6>4JgjdLUCaph7J$HqzR$m z+E{D!2CBnv0H~{zVcWKC`0kr;9xo6f1S?m6g%1`lhJ{77QBi^2dn57lwq4LOvV^l&Kd_BV zbxLg8+FFpEd<_7?HDUk&kWTI_seOpttaRAg@!;Xxmo{lLOKS`oIUY5l3dAQQ;nL+e zygX+Hf(8fD4AIdb78aITCunKO(Vjxf>T%)XMMOj$g^>jZet{#w)MsmK`>?zeGD$TY zoIPM{ZqaE8=%Q`_FlY@UxBZ0V#H#>+d2fG&sk2@MqeIN4C<_2oI z7h+=Z>MOG#=Fl?CMHjrtd2W!Id0xc#&3Hf zA#H4dvrm5*7@31;P=*kKtmLamOSl98aP;uSxS6kap}qz$jZ4lzee09#Wcug6LnsoQmJq+FCP*6 z4CJY zG!Kg0z8MO6yVgmt*#_u0a4@EYEr6^=iRCN4#-6(`U}&?e~`;{#F`%1`dUly$itT{&j$? z_95!)=-IA|r*F3gP{ROKr#7BGeKBR$T%137_=%g#+jJ{9G8o|6Ibg!HStz+*fc=M# z;pC|^m_P3o3>+8$GgDKLziNc3si`<}^aSdg+u-2d2L{GwP!Z@%nv@C!?c8VJFmU-U z-S*Sd7`$mpPzZbd9Z-6DICty-IM!TnZF`o~FqwLA_wt36H5X~OlMw#R_h@TtgN2n1 z7z_qvby6tW* zBf?gTMKCfngb6LQ^h}gWC2l7r;KYGFsFT(}+1?IAV-pM=I~iOj4=@L2GD1- z;oWcGGb#VK;~E$m!NlBBqw?CqpxP6aq<2!4yIE;izGxnnz8Qv-2lqZIN<3zgEQ}gA z2}Z`I&}Xs1ck_KF<$IDbc8dW@MsFB^psK^lA3v}QM#jdNG~<=W*X(&bxO#en5Wv{n z^2NN)(j^S4RH|Ni>1<&|PmUsY{ETW*C5Nt&6BAJkaI9>g&oTr>_58KJytNhelA5PLitdO|4?G|_F&>Lwn}J2I zPC?e4TeP5J$+5;eOTM7B7m4UaR*FG^K{0y#9-+J_4@bAH#mPMzQFJ%+8PwOv1c|O* zjt7OgkBCug3%>jEW8BM5qm2_XaT;dLTi9XWJ9Zj_=>ELFL|j&c%fClJseA;gwc^TW z+5_rLum=#pHlp+R{l9HR_@p*wA5)Dy+KBKscXYqE}L~IQI95rJ4!`(f7FnatH+O^t;Vx1!e=^5stK_;Om zJKh3tc}_j#i`^NZ!;C13!Y@Cp0RW^XUBlWHOCXch(m4LyH{XYUV9*mzZf34KBoWmk zGw5gqLqp@ozis($7&`7HT5+cPEuhi`15~u%0|En)lbsC!s46SL`BO(Q|E&)}$fKH! zxuq38{ptq@GwuL{piifLuG5~>F$&l_yCLMI1)wMfcrG4b>a%)CPrC5{&prak+vuO^ zjtHGbJ3n_U3X+;?Iv5%lAs{Fi0YgWEV(K2#DT;bza2-8>qL^^;_JfmWpPnlZptYqD z>`rBKngP7~^lK`AP(o`EI5ZeGTw6LAbpoQNBZ~|${@#->hzZt+t6<5suh(h-TL*r^ zm6$U$#5Xs$#FACtL!o$52GiG4jqNRZ`aDFTXh&gAIt-0XZ)wGV<|g^aRTbqE?`Een z`~(9ZvnO|NKWsmE4#|n}Ff}uWlZ$(=X#NOCUW^6qW!*ueObTmj`^8#4;K<&e3r2)Y zh~Bqt6RO1`S}nlVfsbin3lK7KT95X5*KvTn5Epn@RgT;7G2pp+MdxQH(};?iCK)M7 zM}x;qeD`YX1ygr#Uzj~*^+zVH#r6&1$jcSN-oX(jreB+Ena_dB*beF2K5?nrg2v)WZVbSTnDFECX;1VBuri2qgww4 z-UElfQhG1LyC^3`sH22YHzzYpcp~dS1PJ2Js&>%G%n&;rQ)@DJGvVVuL_-&%PLT;gWmyT*Zv2Vrs&Y8G`@}ZZN#=+rZdlQ0$pw&N)IMtgN}PwR=K&TUwfN z>HI0wh^xSJ_5f2)zXKO|fW+7{5T+!;-pM^dkEuWEzA)t=(EFLwb5F$k4+{-dDBAW2 zZznj7nmij*XTA($BO_e99D_g39tXgHSHA$bc=ZLPr-$6MWMtingPUJK8JlewmYbG% z3;z!i5IkY2vyY%z-@u4i+3^Y6(3r3djft_D1u-$_5DwRYF!fl3@8Q?r8xXt%|F0ql z8oR>L!>`!f#$HY_^$Eq)Czf2E+|k3Y#N97o-HV>je*hpWoYs&Pr*{AV002ovPDHLk FV1jJmN>l&< literal 0 HcmV?d00001 diff --git a/editor/resources/flip_x.png b/editor/resources/flip_x.png new file mode 100644 index 0000000000000000000000000000000000000000..377fb6325cf5427b948b545b187a22761e18fd62 GIT binary patch literal 796 zcmV+%1LOROP)dxhPhkWp{RS~95|CqT z_|vRF7I)U&?dNwO&ED_q|o z<~wO*4dwto0w~+$!1c|0DFDgO04DC|PRWo}+r*9SodZq*?A92!>&Es+0Fs{oOdC8h zbYe;JAfHLTL25^ov(HfGpMfR&KcP%3KNEfXjsf`1wpyv!}qZK5z5ug-$L}Y>V zQ3MrW3-oj$8beZ32c&?WpyjbP$bKs52?|N-scbCh3FZQ=nf=tz6AY5-I-m>C6XZtF z8rkm(^aO#VIu7Ux^aOz<%?x_XA!(Zfx&}Swkfa$xk0~T+hR|aQNt!A2m|cmQDfE~@ zl4cA&Cgrha3_T{0q?tpHw+l3L=<$Xm?E&<7-3ZzP=<$Li-2y%T0DSdakR21?RfW6b zvcF$ZGk*Z^n>63U-NPNh`9JK09PgLSFSHs z9IOj)1K=gXqDuf{5m|0_xVEty6FOepo?q1fCnWC}CLT$CCwa8>#3Ya0*vCNca!2xH awcdXsHUYGJ9T4aM0000P~7l7wx_B9vc14)l0 zeb*8GNV?&j_)yXlNmHGSw_b^lB<*Sjcq2ZR^jZP%K)NUCj--#Ofi@HGOS&)Vo92KQ zNB1wl436$ofDlJ_B|sfVZ@mEXI(l6JiWD3jSO+$NLDTPF#B}tM^g7Uw^*M?N#D~BJ za2x2wK7mDp#7DpbV4RZo6b%p`m&8XYCQh*=(!KJCZvexT?;6E|iAQzxXac0-=#2}I znxi)+K&p4;Y)-8P7U`hA^d;oULY@ZGtI&|nv@JN7FV3Ju3z5zG` zU}j&+d;U}4mvaFIz$;0Y|G5cfcop6?vzwOl!;qP+oZ1qb<8k;I@E%BE1}9+qSOS=Z zwd86HEniByp2F9?(BWu}vGqGiwnTVhQm zhsT;+P;z)LN}ie=xm0EUn;f~7rH>^?u66mp$6yv h9EYp3-STwqH~{U4SQ6+7VD9NGW? literal 0 HcmV?d00001 diff --git a/editor/resources/palette.png b/editor/resources/palette.png new file mode 100644 index 0000000000000000000000000000000000000000..a09d981c443c12d219c776727e10d4581a8e6018 GIT binary patch literal 2004 zcmV;_2P^oAP)#19oIL2Rpn z6-$bKXi-5w6vPUxF-^oE^@AT8HNx7O#3ascHj|y5mpkX)`}kpJhH>0^+nJr+X&+cv z=A8T7^Z%dcJTK>*D{O

R#YR;CkTZuBIYzPwW3Pz^Sgb5^w^jh{&I(H5Qo>;2iJ{ z;5Oh(fDQPYRR>gCWc&sAA+RJO5j#y(^)ssaM^!btwQ@#PA5qo)I|1}ywoRsHv*qmScQ<2arM5nohQcT0VMs#>iz_z__5q(s*1b(+m4p6B7ZE{@|& zs`5GDZV|aQbpllNQ@{^^+%&9cWo3os_`XlESj4vNNmc(2xK~6<8#Owc z0Dcg73V7?ZmQcA|=E8*wR4Ns!)hd={;rl-6bedEuHL3dBfu~gURuKt$8?7~44pU>lY9wPl$TO7wkQA8qZ^dKfp-o9zq-20<;$0;R;!2z*=&|XA^|Xz1jlg@5k!PIj94NCw`V?k)NwMwm4!}t9T35`Yr-}kB2Y9tbgK}#pA zs-F~*CpL@%sw%*Pz+I!vylva$ayb%-1VIpV29n8Sa9x)|p@8eUBRMk1aXPc!kFYQd zsZ=W1woS9yq+YKR1Odrpl1wHuYV!1jzKM{k-VZ!FZk%a{u2QLlZQJ<1PoYqlMjZb? z3uuF0US6hDD&e{=`Fx&qI!&Qa=<9E~g9flqL@rB@@*?meklyUz+F?*cMxOc2yayl% z0#;U5FvgHdr3O0N4dg2#@^CK!CxQ1eN7D8IZD%`%JS!r1NEi4!f#34V5d-AADf=w1 zEOCLGtk&$`*tHk;N|JZCW5)gSjH0_{=nbDkGQ)kMsy1*D*fX!-8;u6W7~(koALp1z zBs$4h9|w8b0p2vP;M?VNy8x?HDxGvX3`5fCG`U=^Q)X|lxpKg1ZD-A`>4#xRv)QCp ztFgSiOcX_(S#ReG<#HL{_ctn*_FJqUPRymW>kwlM*REab{MN2YjWJxkdKKHYab0&q zf8dN-?d?>tEDPJV@jMUD^N6B|AP5i<>h(Hd7;@;)q5kDKGddmGN-J1cSfE%eb~aSn zOCt>z5h! ziO7Au;9KSv(C5Tu9%ONG@j~y8&|Sc5fTu-dB;ODO!2!?n&dnkI>d~V|Z=W~`I(znP z=J4Ue{}GYokgjFmA=a+QHk zvbebTh5U~aAd%X0#$wO2s&<sF;pIy0r(T`Q{hP+-y27CHcyVIPgHeniydF6s;`?? z*t<5yi{Czi&M(iv^FJ`Hu=mWEFMgqQ_Rvq?yHvHY#o%8YG=_1&zkibh_@}Dg+oNlb zsy;YE7CEV`4l;cilYke1o93wCiGdG_NZ(U$Yn<7iGw=Xk9Rz;dF!-K11OFWnd1P3p zaR|6`j=+CIM7};s$8j!8@0}CypJ)9FKhq{)9^{CK92@6>aR|7wgMlvr9~F^5jQij? zKHzUV6#TD%w`~UeSOlCqcTRU0@EY&|5xGZ1E^b!I$ON1@bLR980bU2b3G5jbWLYEzM!fPw+?EyRAdtbSge1u`{pT+ z<#g-s=OXg_j?Okz)lt>&4>=F$Cq`8(t$l(ARP~nDr#agqlYTjRJ@7H$hSvVzbv<34 m0T#Pv_&jh?M9%J%$^0)X?m?N!ni9$Y0000W literal 0 HcmV?d00001 diff --git a/editor/resources/turn_left.png b/editor/resources/turn_left.png new file mode 100644 index 0000000000000000000000000000000000000000..0f98456d27a078bbd29fcafcb20b2568204e569f GIT binary patch literal 852 zcmV-a1FQUrP)^A%%Y%tKwt_5fr}(s2$mv*t)fjtLF?K@x(pYAP!trj zvMgE%k`P4^m5)t@&?1#w{abh!LO%HX=H5H=`QO~ty)(aa&zw2u%os&1N!x)spbDr5 zwgHtudHn2&iVXN5&;lF-jsiQ^dM7FZ=r*7oI1~Tbyc7ireHiEjI)Ji5z7+)qejezH z-P?(x0H7n_5^x!)asB^U9|G>VboG@~;u&~OQa>=32E11uAgLL6 z5GN?9sl*fb+rV(z@R@r+oT?51C$gGDlp%Z_@EEAh@+U+Y!8ZZVfn3(huOtKbZs4U4 z@TnUhhVS=ePSP*O@YRGONq09g{8op7@3;4nei;J{ID(&*)DRl{ z4oM4+4A4txl61!r{G%-9Zw10{0e(8@?o0yv&1^Qbj!>7Q)>dy8@Gji{y#v=DB1B25 zca-Mt4YeuS;b8m$Gy9UI0jvNImja^<7H|{;AKU??A%vui}Kv>-ztGLhuuk%J5y1 zHc1+G3_lgx0Z1;sk>Tplvx(3osZ`Ra0|SHyzr+Z;*+;(emyzz997Bs!IB1T9lM{R!@}g99VnT9W8AvJoYz eU((qS=K2p&DO9@i4#mL$0000cCczI)Dm4_xl*9`5_xbIy0(_q;dM37`Ya z0JqcoU%(|`8u&3HZn02NCwwwD)syNT^{qAvc$7cYKu=8gI8zK=0DHq;}MyHVWu6k(R}# zwh90k2F|Jn+bW>TKJ|Nbd0PblYyd8(tJ^97U=;X49dD}ufFa;q%1j#XE&}mvi zJdziT03*Ok;GMKaUmJjkoe?paOS1H``iXic*VY=rMJw zW(m{kXzuldarJ7A5-#VKKm3=7&(gH5gD*;{E{&*XYLf6_NoBUrYY=c(9WF`2do?VH zZzu_Mm3pmW2_KavVYFfiw~Kfx)GXmJrIDTxcPiWvUo26=#`NW@V3HC^7baEkkGi5% z2`4L(aH!IHK!XAn1HYtlSU=1G%Oc`#Q6;hTg?Cj{O50AUs9#mE41O+Iz#A1Em)8_o z6^@ACfhrznPklU&3}(R+;0Ca$k6~^^#BgylAkBhVpx=VWE9$x;1tcz3FvjLmm%wu1 zYhX>ELrtf>;L1Ecb3bYd>IL9iAm6g~!$#1tsPUH_0)FCQn@ny@0~;b@rnWsGBIba1 zf$NzCe;pW$h?z$AfYe%hJ{?;J&3#~XMEus+Qy?NP16zQ50}g%;*wPz()4Tn42S*Qdb*+D_vV@g52@eP9DLqIQ}v}9ZjHJeE2SVZnwlW0(path)) { + Ok(tile_set) => { + Log::info(format!("send OpenTileSetEditor {tile_set:?}")); + sender.send(Message::OpenTileSetEditor(tile_set)) + } + Err(err) => Log::err(format!("Open tileset error: {err:?}")), + } + } + } else if self + .path + .extension() + .map_or(false, |ext| ext == "tile_map_brush") + { + Log::info(format!( + "Found tile_map_brush extension {:?} : {:?}", + self.path, + self.path.extension() + )); if let Ok(path) = make_relative_path(&self.path) { - if let Ok(tile_set) = block_on(resource_manager.request::(path)) { - sender.send(Message::OpenTileSetEditor(tile_set)); + match block_on(resource_manager.request::(path)) { + Ok(brush) => sender.send(Message::OpenTileMapBrushEditor(brush)), + Err(err) => Log::err(format!("Open tile_map_brush error: {err:?}")), } } } else if self.path.is_dir() { diff --git a/editor/src/interaction/mod.rs b/editor/src/interaction/mod.rs index cd0a90142..8df0357b9 100644 --- a/editor/src/interaction/mod.rs +++ b/editor/src/interaction/mod.rs @@ -116,6 +116,33 @@ pub trait InteractionMode: BaseInteractionMode { settings: &Settings, ); + /// Called when the mouse enters the scene viewer while this interaction mode is active. + #[allow(unused_variables)] + fn on_mouse_enter( + &mut self, + editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + frame_size: Vector2, + settings: &Settings, + ) { + } + + /// Called when the mouse leaves the scene viewer while this interaction mode is active. + /// - `mouse_position`: The position of the mouse relative to the scene viewer, with (0,0) being the left-top corner. + /// - `editor_selection`: The currently selected object in the editor. + #[allow(unused_variables)] + fn on_mouse_leave( + &mut self, + mouse_position: Vector2, + editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + frame_size: Vector2, + settings: &Settings, + ) { + } + fn update( &mut self, #[allow(unused_variables)] editor_selection: &Selection, diff --git a/editor/src/message.rs b/editor/src/message.rs index c35ab6fec..986d29395 100644 --- a/editor/src/message.rs +++ b/editor/src/message.rs @@ -33,7 +33,7 @@ use crate::{ scene::Selection, SaveSceneConfirmationDialogAction, }; -use fyrox::scene::tilemap::tileset::TileSetResource; +use fyrox::scene::tilemap::{brush::TileMapBrushResource, tileset::TileSetResource}; use std::{path::PathBuf, sync::mpsc::Sender}; #[derive(Debug)] @@ -66,6 +66,7 @@ pub enum Message { OpenAbsmEditor, OpenMaterialEditor(MaterialResource), OpenTileSetEditor(TileSetResource), + OpenTileMapBrushEditor(TileMapBrushResource), OpenNodeRemovalDialog, ShowInAssetBrowser(PathBuf), LocateObject { diff --git a/editor/src/plugins/collider/mod.rs b/editor/src/plugins/collider/mod.rs index 3b88b95be..68a3855af 100644 --- a/editor/src/plugins/collider/mod.rs +++ b/editor/src/plugins/collider/mod.rs @@ -250,7 +250,7 @@ fn make_shape_gizmo( ColliderShape::Triangle(_) => Box::new(Triangle2DShapeGizmo::new(root, visible, scene)), ColliderShape::Trimesh(_) | ColliderShape::Heightfield(_) - | ColliderShape::TileMap(_) => Box::new(DummyShapeGizmo), + | ColliderShape::TileMap(_, _) => Box::new(DummyShapeGizmo), } } else { Box::new(DummyShapeGizmo) diff --git a/editor/src/plugins/tilemap/colliders_tab.rs b/editor/src/plugins/tilemap/colliders_tab.rs new file mode 100644 index 000000000..ad9ddb251 --- /dev/null +++ b/editor/src/plugins/tilemap/colliders_tab.rs @@ -0,0 +1,454 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use fyrox::{ + fxhash::FxHashMap, + gui::{ + button::ButtonMessage, + color::{ColorFieldBuilder, ColorFieldMessage}, + grid::*, + list_view::{ListView, ListViewBuilder, ListViewMessage}, + numeric::NumericUpDownBuilder, + scroll_viewer::ScrollViewerBuilder, + stack_panel::StackPanelBuilder, + text::{TextBuilder, TextMessage}, + text_box::{TextBoxBuilder, TextBoxMessage, TextCommitMode}, + utils::{make_arrow, ArrowDirection}, + HorizontalAlignment, VerticalAlignment, + }, +}; + +use fyrox::scene::tilemap::{tileset::*, *}; + +use crate::{send_sync_message, MSG_SYNC_FLAG}; + +use super::*; +use commands::*; + +pub struct CollidersTab { + handle: Handle, + list: Handle, + tile_resource: TileResource, + up_button: Handle, + down_button: Handle, + remove_button: Handle, + add_button: Handle, + data_panel: Handle, + name_field: Handle, + color_field: Handle, +} + +fn make_arrow_button( + ctx: &mut BuildContext, + dir: ArrowDirection, + column: usize, + row: usize, +) -> Handle { + let arrow = make_arrow(ctx, dir, 16.0); + ButtonBuilder::new( + WidgetBuilder::new() + .on_column(column) + .on_row(row) + .with_height(24.0) + .with_margin(Thickness::uniform(1.0)), + ) + .with_content(arrow) + .build(ctx) +} + +fn make_button( + title: &str, + tooltip: &str, + ctx: &mut BuildContext, + column: usize, + row: usize, +) -> Handle { + ButtonBuilder::new( + WidgetBuilder::new() + .on_column(column) + .on_row(row) + .with_height(24.0) + .with_margin(Thickness::uniform(1.0)) + .with_tooltip(make_simple_tooltip(ctx, tooltip)), + ) + .with_text(title) + .build(ctx) +} + +pub fn make_list_item(ctx: &mut BuildContext, collider: &TileSetColliderLayer) -> Handle { + let content = GridBuilder::new( + WidgetBuilder::new() + .with_child( + BorderBuilder::new( + WidgetBuilder::new() + .with_horizontal_alignment(HorizontalAlignment::Center) + .with_vertical_alignment(VerticalAlignment::Center) + .with_width(16.0) + .with_height(16.0) + .with_background(Brush::Solid(collider.color)), + ) + .build(ctx), + ) + .with_child( + TextBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::left(5.0)) + .on_column(1), + ) + .with_vertical_text_alignment(VerticalAlignment::Center) + .with_horizontal_text_alignment(HorizontalAlignment::Left) + .with_text(&collider.name) + .build(ctx), + ), + ) + .add_row(Row::auto()) + .add_column(Column::strict(20.0)) + .add_column(Column::stretch()) + .build(ctx); + DecoratorBuilder::new( + BorderBuilder::new(WidgetBuilder::new().with_child(content)) + .with_corner_radius(4.0) + .with_pad_by_corner_radius(false), + ) + .build(ctx) +} + +pub fn make_items(ctx: &mut BuildContext, tile_set: &TileSet) -> Vec> { + tile_set + .colliders + .iter() + .map(|c| make_list_item(ctx, c)) + .collect() +} + +impl CollidersTab { + pub fn new(tile_resource: TileResource, ctx: &mut BuildContext) -> Self { + let items = if let TileResource::TileSet(t) = &tile_resource { + make_items(ctx, &t.data_ref()) + } else { + Vec::default() + }; + let properties_scroll = ScrollViewerBuilder::new(WidgetBuilder::new()).build(ctx); + let list = ListViewBuilder::new(WidgetBuilder::new().on_row(1)) + .with_items(items) + .with_scroll_viewer(properties_scroll) + .build(ctx); + let up_button = make_arrow_button(ctx, ArrowDirection::Top, 0, 0); + let down_button = make_arrow_button(ctx, ArrowDirection::Bottom, 1, 0); + let add_button = make_button("New", "Create a new collider.", ctx, 2, 0); + let up_down = GridBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .on_row(2) + .with_child(up_button) + .with_child(down_button) + .with_child(add_button), + ) + .add_row(Row::auto()) + .add_column(Column::stretch()) + .add_column(Column::stretch()) + .add_column(Column::stretch()) + .build(ctx); + let left_label = TextBuilder::new( + WidgetBuilder::new() + .with_horizontal_alignment(HorizontalAlignment::Center) + .with_margin(Thickness::uniform(2.0)), + ) + .with_text("Colliders:") + .build(ctx); + let left_side = GridBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .with_child(left_label) + .with_child(list) + .with_child(up_down), + ) + .add_row(Row::auto()) + .add_row(Row::stretch()) + .add_row(Row::strict(30.0)) + .add_column(Column::stretch()) + .build(ctx); + let name_text = TextBuilder::new( + WidgetBuilder::new() + .with_vertical_alignment(VerticalAlignment::Center) + .with_margin(Thickness::right(4.0)), + ) + .with_text("Name:") + .build(ctx); + let name_field = TextBoxBuilder::new(WidgetBuilder::new().with_height(20.0).on_column(1)) + .with_text_commit_mode(TextCommitMode::Changed) + .with_vertical_text_alignment(VerticalAlignment::Center) + .build(ctx); + let remove_button = make_button( + "Delete", + "Delete this collider from every tile in the tile set.", + ctx, + 2, + 0, + ); + let header = GridBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .with_child(name_text) + .with_child(name_field) + .with_child(remove_button), + ) + .add_column(Column::auto()) + .add_column(Column::stretch()) + .add_column(Column::strict(100.0)) + .add_row(Row::auto()) + .build(ctx); + let color_field = ColorFieldBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .on_row(1) + .with_height(30.0), + ) + .build(ctx); + let data_panel = GridBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .on_column(1) + .with_child(header) + .with_child(color_field), + ) + .add_row(Row::auto()) + .add_row(Row::auto()) + .add_column(Column::stretch()) + .build(ctx); + Self { + handle: GridBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(2.0)) + .with_child(left_side) + .with_child(data_panel), + ) + .add_row(Row::stretch()) + .add_column(Column::stretch()) + .add_column(Column::stretch()) + .build(ctx), + list, + tile_resource, + up_button, + down_button, + add_button, + remove_button, + data_panel, + name_field, + color_field, + } + } + pub fn handle(&self) -> Handle { + self.handle + } + pub fn sync_to_model(&mut self, tile_set: &TileSet, ui: &mut UserInterface) { + let items = make_items(&mut ui.build_ctx(), tile_set); + ui.send_message(ListViewMessage::items( + self.list, + MessageDirection::ToWidget, + items, + )); + self.sync_data(tile_set, ui); + } + fn sync_data(&mut self, tile_set: &TileSet, ui: &mut UserInterface) { + let sel_index = self.selection_index(ui); + let name = match sel_index { + Some(index) => tile_set + .colliders + .get(index) + .map(|c| c.name.to_string()) + .unwrap_or_default(), + None => String::default(), + }; + let color = match sel_index { + Some(index) => tile_set + .colliders + .get(index) + .map(|c| c.color) + .unwrap_or(Color::BLACK), + None => Color::BLACK, + }; + ui.send_message(WidgetMessage::enabled( + self.data_panel, + MessageDirection::ToWidget, + sel_index.is_some(), + )); + send_sync_message( + ui, + TextMessage::text(self.name_field, MessageDirection::ToWidget, name), + ); + send_sync_message( + ui, + ColorFieldMessage::color(self.color_field, MessageDirection::ToWidget, color), + ); + } + pub fn handle_ui_message( + &mut self, + tile_set: TileSetResource, + message: &UiMessage, + ui: &mut UserInterface, + sender: &MessageSender, + ) { + if message.direction() == MessageDirection::ToWidget || message.flags == MSG_SYNC_FLAG { + return; + } + if let Some(ListViewMessage::SelectionChanged(_)) = message.data() { + if message.destination() == self.list { + self.sync_data(&tile_set.data_ref(), ui); + } + } else if let Some(TextMessage::Text(value)) = message.data() { + if message.destination() == self.name_field { + self.update_name(tile_set, value, ui, sender); + } + } else if let Some(&ColorFieldMessage::Color(value)) = message.data() { + if message.destination() == self.color_field { + self.update_color(tile_set, value, ui, sender); + } + } else if let Some(ButtonMessage::Click) = message.data() { + if message.destination() == self.up_button { + self.move_layer(tile_set, -1, ui, sender); + } else if message.destination() == self.down_button { + self.move_layer(tile_set, 1, ui, sender); + } else if message.destination() == self.add_button { + self.add_layer(tile_set, ui, sender); + } else if message.destination() == self.remove_button { + self.remove_layer(tile_set, ui, sender); + } + } + } + fn selection_index(&self, ui: &UserInterface) -> Option { + ui.node(self.list) + .cast::()? + .selection + .last() + .copied() + } + fn update_name( + &self, + resource: TileSetResource, + name: &str, + ui: &UserInterface, + sender: &MessageSender, + ) { + let tile_set = resource.data_ref(); + let Some(sel_index) = self + .selection_index(ui) + .map(|i| i.clamp(0, tile_set.colliders.len() - 1)) + else { + return; + }; + let Some(uuid) = tile_set.colliders.get(sel_index).map(|l| l.uuid) else { + return; + }; + ui.send_message(WidgetMessage::focus( + self.name_field, + MessageDirection::ToWidget, + )); + sender.do_command(SetColliderLayerNameCommand { + tile_set: resource.clone(), + uuid, + name: name.into(), + }); + } + fn update_color( + &self, + resource: TileSetResource, + color: Color, + ui: &UserInterface, + sender: &MessageSender, + ) { + let tile_set = resource.data_ref(); + let Some(sel_index) = self + .selection_index(ui) + .map(|i| i.clamp(0, tile_set.colliders.len() - 1)) + else { + return; + }; + let Some(uuid) = tile_set.colliders.get(sel_index).map(|l| l.uuid) else { + return; + }; + sender.do_command(SetColliderLayerColorCommand { + tile_set: resource.clone(), + uuid, + color, + }); + } + fn move_layer( + &self, + resource: TileSetResource, + amount: isize, + ui: &UserInterface, + sender: &MessageSender, + ) { + let tile_set = resource.data_ref(); + let Some(sel_index) = self.selection_index(ui) else { + return; + }; + let new_index = sel_index + .saturating_add_signed(amount) + .clamp(0, tile_set.colliders.len() - 1); + if sel_index == new_index { + return; + } + ui.send_message(ListViewMessage::selection( + self.list, + MessageDirection::ToWidget, + vec![new_index], + )); + sender.do_command(MoveColliderLayerCommand { + tile_set: resource.clone(), + start: sel_index, + end: new_index, + }); + } + fn add_layer(&self, resource: TileSetResource, ui: &UserInterface, sender: &MessageSender) { + let tile_set = resource.data_ref(); + let index = self + .selection_index(ui) + .map(|i| i + 1) + .unwrap_or(0) + .clamp(0, tile_set.colliders.len()); + ui.send_message(ListViewMessage::selection( + self.list, + MessageDirection::ToWidget, + vec![index], + )); + sender.do_command(AddColliderLayerCommand { + tile_set: resource.clone(), + index, + uuid: Uuid::new_v4(), + }); + } + fn remove_layer(&self, resource: TileSetResource, ui: &UserInterface, sender: &MessageSender) { + let tile_set = resource.data_ref(); + let Some(index) = self + .selection_index(ui) + .map(|i| i.clamp(0, tile_set.colliders.len() - 1)) + else { + return; + }; + sender.do_command(RemoveColliderLayerCommand { + tile_set: resource.clone(), + index, + layer: None, + values: FxHashMap::default(), + }); + } +} diff --git a/editor/src/plugins/tilemap/commands.rs b/editor/src/plugins/tilemap/commands.rs index 546ffc9f4..b1f7c7984 100644 --- a/editor/src/plugins/tilemap/commands.rs +++ b/editor/src/plugins/tilemap/commands.rs @@ -18,83 +18,572 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use fyrox::{ + core::{color::Color, ImmutableString, Uuid}, + fxhash::FxHashMap, + material::MaterialResource, + scene::tilemap::{ + tileset::{ + AbstractTile, NamableValue, NamedValue, TileCollider, TileSetColliderLayer, + TileSetPage, TileSetPageSource, TileSetPropertyLayer, TileSetPropertyType, + TileSetPropertyValue, + }, + OrthoTransform, OrthoTransformation, TileDefinitionHandle, + }, +}; + use crate::{ command::{CommandContext, CommandTrait}, fyrox::{ - core::{algebra::Vector2, log::Log, Uuid}, + core::algebra::Vector2, scene::tilemap::{ - brush::{BrushTile, TileMapBrushResource}, - tileset::{TileDefinition, TileDefinitionHandle, TileSetResource}, + brush::TileMapBrushPage, brush::TileMapBrushResource, swap_hash_map_entry, + tileset::TileSetResource, TileSetUpdate, TilesUpdate, }, }, }; #[derive(Debug)] -pub struct AddTileCommand { +pub struct SetColliderLayerNameCommand { pub tile_set: TileSetResource, - pub tile: Option, - pub handle: TileDefinitionHandle, + pub uuid: Uuid, + pub name: ImmutableString, } -impl CommandTrait for AddTileCommand { +impl SetColliderLayerNameCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(collider) = tile_set.find_collider_mut(self.uuid) else { + return; + }; + std::mem::swap(&mut collider.name, &mut self.name); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetColliderLayerNameCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { - "Add Tile".into() + "Set Collider Layer Name".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { - self.handle = self - .tile_set - .data_ref() - .tiles - .spawn(self.tile.take().unwrap()); + self.swap(); } fn revert(&mut self, _context: &mut dyn CommandContext) { - self.tile = self.tile_set.data_ref().tiles.try_free(self.handle); + self.swap(); } } #[derive(Debug)] -pub struct RemoveTileCommand { +pub struct SetPropertyLayerNameCommand { pub tile_set: TileSetResource, - pub handle: TileDefinitionHandle, - pub tile: Option, + pub uuid: Uuid, + pub name: ImmutableString, +} + +impl SetPropertyLayerNameCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + std::mem::swap(&mut property.name, &mut self.name); + tile_set.change_count.increment(); + } } -impl CommandTrait for RemoveTileCommand { - fn name(&mut self, _text: &dyn CommandContext) -> String { - "Remove Tile".into() +impl CommandTrait for SetPropertyLayerNameCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Property Layer Name".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { - self.tile = self.tile_set.data_ref().tiles.try_free(self.handle); + self.swap(); } fn revert(&mut self, _context: &mut dyn CommandContext) { - self.handle = self - .tile_set - .data_ref() - .tiles - .spawn(self.tile.take().unwrap()); + self.swap(); } } #[derive(Debug)] -pub struct SetBrushTilesCommand { +pub struct SetColliderLayerColorCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub color: Color, +} + +impl SetColliderLayerColorCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(collider) = tile_set.find_collider_mut(self.uuid) else { + return; + }; + std::mem::swap(&mut collider.color, &mut self.color); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetColliderLayerColorCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Collider Layer Color".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +pub struct SetPropertyValueColorCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub name_index: usize, + pub color: Color, +} + +impl SetPropertyValueColorCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + let Some(named_value) = property.named_values.get_mut(self.name_index) else { + return; + }; + std::mem::swap(&mut named_value.color, &mut self.color); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetPropertyValueColorCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Property Value Color".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +pub struct SetPropertyValueNameCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub name_index: usize, + pub name: String, +} + +impl SetPropertyValueNameCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + let Some(named_value) = property.named_values.get_mut(self.name_index) else { + return; + }; + std::mem::swap(&mut named_value.name, &mut self.name); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetPropertyValueNameCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Property Value Name".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +pub struct SetPropertyValueCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub name_index: usize, + pub value: NamableValue, +} + +impl SetPropertyValueCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + let Some(named_value) = property.named_values.get_mut(self.name_index) else { + return; + }; + std::mem::swap(&mut named_value.value, &mut self.value); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetPropertyValueCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Property Value Name".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +pub struct AddPropertyValueCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub value_type: TileSetPropertyType, + pub index: usize, +} + +impl CommandTrait for AddPropertyValueCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Add Property Value Name".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + let value = match self.value_type { + TileSetPropertyType::I32 => NamableValue::I32(0), + TileSetPropertyType::F32 => NamableValue::F32(0.0), + TileSetPropertyType::String => NamableValue::I32(0), + TileSetPropertyType::NineSlice => NamableValue::I8(0), + }; + property.named_values.insert( + self.index, + NamedValue { + value, + ..NamedValue::default() + }, + ); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + property.named_values.remove(self.index); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct RemovePropertyValueCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub value: Option, + pub index: usize, +} + +impl CommandTrait for RemovePropertyValueCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Remove Property Value Name".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + self.value = Some(property.named_values.remove(self.index)); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + property + .named_values + .insert(self.index, self.value.take().unwrap()); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct AddColliderLayerCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub index: usize, +} + +impl CommandTrait for AddColliderLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Add Collider Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.colliders.insert( + self.index, + TileSetColliderLayer { + uuid: self.uuid, + ..TileSetColliderLayer::default() + }, + ); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.colliders.remove(self.index); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct AddPropertyLayerCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub index: usize, + pub prop_type: TileSetPropertyType, +} + +impl CommandTrait for AddPropertyLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Add Property Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.properties.insert( + self.index, + TileSetPropertyLayer { + uuid: self.uuid, + prop_type: self.prop_type, + ..TileSetPropertyLayer::default() + }, + ); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.properties.remove(self.index); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct MovePropertyLayerCommand { + pub tile_set: TileSetResource, + pub start: usize, + pub end: usize, +} + +impl CommandTrait for MovePropertyLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Move Property Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.properties.swap(self.start, self.end); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.properties.swap(self.start, self.end); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct MovePropertyValueCommand { + pub tile_set: TileSetResource, + pub uuid: Uuid, + pub start: usize, + pub end: usize, +} + +impl MovePropertyValueCommand { + fn swap(&self) { + let mut tile_set = self.tile_set.data_ref(); + let Some(property) = tile_set.find_property_mut(self.uuid) else { + return; + }; + property.named_values.swap(self.start, self.end); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for MovePropertyValueCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Move Property Value".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap(); + } +} + +#[derive(Debug)] +pub struct MoveColliderLayerCommand { + pub tile_set: TileSetResource, + pub start: usize, + pub end: usize, +} + +impl CommandTrait for MoveColliderLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Move Collider Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.colliders.swap(self.start, self.end); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.colliders.swap(self.start, self.end); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct RemoveColliderLayerCommand { + pub tile_set: TileSetResource, + pub index: usize, + pub layer: Option, + pub values: FxHashMap, +} + +impl CommandTrait for RemoveColliderLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Remove Collider Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let layer = tile_set.colliders.remove(self.index); + let uuid = layer.uuid; + self.layer = Some(layer); + tile_set.swap_all_values_for_collider(uuid, &mut self.values); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let layer = self.layer.take().unwrap(); + let uuid = layer.uuid; + tile_set.colliders.insert(self.index, layer); + tile_set.swap_all_values_for_collider(uuid, &mut self.values); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct RemovePropertyLayerCommand { + pub tile_set: TileSetResource, + pub index: usize, + pub layer: Option, + pub values: FxHashMap, +} + +impl CommandTrait for RemovePropertyLayerCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Remove Property Layer".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let layer = tile_set.properties.remove(self.index); + let uuid = layer.uuid; + self.layer = Some(layer); + tile_set.swap_all_values_for_property(uuid, &mut self.values); + tile_set.change_count.increment(); + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + let mut tile_set = self.tile_set.data_ref(); + let layer = self.layer.take().unwrap(); + let uuid = layer.uuid; + tile_set.properties.insert(self.index, layer); + tile_set.swap_all_values_for_property(uuid, &mut self.values); + tile_set.change_count.increment(); + } +} + +#[derive(Debug)] +pub struct SetBrushPageCommand { pub brush: TileMapBrushResource, - pub tiles: Vec, + pub position: Vector2, + pub page: Option, } -impl SetBrushTilesCommand { +impl SetBrushPageCommand { fn swap(&mut self) { - std::mem::swap(&mut self.brush.data_ref().tiles, &mut self.tiles); - Log::verify(self.brush.save_back()); + let mut brush = self.brush.data_ref(); + swap_hash_map_entry(brush.pages.entry(self.position), &mut self.page); + brush.change_count.increment(); } } -impl CommandTrait for SetBrushTilesCommand { +impl CommandTrait for SetBrushPageCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Set Brush Page".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct SetTileSetPageCommand { + pub tile_set: TileSetResource, + pub position: Vector2, + pub page: Option, +} + +impl SetTileSetPageCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + swap_hash_map_entry(tile_set.pages.entry(self.position), &mut self.page); + tile_set.rebuild_transform_sets(); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for SetTileSetPageCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { - "Set Brush Tiles".to_string() + "Modify Tile Set Page".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { @@ -107,28 +596,97 @@ impl CommandTrait for SetBrushTilesCommand { } #[derive(Debug)] -pub struct MoveBrushTilesCommand { +pub struct MoveTileSetPageCommand { + pub tile_set: TileSetResource, + pub pages: Vec>, + data: Vec>, + pub start_offset: Vector2, + pub end_offset: Vector2, +} + +impl MoveTileSetPageCommand { + pub fn new(tile_set: TileSetResource, pages: Vec>, offset: Vector2) -> Self { + Self { + data: vec![None; pages.len()], + tile_set, + pages, + start_offset: Vector2::new(0, 0), + end_offset: offset, + } + } + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + for (i, p) in self.pages.iter().enumerate() { + swap_hash_map_entry( + tile_set.pages.entry(*p + self.start_offset), + &mut self.data[i], + ); + } + for (i, p) in self.pages.iter().enumerate() { + swap_hash_map_entry( + tile_set.pages.entry(*p + self.end_offset), + &mut self.data[i], + ); + } + std::mem::swap(&mut self.start_offset, &mut self.end_offset); + tile_set.rebuild_transform_sets(); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for MoveTileSetPageCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Move Tile Set Page".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct MoveBrushPageCommand { pub brush: TileMapBrushResource, - pub positions: Vec<(Uuid, Vector2)>, + pub pages: Vec>, + data: Vec>, + pub start_offset: Vector2, + pub end_offset: Vector2, } -impl MoveBrushTilesCommand { +impl MoveBrushPageCommand { + pub fn new( + brush: TileMapBrushResource, + pages: Vec>, + offset: Vector2, + ) -> Self { + Self { + data: vec![None; pages.len()], + brush, + pages, + start_offset: Vector2::new(0, 0), + end_offset: offset, + } + } fn swap(&mut self) { let mut brush = self.brush.data_ref(); - for (id, pos) in self.positions.iter_mut() { - if let Some(index) = brush.tiles.iter_mut().position(|tile| tile.id == *id) { - let tile = &mut brush.tiles[index]; - std::mem::swap(pos, &mut tile.local_position); - } + for (i, p) in self.pages.iter().enumerate() { + swap_hash_map_entry(brush.pages.entry(*p + self.start_offset), &mut self.data[i]); + } + for (i, p) in self.pages.iter().enumerate() { + swap_hash_map_entry(brush.pages.entry(*p + self.end_offset), &mut self.data[i]); } - drop(brush); - Log::verify(self.brush.save_back()); + std::mem::swap(&mut self.start_offset, &mut self.end_offset); + brush.change_count.increment(); } } -impl CommandTrait for MoveBrushTilesCommand { +impl CommandTrait for MoveBrushPageCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { - "Move Brush Tiles".into() + "Move Brush Page".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { @@ -141,53 +699,317 @@ impl CommandTrait for MoveBrushTilesCommand { } #[derive(Debug)] -pub struct RemoveBrushTileCommand { - pub brush: TileMapBrushResource, - pub id: Uuid, - pub tile: Option, +pub struct MoveTileSetTileCommand { + pub tile_set: TileSetResource, + pub page: Vector2, + pub tiles: Vec>, + data: Vec>, + pub start_offset: Vector2, + pub end_offset: Vector2, +} + +impl MoveTileSetTileCommand { + pub fn new( + tile_set: TileSetResource, + page: Vector2, + tiles: Vec>, + offset: Vector2, + ) -> Self { + Self { + data: vec![None; tiles.len()], + tile_set, + page, + tiles, + start_offset: Vector2::new(0, 0), + end_offset: offset, + } + } + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + for (i, p) in self.tiles.iter().enumerate() { + self.data[i] = + tile_set.set_abstract_tile(self.page, *p + self.start_offset, self.data[i].take()); + } + for (i, p) in self.tiles.iter().enumerate() { + self.data[i] = + tile_set.set_abstract_tile(self.page, *p + self.end_offset, self.data[i].take()); + } + std::mem::swap(&mut self.start_offset, &mut self.end_offset); + tile_set.rebuild_transform_sets(); + tile_set.change_count.increment(); + } } -impl CommandTrait for RemoveBrushTileCommand { +impl CommandTrait for MoveTileSetTileCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { - "Remove Brush Tile".into() + "Move Tile in Tile Set".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct MoveBrushTileCommand { + pub brush: TileMapBrushResource, + pub page: Vector2, + pub tiles: Vec>, + data: Vec>, + pub start_offset: Vector2, + pub end_offset: Vector2, +} + +impl MoveBrushTileCommand { + pub fn new( + brush: TileMapBrushResource, + page: Vector2, + tiles: Vec>, + offset: Vector2, + ) -> Self { + Self { + data: vec![None; tiles.len()], + brush, + page, + tiles, + start_offset: Vector2::new(0, 0), + end_offset: offset, + } + } + fn swap(&mut self) { let mut brush = self.brush.data_ref(); - let index = brush - .tiles - .iter() - .position(|tile| tile.id == self.id) + let page = brush.pages.get_mut(&self.page).unwrap(); + for (i, p) in self.tiles.iter().enumerate() { + swap_hash_map_entry(page.tiles.entry(*p + self.start_offset), &mut self.data[i]); + } + for (i, p) in self.tiles.iter().enumerate() { + swap_hash_map_entry(page.tiles.entry(*p + self.end_offset), &mut self.data[i]); + } + std::mem::swap(&mut self.start_offset, &mut self.end_offset); + brush.change_count.increment(); + } +} + +impl CommandTrait for MoveBrushTileCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Move Tile in Brush".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct TransformTilesCommand { + pub tile_set: TileSetResource, + pub page: Vector2, + pub tiles: Vec>, + pub transformation: OrthoTransformation, +} + +impl TransformTilesCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let source = tile_set + .pages + .get_mut(&self.page) + .map(|p| &mut p.source) .unwrap(); - self.tile = Some(brush.tiles.remove(index)); - drop(brush); - Log::verify(self.brush.save_back()); + let TileSetPageSource::Freeform(map) = source else { + panic!(); + }; + for p in self.tiles.iter() { + let Some(def) = map.get_mut(p) else { + continue; + }; + def.material_bounds.bounds = def + .material_bounds + .bounds + .clone() + .transformed(self.transformation); + } + self.transformation = self.transformation.inverted(); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for TransformTilesCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Transform Tiles in Tile Set".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() } fn revert(&mut self, _context: &mut dyn CommandContext) { - self.brush.data_ref().tiles.push(self.tile.take().unwrap()); - Log::verify(self.brush.save_back()); + self.swap() } } #[derive(Debug)] -pub struct AddBrushTileCommand { +pub struct SetBrushTilesCommand { pub brush: TileMapBrushResource, - pub tile: Option, + pub page: Vector2, + pub tiles: TilesUpdate, +} + +impl SetBrushTilesCommand { + fn swap(&mut self) { + let mut brush = self.brush.data_ref(); + if let Some(page) = brush.pages.get_mut(&self.page) { + page.tiles.swap_tiles(&mut self.tiles); + } + brush.change_count.increment(); + } +} + +impl CommandTrait for SetBrushTilesCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Modify Brush Tiles".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct SetTileSetTilesCommand { + pub tile_set: TileSetResource, + pub tiles: TileSetUpdate, +} + +impl SetTileSetTilesCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + tile_set.swap(&mut self.tiles); + tile_set.rebuild_transform_sets(); + tile_set.change_count.increment(); + } } -impl CommandTrait for AddBrushTileCommand { +impl CommandTrait for SetTileSetTilesCommand { fn name(&mut self, _context: &dyn CommandContext) -> String { - "Add Brush Tile".to_string() + "Modify Tile Set Tiles".into() } fn execute(&mut self, _context: &mut dyn CommandContext) { - self.brush.data_ref().tiles.push(self.tile.take().unwrap()); - Log::verify(self.brush.save_back()); + self.swap() } fn revert(&mut self, _context: &mut dyn CommandContext) { - self.tile = self.brush.data_ref().tiles.pop(); - Log::verify(self.brush.save_back()); + self.swap() + } +} + +#[derive(Debug)] +pub struct ModifyPageTileSizeCommand { + pub tile_set: TileSetResource, + pub page: Vector2, + pub size: Vector2, +} + +impl ModifyPageTileSizeCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let TileSetPageSource::Material(mat) = + &mut tile_set.pages.get_mut(&self.page).unwrap().source + else { + panic!(); + }; + std::mem::swap(&mut self.size, &mut mat.tile_size); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for ModifyPageTileSizeCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Modify Tile Size".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct ModifyPageMaterialCommand { + pub tile_set: TileSetResource, + pub page: Vector2, + pub material: MaterialResource, +} + +impl ModifyPageMaterialCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let TileSetPageSource::Material(mat) = + &mut tile_set.pages.get_mut(&self.page).unwrap().source + else { + panic!(); + }; + std::mem::swap(&mut self.material, &mut mat.material); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for ModifyPageMaterialCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Modify Tile Page Material".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } +} + +#[derive(Debug)] +pub struct ModifyPageIconCommand { + pub tile_set: TileSetResource, + pub page: Vector2, + pub icon: Option, +} + +impl ModifyPageIconCommand { + fn swap(&mut self) { + let mut tile_set = self.tile_set.data_ref(); + let page = &mut tile_set.pages.get_mut(&self.page).unwrap(); + std::mem::swap(&mut self.icon, &mut page.icon); + tile_set.change_count.increment(); + } +} + +impl CommandTrait for ModifyPageIconCommand { + fn name(&mut self, _context: &dyn CommandContext) -> String { + "Modify Tile Page Icon".into() + } + + fn execute(&mut self, _context: &mut dyn CommandContext) { + self.swap() + } + + fn revert(&mut self, _context: &mut dyn CommandContext) { + self.swap() } } diff --git a/editor/src/plugins/tilemap/handle_editor.rs b/editor/src/plugins/tilemap/handle_editor.rs new file mode 100644 index 000000000..7f4a38913 --- /dev/null +++ b/editor/src/plugins/tilemap/handle_editor.rs @@ -0,0 +1,209 @@ +// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use fyrox::gui::{ + button::ButtonMessage, + text::{TextBuilder, TextMessage}, + text_box::TextBoxMessage, + Control, +}; + +use crate::{ + fyrox::gui::{ + define_constructor, define_widget_deref, + grid::{Column, GridBuilder, Row}, + stack_panel::StackPanelBuilder, + text_box::TextBoxBuilder, + widget::Widget, + Orientation, + }, + send_sync_message, +}; + +use super::*; + +const BUTTON_SIZE: f32 = 12.0; + +#[derive(Debug, PartialEq, Clone)] +pub enum TileHandleEditorMessage { + Goto, + OpenPalette, + Value(Option), +} + +impl TileHandleEditorMessage { + define_constructor!(TileHandleEditorMessage:Goto => fn goto(), layout: false); + define_constructor!(TileHandleEditorMessage:OpenPalette => fn open_palette(), layout: false); + define_constructor!(TileHandleEditorMessage:Value => fn value(Option), layout: false); +} + +#[derive(Debug, Default, Clone, Visit, Reflect, TypeUuidProvider, ComponentProvider)] +#[type_uuid(id = "86513074-461d-4583-a214-fb84f5aacac1")] +pub struct TileHandleEditor { + widget: Widget, + value: Option, + field: Handle, + palette_button: Handle, + goto_button: Handle, +} + +define_widget_deref!(TileHandleEditor); + +fn value_to_string(value: Option) -> String { + if let Some(v) = value { + v.to_string() + } else { + "?".into() + } +} + +fn make_label(name: &str, ctx: &mut BuildContext) -> Handle { + TextBuilder::new(WidgetBuilder::new()) + .with_text(name) + .build(ctx) +} + +impl TileHandleEditor { + pub fn value(&self) -> Option { + self.value + } +} + +impl Control for TileHandleEditor { + fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { + self.widget.handle_routed_message(ui, message); + if message.direction() == MessageDirection::ToWidget { + if let Some(TileHandleEditorMessage::Value(value)) = message.data() { + self.value = *value; + ui.send_message(TextMessage::text( + self.field, + MessageDirection::ToWidget, + value_to_string(*value), + )); + ui.send_message(message.reverse()); + } + } else if let Some(ButtonMessage::Click) = message.data() { + if message.destination() == self.palette_button { + ui.send_message(TileHandleEditorMessage::open_palette( + self.handle(), + MessageDirection::FromWidget, + )); + } else if message.destination() == self.goto_button { + ui.send_message(TileHandleEditorMessage::goto( + self.handle(), + MessageDirection::FromWidget, + )); + } + } else if let Some(TextMessage::Text(text)) = message.data() { + if message.flags == 0 { + if let Ok(value) = text.parse() { + if self.value != Some(value) { + self.value = Some(value); + ui.send_message(TileHandleEditorMessage::value( + self.handle(), + MessageDirection::FromWidget, + self.value, + )); + } + } + send_sync_message( + ui, + TextMessage::text( + self.field, + MessageDirection::ToWidget, + value_to_string(self.value), + ), + ); + } + } + } +} + +pub struct TileHandleEditorBuilder { + widget_builder: WidgetBuilder, + label: String, + value: Option, +} + +impl TileHandleEditorBuilder { + pub fn new(widget_builder: WidgetBuilder) -> Self { + Self { + widget_builder, + label: String::default(), + value: None, + } + } + pub fn with_label(mut self, label: &str) -> Self { + self.label = label.into(); + self + } + pub fn with_value(mut self, value: Option) -> Self { + self.value = value; + self + } + pub fn build(self, ctx: &mut BuildContext) -> Handle { + let field = TextBoxBuilder::new(WidgetBuilder::new().on_column(1)) + .with_text(value_to_string(self.value)) + .build(ctx); + let goto_button = make_drawing_mode_button( + ctx, + BUTTON_SIZE, + BUTTON_SIZE, + PICK_IMAGE.clone(), + "Jump to tile", + None, + ); + let palette_button = make_drawing_mode_button( + ctx, + BUTTON_SIZE, + BUTTON_SIZE, + PALETTE_IMAGE.clone(), + "Open in palette window", + None, + ); + let buttons = StackPanelBuilder::new( + WidgetBuilder::new() + .on_column(2) + .with_child(goto_button) + .with_child(palette_button), + ) + .with_orientation(Orientation::Horizontal) + .build(ctx); + let content = GridBuilder::new( + WidgetBuilder::new() + .with_child(make_label(&self.label, ctx)) + .with_child(field) + .with_child(buttons), + ) + .add_row(Row::auto()) + .add_column(Column::strict(FIELD_LABEL_WIDTH)) + .add_column(Column::stretch()) + .add_column(Column::auto()) + .build(ctx); + + ctx.add_node(UiNode::new(TileHandleEditor { + widget: self.widget_builder.with_child(content).build(), + value: self.value, + field, + goto_button, + palette_button, + })) + } +} diff --git a/editor/src/plugins/tilemap/mod.rs b/editor/src/plugins/tilemap/mod.rs index 21d5b49d3..e614857b9 100644 --- a/editor/src/plugins/tilemap/mod.rs +++ b/editor/src/plugins/tilemap/mod.rs @@ -20,15 +20,30 @@ #![allow(clippy::collapsible_match)] // STFU +mod colliders_tab; mod commands; +mod handle_editor; mod misc; pub mod palette; pub mod panel; +mod panel_preview; mod preview; +mod properties_tab; +mod tile_bounds_editor; +mod tile_inspector; +mod tile_prop_editor; pub mod tile_set_import; pub mod tileset; +use colliders_tab::*; +use handle_editor::*; +use palette::PaletteWidget; +use panel::TileMapPanel; +use panel_preview::*; +use properties_tab::*; +pub use tile_bounds_editor::*; +use tile_inspector::*; +pub use tile_prop_editor::*; -use crate::inspector::InspectorPlugin; use crate::{ command::SetPropertyCommand, fyrox::{ @@ -38,41 +53,93 @@ use crate::{ math::{plane::Plane, Matrix4Ext, Rect}, parking_lot::Mutex, pool::Handle, + reflect::prelude::*, type_traits::prelude::*, + visitor::prelude::*, Uuid, }, engine::Engine, graph::{BaseSceneGraph, SceneGraph, SceneGraphNode}, gui::{ button::ButtonBuilder, - inspector::editors::inherit::InheritablePropertyEditorDefinition, key::HotKey, - message::KeyCode, message::MessageDirection, message::UiMessage, - utils::make_simple_tooltip, widget::WidgetBuilder, widget::WidgetMessage, BuildContext, - Thickness, UiNode, + inspector::editors::inherit::InheritablePropertyEditorDefinition, + key::HotKey, + message::{KeyCode, MessageDirection, UiMessage}, + utils::make_simple_tooltip, + widget::{WidgetBuilder, WidgetMessage}, + BuildContext, Thickness, UiNode, }, scene::{ debug::Line, node::Node, tilemap::{ - brush::{BrushTile, TileMapBrush}, - tileset::TileSet, - Tile, TileMap, Tiles, + brush::TileMapBrush, + tileset::{TileSet, TileSetResource}, + RandomTileSource, RepeatTileSource, Stamp, Tile, TileMap, TileResource, Tiles, + TilesUpdate, }, Scene, }, }, + inspector::InspectorPlugin, interaction::{make_interaction_mode_button, InteractionMode}, + load_image, message::MessageSender, plugin::EditorPlugin, plugins::tilemap::{ - misc::TilesPropertyEditorDefinition, palette::PaletteMessage, panel::TileMapPanel, - preview::TileSetPreview, tileset::TileSetEditor, + misc::TilesPropertyEditorDefinition, palette::PaletteMessage, preview::TileSetPreview, + tileset::TileSetEditor, }, scene::{commands::GameSceneContext, controller::SceneController, GameScene, Selection}, settings::Settings, Editor, Message, }; -use std::sync::Arc; +use fyrox::{ + asset::untyped::UntypedResource, + core::{log::Log, parking_lot::MutexGuard, ImmutableString}, + fxhash::FxHashSet, + gui::{ + border::BorderBuilder, brush::Brush, decorator::DecoratorBuilder, image::ImageBuilder, + UserInterface, BRUSH_BRIGHT_BLUE, BRUSH_DARKER, BRUSH_LIGHT, BRUSH_LIGHTER, BRUSH_LIGHTEST, + }, + scene::tilemap::{ + tileset::{TileCollider, TileMaterialBounds}, + TileDefinitionHandle, TilePaletteStage, TransTilesUpdate, + }, +}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; + +lazy_static! { + static ref BRUSH_IMAGE: Option = + load_image(include_bytes!("../../../resources/brush.png")); + static ref ERASER_IMAGE: Option = + load_image(include_bytes!("../../../resources/eraser.png")); + static ref FILL_IMAGE: Option = + load_image(include_bytes!("../../../resources/fill.png")); + static ref PICK_IMAGE: Option = + load_image(include_bytes!("../../../resources/pipette.png")); + static ref RECT_FILL_IMAGE: Option = + load_image(include_bytes!("../../../resources/rect_fill.png")); + static ref NINE_SLICE_IMAGE: Option = + load_image(include_bytes!("../../../resources/nine_slice.png")); + static ref LINE_IMAGE: Option = + load_image(include_bytes!("../../../resources/line.png")); + static ref TURN_LEFT_IMAGE: Option = + load_image(include_bytes!("../../../resources/turn_left.png")); + static ref TURN_RIGHT_IMAGE: Option = + load_image(include_bytes!("../../../resources/turn_right.png")); + static ref FLIP_X_IMAGE: Option = + load_image(include_bytes!("../../../resources/flip_x.png")); + static ref FLIP_Y_IMAGE: Option = + load_image(include_bytes!("../../../resources/flip_y.png")); + static ref RANDOM_IMAGE: Option = + load_image(include_bytes!("../../../resources/die.png")); + static ref PALETTE_IMAGE: Option = + load_image(include_bytes!("../../../resources/palette.png")); +} fn make_button( title: &str, @@ -92,38 +159,104 @@ fn make_button( .build(ctx) } +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Visit, Reflect)] pub enum DrawingMode { + #[default] Draw, Erase, FloodFill, - Pick { - click_grid_position: Option>, - }, - RectFill { - click_grid_position: Option>, - }, - NineSlice { - click_grid_position: Option>, + Pick, + RectFill, + NineSlice, + Line, + Material, + Property, + Color, + Collider, +} + +struct InteractionContext { + changed_tiles: TilesUpdate, +} + +#[derive(Clone, Default, Debug, PartialEq)] +enum MouseMode { + #[default] + None, + Dragging { + initial_position: Vector2, + offset: Vector2, }, - Line { - click_grid_position: Option>, + Drawing { + start_tile: Vector2, + end_tile: Vector2, }, } -struct InteractionContext { - previous_tiles: Tiles, +#[derive(Debug, PartialEq, Clone)] +struct OpenTilePanelMessage { + resource: TileResource, + center: Option, +} + +impl OpenTilePanelMessage { + fn message(resource: TileResource, center: Option) -> UiMessage { + UiMessage::with_data(Self { resource, center }) + } +} + +fn make_drawing_mode_button( + ctx: &mut BuildContext, + width: f32, + height: f32, + image: Option, + tooltip: &str, + tab_index: Option, +) -> Handle { + ButtonBuilder::new( + WidgetBuilder::new() + .with_tab_index(tab_index) + .with_tooltip(make_simple_tooltip(ctx, tooltip)) + .with_margin(Thickness::uniform(1.0)), + ) + .with_back( + DecoratorBuilder::new( + BorderBuilder::new(WidgetBuilder::new().with_foreground(BRUSH_DARKER)) + .with_pad_by_corner_radius(false) + .with_corner_radius(4.0) + .with_stroke_thickness(Thickness::uniform(1.0)), + ) + .with_selected_brush(BRUSH_BRIGHT_BLUE) + .with_normal_brush(BRUSH_LIGHT) + .with_hover_brush(BRUSH_LIGHTER) + .with_pressed_brush(BRUSH_LIGHTEST) + .build(ctx), + ) + .with_content( + ImageBuilder::new( + WidgetBuilder::new() + .with_background(Brush::Solid(Color::opaque(180, 180, 180))) + .with_margin(Thickness::uniform(2.0)) + .with_width(width) + .with_height(height), + ) + .with_opt_texture(image) + .build(ctx), + ) + .build(ctx) } #[derive(TypeUuidProvider)] #[type_uuid(id = "33fa8ef9-a29c-45d4-a493-79571edd870a")] pub struct TileMapInteractionMode { tile_map: Handle, - brush: Arc>, - brush_position: Vector2, - interaction_context: Option, + state: TileDrawStateRef, + panel: Handle, + brush_position: Option>, + click_grid_position: Option>, sender: MessageSender, - drawing_mode: DrawingMode, - drawing_modes_panel: Handle, + update: TransTilesUpdate, + mode: MouseMode, } impl TileMapInteractionMode { @@ -147,6 +280,9 @@ impl TileMapInteractionMode { ray.plane_intersection_point(&plane) .map(|intersection| tile_map.world_to_grid(intersection)) } + fn sync_to_state(&mut self, ui: &mut UserInterface) { + // TODO + } } impl InteractionMode for TileMapInteractionMode { @@ -164,44 +300,33 @@ impl InteractionMode for TileMapInteractionMode { }; let scene = &mut engine.scenes[game_scene.scene]; + /* TODO + let brush = self.brush.lock(); - let brush = self.brush.lock(); - - if let Some(grid_coord) = self.pick_grid(scene, game_scene, mouse_position, frame_size) { - let Some(tile_map) = scene.graph.try_get_mut_of_type::(self.tile_map) else { - return; - }; + if let Some(grid_coord) = self.pick_grid(scene, game_scene, mouse_position, frame_size) { + let Some(tile_map) = scene.graph.try_get_mut_of_type::(self.tile_map) else { + return; + }; - self.interaction_context = Some(InteractionContext { - previous_tiles: tile_map.tiles().clone(), - }); + self.interaction_context = Some(InteractionContext { + previous_tiles: tile_map.tiles().clone(), + }); - self.brush_position = grid_coord; + self.brush_position = grid_coord; + self.click_grid_position = Some(grid_coord); - match self.drawing_mode { - DrawingMode::Draw => tile_map.tiles.draw(grid_coord, &brush), - DrawingMode::Erase => { - tile_map.tiles.erase(grid_coord, &brush); - } - DrawingMode::FloodFill => { - tile_map.tiles.flood_fill(grid_coord, &brush); - } - DrawingMode::RectFill { - ref mut click_grid_position, - } - | DrawingMode::Pick { - ref mut click_grid_position, - } - | DrawingMode::NineSlice { - ref mut click_grid_position, - } - | DrawingMode::Line { - ref mut click_grid_position, - } => { - *click_grid_position = Some(grid_coord); + match self.drawing_mode { + DrawingMode::Draw => tile_map.tiles.draw(grid_coord, &brush), + DrawingMode::Erase => { + tile_map.tiles.erase(grid_coord, &brush); + } + DrawingMode::FloodFill => { + tile_map.tiles.flood_fill(grid_coord, &brush); + } + _ => (), + } } - } - } + */ } fn on_left_mouse_button_up( @@ -225,85 +350,83 @@ impl InteractionMode for TileMapInteractionMode { let Some(tile_map) = scene.graph.try_get_mut_of_type::(tile_map_handle) else { return; }; - - if let Some(interaction_context) = self.interaction_context.take() { - if let Some(grid_coord) = grid_coord { - let mut brush = self.brush.lock(); - match self.drawing_mode { - DrawingMode::Pick { - click_grid_position, - } => { - if let Some(click_grid_position) = click_grid_position { - brush.tiles.clear(); - let selected_rect = Rect::from_points(grid_coord, click_grid_position); - for y in selected_rect.position.y - ..(selected_rect.position.y + selected_rect.size.y) - { - for x in selected_rect.position.x - ..(selected_rect.position.x + selected_rect.size.x) - { - let position = Vector2::new(x, y); - if let Some(tile) = tile_map.tiles().get(&position) { - brush.tiles.push(BrushTile { - definition_handle: tile.definition_handle, - local_position: position - selected_rect.position, - id: Uuid::new_v4(), - }) + /* TODO + if let Some(interaction_context) = self.interaction_context.take() { + if let Some(grid_coord) = grid_coord { + let mut brush = self.brush.lock(); + match self.drawing_mode { + DrawingMode::Pick => { + if let Some(click_grid_position) = self.click_grid_position { + brush.clear(); + let selected_rect = Rect::from_points(grid_coord, click_grid_position); + for y in selected_rect.position.y + ..(selected_rect.position.y + selected_rect.size.y) + { + for x in selected_rect.position.x + ..(selected_rect.position.x + selected_rect.size.x) + { + let position = Vector2::new(x, y); + if let Some(tile) = tile_map.tiles().get(&position) { + let pos = position - selected_rect.position; + brush.insert(&pos, tile.definition_handle); + } + } } } } + DrawingMode::RectFill => { + if let Some(click_grid_position) = self.click_grid_position { + interaction_context.changed_tiles.clear(); + let source = if self.random_mode { + interaction_context.changed_tiles.rect_fill( + Rect::from_points(grid_coord, click_grid_position), + &RandomTileSource(&*brush), + ); + } else { + interaction_context.changed_tiles.rect_fill( + Rect::from_points(grid_coord, click_grid_position), + &RepeatTileSource::new(&*brush), + ); + }; + } + } + DrawingMode::NineSlice => { + if let Some(click_grid_position) = self.click_grid_position { + tile_map.tiles.nine_slice( + Rect::from_points(grid_coord, click_grid_position), + &brush, + ) + } + } + DrawingMode::Line => { + if let Some(click_grid_position) = self.click_grid_position { + tile_map.tiles.draw_line_with_brush( + self.brush_position, + click_grid_position, + &brush, + ); + } + } + _ => (), } } - DrawingMode::RectFill { - click_grid_position, - } => { - if let Some(click_grid_position) = click_grid_position { - tile_map.tiles.rect_fill( - Rect::from_points(grid_coord, click_grid_position), - &brush, - ); - } - } - DrawingMode::NineSlice { - click_grid_position, - } => { - if let Some(click_grid_position) = click_grid_position { - tile_map.tiles.nine_slice( - Rect::from_points(grid_coord, click_grid_position), - &brush, - ) - } - } - DrawingMode::Line { - click_grid_position, - } => { - if let Some(click_grid_position) = click_grid_position { - tile_map.tiles.draw_line_with_brush( - self.brush_position, - click_grid_position, - &brush, - ); - } + + if !matches!(self.drawing_mode, DrawingMode::Pick { .. }) { + let new_tiles = tile_map.tiles().clone(); + tile_map.set_tiles(interaction_context.previous_tiles); + self.sender.do_command(SetPropertyCommand::new( + "tiles".to_string(), + Box::new(new_tiles), + move |ctx| { + ctx.get_mut::() + .scene + .graph + .node_mut(tile_map_handle) + }, + )); } - _ => (), } - } - - if !matches!(self.drawing_mode, DrawingMode::Pick { .. }) { - let new_tiles = tile_map.tiles().clone(); - tile_map.set_tiles(interaction_context.previous_tiles); - self.sender.do_command(SetPropertyCommand::new( - "tiles".to_string(), - Box::new(new_tiles), - move |ctx| { - ctx.get_mut::() - .scene - .graph - .node_mut(tile_map_handle) - }, - )); - } - } + */ } fn on_mouse_move( @@ -321,28 +444,29 @@ impl InteractionMode for TileMapInteractionMode { }; let scene = &mut engine.scenes[game_scene.scene]; + /* + let brush = self.brush.lock(); - let brush = self.brush.lock(); - - if let Some(grid_coord) = self.pick_grid(scene, game_scene, mouse_position, frame_size) { - let Some(tile_map) = scene.graph.try_get_mut_of_type::(self.tile_map) else { - return; - }; + if let Some(grid_coord) = self.pick_grid(scene, game_scene, mouse_position, frame_size) { + let Some(tile_map) = scene.graph.try_get_mut_of_type::(self.tile_map) else { + return; + }; - self.brush_position = grid_coord; + self.brush_position = grid_coord; - if self.interaction_context.is_some() { - match self.drawing_mode { - DrawingMode::Draw => tile_map.tiles.draw(grid_coord, &brush), - DrawingMode::Erase => { - tile_map.tiles.erase(grid_coord, &brush); - } - _ => { - // Do nothing + if self.interaction_context.is_some() { + match self.drawing_mode { + DrawingMode::Draw => tile_map.tiles.draw(grid_coord, &brush), + DrawingMode::Erase => { + tile_map.tiles.erase(grid_coord, &brush); + } + _ => { + // Do nothing + } + } } } - } - } + */ } fn update( @@ -383,171 +507,160 @@ impl InteractionMode for TileMapInteractionMode { for x in -size..size { draw_line(Vector2::new(x, -size), Vector2::new(x, size), Color::WHITE); } - - match self.drawing_mode { - DrawingMode::Draw | DrawingMode::Erase => { - self.brush.lock().draw_outline( - &mut scene.drawing_context, - self.brush_position, - &transform, - Color::RED, - ); - } - DrawingMode::FloodFill => { - scene.drawing_context.draw_rectangle( - 0.5, - 0.5, - transform - * Matrix4::new_translation( - &(self.brush_position.cast::().to_homogeneous() - + Vector3::new(0.5, 0.5, 0.0)), - ), - Color::RED, - ); - } - DrawingMode::Line { - click_grid_position, - } => { - if self.interaction_context.is_some() { - if let Some(click_grid_position) = click_grid_position { - for point in [click_grid_position, self.brush_position] { - scene.drawing_context.draw_rectangle( - 0.5, - 0.5, - transform - * Matrix4::new_translation( - &(point.cast::().to_homogeneous() - + Vector3::new(0.5, 0.5, 0.0)), - ), - Color::RED, - ); - } + /* TODO + match self.drawing_mode { + DrawingMode::Draw | DrawingMode::Erase => { + self.brush.lock().draw_outline( + &mut scene.drawing_context, + self.brush_position, + &transform, + Color::RED, + ); } - } - } - DrawingMode::Pick { - click_grid_position, - } - | DrawingMode::RectFill { - click_grid_position, - } - | DrawingMode::NineSlice { - click_grid_position, - } => { - if self.interaction_context.is_some() { - if let Some(click_grid_position) = click_grid_position { - let rect = Rect::from_points(click_grid_position, self.brush_position); - let position = rect.position.cast::(); - let half_size = rect.size.cast::().scale(0.5); - + DrawingMode::FloodFill => { scene.drawing_context.draw_rectangle( - half_size.x, - half_size.y, + 0.5, + 0.5, transform * Matrix4::new_translation( - &(position + half_size).to_homogeneous(), + &(self.brush_position.cast::().to_homogeneous() + + Vector3::new(0.5, 0.5, 0.0)), ), Color::RED, ); } + DrawingMode::Line { + click_grid_position, + } => { + if self.interaction_context.is_some() { + if let Some(click_grid_position) = click_grid_position { + for point in [click_grid_position, self.brush_position] { + scene.drawing_context.draw_rectangle( + 0.5, + 0.5, + transform + * Matrix4::new_translation( + &(point.cast::().to_homogeneous() + + Vector3::new(0.5, 0.5, 0.0)), + ), + Color::RED, + ); + } + } + } + } + DrawingMode::Pick { + click_grid_position, + } + | DrawingMode::RectFill { + click_grid_position, + } + | DrawingMode::NineSlice { + click_grid_position, + } => { + if self.interaction_context.is_some() { + if let Some(click_grid_position) = click_grid_position { + let rect = Rect::from_points(click_grid_position, self.brush_position); + let position = rect.position.cast::(); + let half_size = rect.size.cast::().scale(0.5); + + scene.drawing_context.draw_rectangle( + half_size.x, + half_size.y, + transform + * Matrix4::new_translation( + &(position + half_size).to_homogeneous(), + ), + Color::RED, + ); + } + } + } } - } - } - let brush = self.brush.lock(); + let brush = self.brush.lock(); - tile_map.overlay_tiles.clear(); - match self.drawing_mode { - DrawingMode::Draw => { - for tile in brush.tiles.iter() { - tile_map.overlay_tiles.insert(Tile { - position: self.brush_position + tile.local_position, - definition_handle: tile.definition_handle, - }); - } - } - DrawingMode::Erase => {} - DrawingMode::FloodFill => { - let tiles = tile_map - .tiles - .flood_fill_immutable(self.brush_position, &brush); - for tile in tiles { - tile_map.overlay_tiles.insert(tile); - } - } - DrawingMode::Pick { .. } => {} - DrawingMode::RectFill { - click_grid_position, - } => { - if self.interaction_context.is_some() { - if let Some(click_grid_position) = click_grid_position { - tile_map.overlay_tiles.rect_fill( - Rect::from_points(self.brush_position, click_grid_position), - &brush, - ); + tile_map.overlay_tiles.clear(); + match self.drawing_mode { + DrawingMode::Draw => { + for tile in brush.tiles.iter() { + tile_map.overlay_tiles.insert(Tile { + position: self.brush_position + tile.local_position, + definition_handle: tile.definition_handle, + }); + } } - } - } - DrawingMode::NineSlice { - click_grid_position, - } => { - if self.interaction_context.is_some() { - if let Some(click_grid_position) = click_grid_position { - tile_map.overlay_tiles.nine_slice( - Rect::from_points(self.brush_position, click_grid_position), - &brush, - ); + DrawingMode::Erase => {} + DrawingMode::FloodFill => { + let tiles = tile_map + .tiles + .flood_fill_immutable(self.brush_position, &brush); + for tile in tiles { + tile_map.overlay_tiles.insert(tile); + } } - } - } - DrawingMode::Line { - click_grid_position, - } => { - if self.interaction_context.is_some() { - if let Some(click_grid_position) = click_grid_position { - tile_map.overlay_tiles.draw_line_with_brush( - self.brush_position, - click_grid_position, - &brush, - ); + DrawingMode::Pick { .. } => {} + DrawingMode::RectFill { + click_grid_position, + } => { + if self.interaction_context.is_some() { + if let Some(click_grid_position) = click_grid_position { + tile_map.overlay_tiles.rect_fill( + Rect::from_points(self.brush_position, click_grid_position), + &brush, + ); + } + } + } + DrawingMode::NineSlice { + click_grid_position, + } => { + if self.interaction_context.is_some() { + if let Some(click_grid_position) = click_grid_position { + tile_map.overlay_tiles.nine_slice( + Rect::from_points(self.brush_position, click_grid_position), + &brush, + ); + } + } + } + DrawingMode::Line { + click_grid_position, + } => { + if self.interaction_context.is_some() { + if let Some(click_grid_position) = click_grid_position { + tile_map.overlay_tiles.draw_line_with_brush( + self.brush_position, + click_grid_position, + &brush, + ); + } + } } } - } - } + */ } fn activate(&mut self, _controller: &dyn SceneController, engine: &mut Engine) { - engine - .user_interfaces - .first() - .send_message(WidgetMessage::enabled( - self.drawing_modes_panel, + if self.panel.is_some() { + let ui = engine.user_interfaces.first_mut(); + ui.send_message(WidgetMessage::visibility( + self.panel, MessageDirection::ToWidget, true, )); + } } - fn deactivate(&mut self, controller: &dyn SceneController, engine: &mut Engine) { - engine - .user_interfaces - .first() - .send_message(WidgetMessage::enabled( - self.drawing_modes_panel, + fn deactivate(&mut self, _controller: &dyn SceneController, engine: &mut Engine) { + if self.panel.is_some() { + let ui = engine.user_interfaces.first_mut(); + ui.send_message(WidgetMessage::visibility( + self.panel, MessageDirection::ToWidget, false, )); - - let Some(game_scene) = controller.downcast_ref::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - let Some(tile_map) = scene.graph.try_get_mut_of_type::(self.tile_map) else { - return; - }; - - tile_map.overlay_tiles.clear(); + } } fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle { @@ -570,27 +683,29 @@ impl InteractionMode for TileMapInteractionMode { _engine: &mut Engine, _settings: &Settings, ) -> bool { - if let HotKey::Some { code, .. } = hotkey { - match *code { - KeyCode::AltLeft => { - self.drawing_mode = DrawingMode::Pick { - click_grid_position: None, - }; - return true; - } - KeyCode::ShiftLeft => { - self.drawing_mode = DrawingMode::Erase; - return true; - } - KeyCode::ControlLeft => { - self.drawing_mode = DrawingMode::RectFill { - click_grid_position: None, - }; - return true; + /* + if let HotKey::Some { code, .. } = hotkey { + match *code { + KeyCode::AltLeft => { + self.drawing_mode = DrawingMode::Pick { + click_grid_position: None, + }; + return true; + } + KeyCode::ShiftLeft => { + self.drawing_mode = DrawingMode::Erase; + return true; + } + KeyCode::ControlLeft => { + self.drawing_mode = DrawingMode::RectFill { + click_grid_position: None, + }; + return true; + } + _ => (), + } } - _ => (), - } - } + */ false } @@ -601,6 +716,7 @@ impl InteractionMode for TileMapInteractionMode { _engine: &mut Engine, _settings: &Settings, ) -> bool { + /* if let HotKey::Some { code, .. } = hotkey { match *code { KeyCode::AltLeft => { @@ -624,40 +740,235 @@ impl InteractionMode for TileMapInteractionMode { _ => (), } } + */ false } } #[derive(Default)] pub struct TileMapEditorPlugin { + state: TileDrawStateRef, tile_set_editor: Option, - brush: Arc>, panel: Option, tile_map: Handle, } +#[derive(Default, Debug, Clone, Visit)] +pub struct TileDrawState { + /// True if the state has been changed and the change has not yet caused the UI to update. + dirty: bool, + tile_set: Option, + stamp: Stamp, + drawing_mode: DrawingMode, + active_prop: Option, + draw_value: DrawValue, + random_mode: bool, + selection: TileDrawSelection, +} + +#[derive(Debug, Clone, Visit, Reflect)] +pub enum DrawValue { + I8(i8), + I32(i32), + F32(f32), + String(ImmutableString), + Color(Color), + Material(TileMaterialBounds), + Collider(TileCollider), +} + +impl Default for DrawValue { + fn default() -> Self { + Self::I32(0) + } +} + +#[derive(Debug, Default, Clone, Visit)] +pub struct TileDrawStateRef(Arc>); +pub struct TileDrawStateGuard<'a>(MutexGuard<'a, TileDrawState>); +pub struct TileDrawStateGuardMut<'a>(MutexGuard<'a, TileDrawState>); + +impl<'a> Deref for TileDrawStateGuard<'a> { + type Target = TileDrawState; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> Deref for TileDrawStateGuardMut<'a> { + type Target = TileDrawState; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a> DerefMut for TileDrawStateGuardMut<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl TileDrawStateRef { + pub fn lock(&self) -> TileDrawStateGuard { + TileDrawStateGuard(self.0.lock()) + } + pub fn lock_mut(&self) -> TileDrawStateGuardMut { + self.lock().into_mut() + } + pub fn check_dirty(&self) -> bool { + let mut state = self.0.lock(); + let dirty = state.dirty; + state.dirty = false; + dirty + } +} + +impl<'a> TileDrawStateGuard<'a> { + pub fn into_mut(self) -> TileDrawStateGuardMut<'a> { + let mut result = TileDrawStateGuardMut(self.0); + result.dirty = true; + result + } +} + +#[derive(Default, Debug, Clone, Visit)] +struct TileDrawSelection { + pub source: SelectionSource, + pub page: Vector2, + pub positions: FxHashSet>, + pub tiles: Tiles, +} + +impl TileDrawState { + /// True if the current selection is not empty + #[inline] + pub fn has_selection(&self) -> bool { + !self.selection.positions.is_empty() + } + /// The handle of the palette widget that is currently being used to select tiles, or else Handle::NONE. + #[inline] + pub fn selection_palette(&self) -> Handle { + match self.selection.source { + SelectionSource::Widget(h) => h, + _ => Handle::NONE, + } + } + /// The handle of the tile map node that is currently being used to select tiles, or else Handle::NONE. + #[inline] + pub fn selection_node(&self) -> Handle { + match self.selection.source { + SelectionSource::Node(h) => h, + _ => Handle::NONE, + } + } + #[inline] + pub fn set_palette(&mut self, handle: Handle) { + self.selection.source = SelectionSource::Widget(handle); + } + #[inline] + pub fn set_node(&mut self, handle: Handle) { + self.selection.source = SelectionSource::Node(handle); + } + #[inline] + pub fn selection_tiles(&self) -> &Tiles { + &self.selection.tiles + } + #[inline] + pub fn selection_tiles_mut(&mut self) -> &mut Tiles { + &mut self.selection.tiles + } + #[inline] + pub fn selection_positions(&self) -> &FxHashSet> { + &self.selection.positions + } + #[inline] + pub fn selection_positions_mut(&mut self) -> &mut FxHashSet> { + &mut self.selection.positions + } + #[inline] + pub fn clear_selection(&mut self) { + self.stamp.clear(); + self.selection.positions.clear(); + self.selection.tiles.clear(); + self.selection.source = SelectionSource::None; + } + #[inline] + pub fn update_stamp(&mut self, tile_set: Option) { + self.tile_set = tile_set; + self.stamp + .build(self.selection.tiles.iter().map(|(&x, &y)| (x, y))); + } +} + +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Visit)] +pub enum SelectionSource { + #[default] + None, + Widget(Handle), + Node(Handle), +} + +impl TileMapEditorPlugin { + fn initialize_tile_map_panel( + &mut self, + resource: TileResource, + center: Option, + ui: &mut UserInterface, + sender: &MessageSender, + ) { + if let Some(panel) = &mut self.panel { + panel.to_top(ui); + } else if let Some(editor) = &self.tile_set_editor { + let panel = TileMapPanel::new(&mut ui.build_ctx(), self.state.clone(), sender.clone()); + panel.align(editor.window, ui); + self.panel = Some(panel); + } + if let Some(panel) = &mut self.panel { + panel.set_resource(resource, ui); + if let Some(focus) = center { + panel.set_focus(focus, ui); + } + } + } + fn update_state(&mut self) { + let state = self.state.lock(); + if match &state.drawing_mode { + DrawingMode::Pick => false, + DrawingMode::Color | DrawingMode::Property => self.tile_set_editor.is_none(), + _ => self.panel.is_none(), + } { + state.into_mut().drawing_mode = DrawingMode::Pick; + } + } +} + impl EditorPlugin for TileMapEditorPlugin { fn on_start(&mut self, editor: &mut Editor) { editor .asset_browser .preview_generators .add(TileSet::type_uuid(), TileSetPreview); - - let inspector = editor.plugins.get_mut::(); - inspector - .property_editors - .insert(TilesPropertyEditorDefinition); - inspector - .property_editors - .insert(InheritablePropertyEditorDefinition::::new()); } fn on_sync_to_model(&mut self, editor: &mut Editor) { let ui = editor.engine.user_interfaces.first_mut(); + let palette = self.state.lock().selection_palette(); + if let Some(palette) = ui + .try_get_mut(palette) + .and_then(|p| p.cast_mut::()) + { + palette.sync_selection_to_model(); + } + if let Some(tile_set_editor) = self.tile_set_editor.as_mut() { tile_set_editor.sync_to_model(ui); } + if let Some(panel) = self.panel.as_mut() { + panel.sync_to_state(ui); + } let Some(entry) = editor.scenes.current_scene_entry_mut() else { return; @@ -689,26 +1000,21 @@ impl EditorPlugin for TileMapEditorPlugin { fn on_ui_message(&mut self, message: &mut UiMessage, editor: &mut Editor) { let ui = editor.engine.user_interfaces.first_mut(); - let inspector = editor.plugins.get::(); - if let Some(tile_set_editor) = self.tile_set_editor.take() { self.tile_set_editor = tile_set_editor.handle_ui_message( message, ui, &editor.engine.resource_manager, &editor.message_sender, - inspector.property_editors.clone(), editor.engine.serialization_context.clone(), ); } - if let Some(panel) = self.panel.take() { - if let Some(PaletteMessage::ActiveBrush(brush)) = message.data() { - if message.destination() == panel.palette { - *self.brush.lock() = brush.clone(); - } - } + if let Some(OpenTilePanelMessage { resource, center }) = message.data() { + self.initialize_tile_map_panel(resource.clone(), *center, ui, &editor.message_sender); + } + if let Some(panel) = self.panel.take() { let editor_scene_entry = editor.scenes.current_scene_entry_mut(); let tile_map = editor_scene_entry @@ -736,20 +1042,50 @@ impl EditorPlugin for TileMapEditorPlugin { tile_set_editor.update(); } - if let Some(panel) = self.panel.as_mut() { - panel.update( - editor.engine.user_interfaces.first(), - editor.scenes.current_scene_entry_ref(), - ); + self.update_state(); + + if self.state.check_dirty() { + if let Some(tile_set_editor) = self.tile_set_editor.as_mut() { + tile_set_editor.sync_to_state(editor.engine.user_interfaces.first_mut()); + } + if let Some(panel) = self.panel.as_mut() { + panel.sync_to_state(editor.engine.user_interfaces.first_mut()); + } + if let Some(interaction_mode) = editor + .scenes + .current_scene_entry_mut() + .and_then(|s| s.interaction_modes.of_type_mut::()) + { + interaction_mode.sync_to_state(editor.engine.user_interfaces.first_mut()); + } } } fn on_message(&mut self, message: &Message, editor: &mut Editor) { let ui = editor.engine.user_interfaces.first_mut(); - if let Message::OpenTileSetEditor(tile_set) = message { - let tile_set_editor = TileSetEditor::new(tile_set.clone(), &mut ui.build_ctx()); - self.tile_set_editor = Some(tile_set_editor); + let tile_set: Option = if let Message::OpenTileSetEditor(tile_set) = message { + Some(TileResource::TileSet(tile_set.clone())) + } else if let Message::OpenTileMapBrushEditor(brush) = message { + Some(TileResource::Brush(brush.clone())) + } else { + None + }; + + if let Some(tile_set) = tile_set { + if self.tile_set_editor.is_none() { + let tile_set_editor = TileSetEditor::new( + tile_set.clone(), + self.state.clone(), + editor.message_sender.clone(), + editor.engine.resource_manager.clone(), + &mut ui.build_ctx(), + ); + self.tile_set_editor = Some(tile_set_editor); + } else if let Some(editor) = &mut self.tile_set_editor { + editor.set_tile_resource(tile_set.clone(), ui); + } + self.initialize_tile_map_panel(tile_set, None, ui, &editor.message_sender); } let Some(entry) = editor.scenes.current_scene_entry_mut() else { @@ -771,40 +1107,49 @@ impl EditorPlugin for TileMapEditorPlugin { .interaction_modes .remove_typed::(); - if let Some(panel) = self.panel.take() { - panel.destroy(ui); - } + let mut tile_map: Option<&TileMap> = None; + let mut handle = Handle::NONE; for node_handle in selection.nodes().iter() { - if let Some(tile_map) = scene.graph.try_get(*node_handle) { - let Some(tile_map) = tile_map.component_ref::() else { + if let Some(node) = scene.graph.try_get(*node_handle) { + let Some(t) = node.component_ref::() else { continue; }; + tile_map = Some(t); + handle = *node_handle; + self.tile_map = *node_handle; - let panel = TileMapPanel::new( + break; + } + } + if let Some(tile_map) = tile_map { + if let Some(panel) = &mut self.panel { + panel.set_tile_map(tile_map, ui); + panel.to_top(ui); + } else { + let mut panel = TileMapPanel::new( &mut ui.build_ctx(), - editor.scene_viewer.frame(), - tile_map, + self.state.clone(), + editor.message_sender.clone(), ); - - let drawing_modes_panel = panel.drawing_modes_panel; - + panel.align(editor.scene_viewer.frame(), ui); + panel.set_tile_map(tile_map, ui); self.panel = Some(panel); - - entry.interaction_modes.add(TileMapInteractionMode { - tile_map: *node_handle, - brush: self.brush.clone(), - brush_position: Default::default(), - interaction_context: None, - sender: editor.message_sender.clone(), - drawing_mode: DrawingMode::Draw, - drawing_modes_panel, - }); - - break; } + entry.interaction_modes.add(TileMapInteractionMode { + tile_map: handle, + panel: self.panel.as_ref().unwrap().window, + state: self.state.clone(), + click_grid_position: None, + brush_position: None, + sender: editor.message_sender.clone(), + mode: MouseMode::None, + update: TransTilesUpdate::default(), + }); + } else if let Some(panel) = &mut self.panel { + panel.set_visibility(false, ui); } } } diff --git a/editor/src/plugins/tilemap/palette.rs b/editor/src/plugins/tilemap/palette.rs index 73565ca1d..78e8c2876 100644 --- a/editor/src/plugins/tilemap/palette.rs +++ b/editor/src/plugins/tilemap/palette.rs @@ -18,471 +18,1145 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -use crate::plugins::tilemap::tileset::TileSetTileView; -use crate::{ - absm::selectable::{Selectable, SelectableMessage}, - fyrox::{ - core::{ - algebra::{Matrix3, Point2, Vector2}, - color::Color, - math::{OptionRect, Rect}, - pool::Handle, - reflect::prelude::*, - type_traits::prelude::*, - visitor::prelude::*, - }, - graph::{BaseSceneGraph, SceneGraph}, - gui::{ - brush::Brush, - define_constructor, define_widget_deref, - draw::{CommandTexture, Draw, DrawingContext}, - message::{KeyCode, MessageDirection, MouseButton, UiMessage}, - widget::{Widget, WidgetBuilder, WidgetMessage}, - BuildContext, Control, UiNode, UserInterface, - }, - scene::tilemap::{ - brush::{BrushTile, TileMapBrush}, - tileset::TileSetResource, - }, +use fyrox::fxhash::FxHashSet; +use fyrox::scene::tilemap::tileset::{TileSetPageSource, TileSetPropertyValue}; +use fyrox::scene::tilemap::TileSource; + +use crate::asset::item::AssetItem; +use crate::fyrox::{ + core::{ + algebra::{Matrix3, Point2, Vector2}, + color::Color, + math::Rect, + pool::Handle, + reflect::prelude::*, + type_traits::prelude::*, + visitor::prelude::*, + }, + fxhash::FxHashMap, + graph::BaseSceneGraph, + gui::{ + brush::Brush, + define_constructor, define_widget_deref, + draw::{CommandTexture, Draw, DrawingContext}, + formatted_text::{FormattedText, FormattedTextBuilder}, + message::{KeyCode, MessageDirection, MouseButton, UiMessage}, + widget::{Widget, WidgetBuilder, WidgetMessage}, + BuildContext, Control, UiNode, UserInterface, + }, + material::{Material, MaterialResource}, + resource::texture::TextureKind, + scene::tilemap::{ + TilePaletteStage, TileRect, TileRenderData, TileResource, TileSetUpdate, TransTilesUpdate, }, }; -use fyrox::scene::tilemap::tileset::TileDefinitionHandle; use std::ops::{Deref, DerefMut}; +use super::{commands::*, *}; + +pub const DEFAULT_MATERIAL_COLOR: Color = Color::from_rgba(255, 255, 255, 125); + #[derive(Debug, PartialEq, Clone)] pub enum PaletteMessage { - // Direction: FromWidget - ActiveBrush(TileMapBrush), - // Direction: ToWidget - AddTile(Handle), - // Direction: ToWidget - RemoveTile(Handle), - // Direction: FromWidget - MoveTiles(Vec<(Uuid, Vector2)>), - // Direction: FromWidget - DeleteTiles(Vec), - // Direction: FromWidget - DuplicateTiles(Vec<(Uuid, Vector2)>), - InsertTile { - definition_id: TileDefinitionHandle, - position: Vector2, + SetPage { + source: TileResource, + page: Option>, }, + Center(Vector2), + SelectAll, + Delete, + MaterialColor(Color), + SyncToState, } impl PaletteMessage { - define_constructor!(PaletteMessage:ActiveBrush => fn active_brush(TileMapBrush), layout: false); - define_constructor!(PaletteMessage:AddTile => fn add_tile(Handle), layout: false); - define_constructor!(PaletteMessage:RemoveTile => fn remove_tile(Handle), layout: false); - define_constructor!(PaletteMessage:MoveTiles => fn move_tiles(Vec<(Uuid, Vector2)>), layout: false); - define_constructor!(PaletteMessage:DeleteTiles => fn delete_tiles(Vec), layout: false); - define_constructor!(PaletteMessage:DuplicateTiles => fn duplicate_tiles(Vec<(Uuid, Vector2)>), layout: false); - define_constructor!(PaletteMessage:InsertTile => fn insert_tile(definition_id: TileDefinitionHandle, position: Vector2), layout: false); -} - -#[derive(Debug, Clone, PartialEq, Visit, Reflect, Default)] -pub(super) struct Entry { - pub node: Handle, - pub initial_position: Vector2, -} - -#[derive(Debug, Clone, PartialEq, Visit, Reflect, Default)] -pub(super) struct DragContext { - initial_cursor_position: Vector2, - entries: Vec, + define_constructor!(PaletteMessage:SetPage => fn set_page(source: TileResource, page: Option>), layout: false); + define_constructor!(PaletteMessage:Center => fn center(Vector2), layout: false); + define_constructor!(PaletteMessage:SelectAll => fn select_all(), layout: false); + define_constructor!(PaletteMessage:Delete => fn delete(), layout: false); + define_constructor!(PaletteMessage:MaterialColor => fn material_color(Color), layout: false); + define_constructor!(PaletteMessage:SyncToState => fn sync_to_state(), layout: false); } -#[derive(Clone, Debug, Visit, Reflect, PartialEq)] -enum Mode { +#[derive(Clone, Default, Debug, PartialEq)] +enum MouseMode { + #[default] None, Panning { initial_view_position: Vector2, click_position: Vector2, }, - Selecting { - click_position: Vector2, + Dragging { + initial_position: Vector2, + offset: Vector2, + }, + Drawing { + start_tile: Vector2, + end: MousePos, }, - Dragging(DragContext), +} + +#[derive(Clone, Default, Debug, PartialEq)] +struct MousePos { + fine: Vector2, + grid: Vector2, + subgrid: Vector2, +} + +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Visit, Reflect)] +pub enum PaletteKind { + #[default] + Tiles, + Pages, +} + +#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, Hash)] +pub struct Subposition { + pub tile: Vector2, + pub subtile: Vector2, +} + +fn calc_slice_coord(position: f32, step: f32) -> usize { + let p = position / step; + let p = (p - p.floor()) * 3.0; + (p.floor() as i32).clamp(0, 2) as usize } #[derive(Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)] #[type_uuid(id = "5356a864-c026-4bd7-a4b1-30bacf77d8fa")] pub struct PaletteWidget { widget: Widget, - pub tiles: Vec>, + #[visit(skip)] + #[reflect(hidden)] + sender: MessageSender, + pub content: TileResource, + pub page: Option>, + #[visit(skip)] + #[reflect(hidden)] + pub update: TransTilesUpdate, + #[visit(skip)] + #[reflect(hidden)] + pub tile_set_update: TileSetUpdate, + #[reflect(hidden)] + pub state: TileDrawStateRef, + pub kind: TilePaletteStage, + pub editable: bool, + pub slice_mode: bool, + material_color: Color, + #[visit(skip)] + #[reflect(hidden)] + selecting_tiles: FxHashSet>, + #[visit(skip)] + #[reflect(hidden)] + highlight: FxHashMap, + #[visit(skip)] + #[reflect(hidden)] + cursor_position: Option>, + #[visit(skip)] + #[reflect(hidden)] + slice_position: Vector2, + #[visit(skip)] + #[reflect(hidden)] + position_text: FormattedText, + #[visit(skip)] + #[reflect(hidden)] + overlay: PaletteOverlay, view_position: Vector2, zoom: f32, tile_size: Vector2, - selection: Vec>, - mode: Mode, + #[visit(skip)] + #[reflect(hidden)] + mode: MouseMode, } define_widget_deref!(PaletteWidget); +#[derive(Default, Clone, Debug)] +struct PaletteOverlay { + active: bool, + movable_position: Vector2, + movable_tiles: FxHashMap, TileRenderData>, + erased_tiles: FxHashSet>, +} + +impl PaletteOverlay { + pub fn covers(&self, position: Vector2) -> bool { + self.active + && (self.erased_tiles.contains(&position) + || self + .movable_tiles + .contains_key(&(position - self.movable_position))) + } + pub fn iter(&self) -> impl Iterator, &TileRenderData)> { + let offset = self.movable_position; + self.movable_tiles + .iter() + .filter(|_| self.active) + .map(move |(p, d)| (*p + offset, d)) + } + pub fn clear(&mut self) { + self.movable_tiles.clear(); + self.erased_tiles.clear(); + } + pub fn set_to_stamp(&mut self, stamp: &Stamp, tile_set: &TileSet) { + self.movable_tiles.clear(); + self.erased_tiles.clear(); + for (pos, handle) in stamp.iter() { + let data = tile_set + .get_transformed_render_data(stamp.transformation(), *handle) + .unwrap_or_else(TileRenderData::missing_data); + let _ = self.movable_tiles.insert(pos, data); + } + } +} + +fn apply_transform(trans: &Matrix3, point: Vector2) -> Vector2 { + trans.transform_point(&Point2::from(point)).coords +} + +fn invert_transform(trans: &Matrix3) -> Matrix3 { + trans.try_inverse().unwrap_or(Matrix3::identity()) +} + impl PaletteWidget { - pub fn point_to_local_space(&self, point: Vector2) -> Vector2 { - self.visual_transform() - .try_inverse() - .unwrap_or_default() - .transform_point(&Point2::from(point)) - .coords - } - - pub fn update_transform(&self, ui: &UserInterface) { - let transform = - Matrix3::new_translation(&-self.view_position) * Matrix3::new_scaling(self.zoom); - - ui.send_message(WidgetMessage::layout_transform( - self.handle(), - MessageDirection::ToWidget, - transform, - )); + pub fn stage(&self) -> TilePaletteStage { + match &self.kind { + TilePaletteStage::Pages => TilePaletteStage::Pages, + _ => TilePaletteStage::Tiles, + } } - fn make_drag_context(&self, ui: &UserInterface) -> DragContext { - for selected in self.selection.iter() { - ui.send_message(WidgetMessage::topmost( - *selected, - MessageDirection::ToWidget, - )); + fn sync_to_state(&mut self) { + let state = self.state.lock(); + if state.selection_palette() != self.handle { + self.selecting_tiles.clear(); + } + self.slice_mode = state.drawing_mode == DrawingMode::Property + && matches!(state.draw_value, DrawValue::I8(_)); + if self.editable && self.kind == TilePaletteStage::Tiles { + if state.drawing_mode == DrawingMode::Draw + || state.drawing_mode == DrawingMode::FloodFill + { + if let Some(tile_set) = self.content.get_tile_set() { + self.overlay + .set_to_stamp(&state.stamp, &tile_set.data_ref()); + } else { + self.overlay.clear(); + } + } else { + self.overlay.clear(); + } } + } + + pub fn screen_point_to_tile_point(&self, point: Vector2) -> Vector2 { + let trans = self.visual_transform() * self.tile_to_local(); + let trans = invert_transform(&trans); + apply_transform(&trans, point) + } - DragContext { - initial_cursor_position: self.point_to_local_space(ui.cursor_position()), - entries: self - .selection - .iter() - .map(|n| Entry { - node: *n, - initial_position: ui.node(*n).actual_local_position(), - }) - .collect(), + pub fn tile_point_to_screen_point(&self, point: Vector2) -> Vector2 { + let trans = self.visual_transform() * self.tile_to_local(); + apply_transform(&trans, point) + } + + fn tile_to_local(&self) -> Matrix3 { + let translation = self.actual_local_size.get() * 0.5; + Matrix3::new_translation(&self.view_position) + * Matrix3::new_translation(&translation) + * Matrix3::new_nonuniform_scaling(&Vector2::new(self.zoom, -self.zoom)) + } + + fn calc_mouse_position(&self, screen_point: Vector2) -> MousePos { + let tile_point = self.screen_point_to_tile_point(screen_point); + MousePos { + fine: screen_point, + grid: self.tile_point_to_grid_pos(tile_point), + subgrid: if self.slice_mode { + Vector2::new( + calc_slice_coord(tile_point.x, self.tile_size.x), + calc_slice_coord(tile_point.y, self.tile_size.y), + ) + } else { + Vector2::default() + }, } } - fn tiles_to_brush(&self, tiles: &[Handle], ui: &UserInterface) -> TileMapBrush { - let mut tiles = tiles - .iter() - .filter_map(|h| ui.try_get_of_type::(*h)) - .map(|view| BrushTile { - definition_handle: view.definition_handle, - local_position: view.local_position, - id: view.id, - }) - .collect::>(); + fn set_cursor_position(&mut self, pos: Option>) { + if self.cursor_position == pos { + return; + } + self.cursor_position = pos; + let text = if let Some(pos) = pos { + format!("{}, {}", pos.x, pos.y) + } else { + "".into() + }; + self.position_text.set_text(text); + self.position_text.build(); + } - let mut min_x = i32::MAX; - let mut min_y = i32::MAX; - for tile in tiles.iter() { - if tile.local_position.x < min_x { - min_x = tile.local_position.x; + fn tile_point_to_grid_pos(&self, pos: Vector2) -> Vector2 { + let s = self.tile_size; + Vector2::new((pos.x / s.x).floor() as i32, (pos.y / s.y).floor() as i32) + } + fn send_update(&mut self) { + if self.kind != TilePaletteStage::Tiles { + panic!(); + } + let Some(page) = self.page else { + return; + }; + match &self.content { + TileResource::Empty => (), + TileResource::TileSet(resource) => { + self.tile_set_update.clear(); + self.tile_set_update + .convert(&self.update, &resource.data_ref(), page); + self.sender.do_command(SetTileSetTilesCommand { + tile_set: resource.clone(), + tiles: self.tile_set_update.clone(), + }); + self.tile_set_update.clear(); + self.update.clear(); } - if tile.local_position.y < min_y { - min_y = tile.local_position.y; + TileResource::Brush(resource) => { + if let Some(tile_set) = &resource.data_ref().tile_set { + self.sender.do_command(SetBrushTilesCommand { + brush: resource.clone(), + page, + tiles: self.update.build_tiles_update(&tile_set.data_ref()), + }); + } } } - let origin = Vector2::new(min_x, min_y); - - for tile in tiles.iter_mut() { - tile.local_position -= origin; - // Flip the position, because world's coordinate system is X-left Y-up, but palette has - // X-right Y-down. - tile.local_position = -tile.local_position; + } + fn send_tile_set_update(&mut self) { + if self.kind != TilePaletteStage::Tiles { + panic!(); + } + if let TileResource::TileSet(resource) = &self.content { + self.sender.do_command(SetTileSetTilesCommand { + tile_set: resource.clone(), + tiles: self.tile_set_update.clone(), + }); + self.tile_set_update.clear(); } - - TileMapBrush { tiles } } - - fn set_selection(&mut self, new_selection: &[Handle], ui: &UserInterface) { - if self.selection != new_selection { - for &child in self - .children() - .iter() - .filter(|n| ui.node(**n).query_component::().is_some()) - { - ui.send_message( - SelectableMessage::select( - child, - MessageDirection::ToWidget, - new_selection.contains(&child), - ) - .with_handled(true), - ); + fn delete_tiles(&mut self, ui: &mut UserInterface) -> bool { + let state = self.state.lock_mut(); + if state.selection_palette() != self.handle || !state.has_selection() { + return false; + } + for position in state.selection_positions() { + self.update.insert(*position, None); + } + drop(state); + self.send_update(); + true + } + fn set_page( + &mut self, + resource: TileResource, + page: Option>, + _ui: &mut UserInterface, + ) { + let mut state = self.state.lock_mut(); + if state.selection_palette() == self.handle { + self.selecting_tiles.clear(); + state.clear_selection(); + } + self.page = page; + self.content = resource; + } + fn send_new_page(&mut self, page: Vector2, ui: &mut UserInterface) { + self.page = Some(page); + ui.send_message(PaletteMessage::set_page( + self.handle, + MessageDirection::FromWidget, + self.content.clone(), + Some(page), + )); + } + fn drawing_mode(&self) -> Option { + if self.editable { + match self.kind { + TilePaletteStage::Pages => Some(DrawingMode::Pick), + TilePaletteStage::Tiles => Some(self.state.lock().drawing_mode), } + } else { + Some(DrawingMode::Pick) + } + } + pub fn sync_selection_to_model(&mut self) { + let page = self.page.unwrap_or_default(); + let mut state = self.state.lock_mut(); + self.selecting_tiles.clone_from(state.selection_positions()); + let tiles = state.selection_tiles_mut(); + self.content.get_tiles( + self.stage(), + page, + self.selecting_tiles.iter().copied(), + tiles, + ); + self.selecting_tiles.clear(); + } + fn update_selection(&mut self) { + let MouseMode::Drawing { start_tile, end } = self.mode.clone() else { + return; + }; + let end_tile = end.grid; + if self.kind == TilePaletteStage::Tiles && self.page.is_none() { + return; + } + let page = self.page.unwrap_or_default(); + let mut state = self.state.lock_mut(); + state.set_palette(self.handle); + let positions = state.selection_positions_mut(); + positions.clone_from(&self.selecting_tiles); + positions.extend(TileRect::from_points(start_tile, end_tile).iter()); + let tiles = state.selection_tiles_mut(); + tiles.clear(); + self.content.get_tiles( + self.stage(), + page, + self.selecting_tiles.iter().copied(), + tiles, + ); + let rect = TileRect::from_points(start_tile, end_tile); + self.content + .get_tiles(self.stage(), page, rect.iter(), tiles); + state.update_stamp(self.content.get_tile_set()); + } + fn finalize_selection(&mut self, ui: &mut UserInterface) { + let MouseMode::Drawing { start_tile, end } = self.mode.clone() else { + return; + }; + let end_tile = end.grid; + match self.kind { + TilePaletteStage::Tiles => { + if self.page.is_none() { + return; + } + } + TilePaletteStage::Pages => self.send_new_page(end_tile, ui), + _ => (), + } + let page = self.page.unwrap_or_default(); + self.selecting_tiles + .extend(TileRect::from_points(start_tile, end_tile).iter()); + let mut state = self.state.lock_mut(); + state.set_palette(self.handle); + let positions = state.selection_positions_mut(); + positions.clone_from(&self.selecting_tiles); + let tiles = state.selection_tiles_mut(); + tiles.clear(); + self.content.get_tiles( + self.stage(), + page, + self.selecting_tiles.iter().copied(), + tiles, + ); + state.update_stamp(self.content.get_tile_set()); + } + fn select_all(&mut self) { + let Some(page) = self.page else { + return; + }; + let mut state = self.state.lock_mut(); + let results = match self.stage() { + TilePaletteStage::Tiles => self.content.get_all_tile_positions(page), + TilePaletteStage::Pages => self.content.get_all_page_positions(), + }; + state.set_palette(self.handle); + let tiles = state.selection_tiles_mut(); + tiles.clear(); + self.content + .get_tiles(self.stage(), page, results.into_iter(), tiles); + state.update_stamp(self.content.get_tile_set()); + } + fn begin_motion(&mut self, mode: DrawingMode, pos: MousePos, ui: &mut UserInterface) { + match mode { + DrawingMode::Pick => { + if self.editable && ui.keyboard_modifiers().alt { + self.mode = MouseMode::Dragging { + initial_position: pos.fine, + offset: Vector2::new(0, 0), + }; + self.begin_drag(pos, ui); + } else { + let start_tile = pos.grid; + self.mode = MouseMode::Drawing { + start_tile, + end: pos, + }; + if !ui.keyboard_modifiers().shift { + self.selecting_tiles.clear(); + } + self.update_selection(); + } + } + _ => { + let start_tile = pos.grid; + self.mode = MouseMode::Drawing { + start_tile, + end: pos.clone(), + }; + self.draw(mode, start_tile, start_tile, pos.subgrid, ui); + } + } + } - self.selection = new_selection.to_vec(); - - ui.send_message(PaletteMessage::active_brush( - self.handle(), - MessageDirection::FromWidget, - self.tiles_to_brush(&self.selection, ui), - )); + fn drag_offset(&self, drag_vector: Vector2) -> Vector2 { + let t = self.tile_size; + let p = drag_vector / self.zoom; + Vector2::new((p.x / t.x).round() as i32, -(p.y / t.y).round() as i32) + } + fn continue_motion(&mut self, mode: DrawingMode, pos: MousePos, ui: &mut UserInterface) { + match mode { + DrawingMode::Pick => match &self.mode { + MouseMode::Dragging { + initial_position, .. + } => { + let offset = self.drag_offset(pos.fine - *initial_position); + self.mode = MouseMode::Dragging { + initial_position: *initial_position, + offset, + }; + self.overlay.movable_position = offset; + } + MouseMode::Drawing { start_tile, end } => { + if end.grid != pos.grid { + self.mode = MouseMode::Drawing { + start_tile: *start_tile, + end: pos, + }; + self.update_selection(); + } + } + _ => (), + }, + mode => match self.mode.clone() { + MouseMode::None => { + if mode == DrawingMode::Draw || mode == DrawingMode::FloodFill { + self.overlay.movable_position = pos.grid; + } + } + MouseMode::Drawing { start_tile, end } => { + if end.grid != pos.grid || self.slice_mode && end.subgrid != pos.subgrid { + self.draw(mode, start_tile, pos.grid, pos.subgrid, ui); + self.mode = MouseMode::Drawing { + start_tile, + end: pos, + }; + } + } + _ => (), + }, + } + } - // Make sure to update dragging context if we're in Drag mode. - if let Mode::Dragging(_) = self.mode { - self.mode = Mode::Dragging(self.make_drag_context(ui)); + fn end_motion(&mut self, mode: DrawingMode, pos: MousePos, ui: &mut UserInterface) { + match mode { + DrawingMode::Pick => match &self.mode { + MouseMode::Dragging { + initial_position, .. + } => { + let offset = self.drag_offset(pos.fine - *initial_position); + self.mode = MouseMode::None; + self.end_drag(offset); + } + MouseMode::Drawing { start_tile, .. } => { + self.mode = MouseMode::Drawing { + start_tile: *start_tile, + end: pos, + }; + self.finalize_selection(ui); + } + _ => (), + }, + _ => { + if let MouseMode::Drawing { start_tile, .. } = self.mode.clone() { + let end_tile = pos.grid; + self.mode = MouseMode::None; + self.end_draw(mode, start_tile, end_tile, ui); + } } } } - fn local_to_grid_pos(&self, pos: Vector2) -> Vector2 { - Vector2::new( - (pos.x / self.tile_size.x) as i32, - (pos.y / self.tile_size.y) as i32, - ) + fn begin_drag(&mut self, pos: MousePos, ui: &mut UserInterface) { + let state = self.state.lock(); + if state.selection_palette() != self.handle { + return; + } + let Some(page) = self.page else { + return; + }; + if self.kind == TilePaletteStage::Tiles && self.content.is_material_page(page) { + return; + } + let Some(tile_set) = self.content.get_tile_set() else { + return; + }; + let mut tile_set = tile_set.state(); + let Some(tile_set) = tile_set.data() else { + return; + }; + let tiles = state.selection_positions(); + self.overlay.movable_position = Vector2::default(); + self.overlay.erased_tiles.clear(); + self.overlay.movable_tiles.clear(); + for pos in tiles.iter() { + let Some(handle) = TileDefinitionHandle::try_new(page, *pos) else { + continue; + }; + let Some(data) = tile_set.get_tile_render_data(self.kind, handle) else { + continue; + }; + let _ = self.overlay.erased_tiles.insert(*pos); + let _ = self.overlay.movable_tiles.insert(*pos, data); + } } -} -impl Control for PaletteWidget { - fn measure_override(&self, ui: &UserInterface, _available_size: Vector2) -> Vector2 { - for child_handle in self.widget.children() { - ui.measure_node(*child_handle, self.tile_size); + fn end_drag(&mut self, offset: Vector2) { + match self.kind { + TilePaletteStage::Pages => self.end_page_drag(offset), + TilePaletteStage::Tiles => self.end_tile_drag(offset), } - - Vector2::default() + let mut state = self.state.lock_mut(); + let sel = state.selection_positions_mut(); + sel.clear(); + sel.extend(self.overlay.iter().map(|(p, _)| p)); + self.overlay.erased_tiles.clear(); + self.overlay.movable_tiles.clear(); } - fn arrange_override(&self, ui: &UserInterface, final_size: Vector2) -> Vector2 { - for &child_handle in self.widget.children() { - if let Some(tile) = ui.try_get_of_type::(child_handle) { - ui.arrange_node( - child_handle, - &Rect::new( - tile.local_position.x as f32 * self.tile_size.x, - tile.local_position.y as f32 * self.tile_size.y, - self.tile_size.x, - self.tile_size.y, - ), - ); + fn end_page_drag(&mut self, offset: Vector2) { + let state = self.state.lock(); + if state.selection_palette() != self.handle { + return; + } + let pages = state + .selection_positions() + .iter() + .copied() + .collect::>(); + match self.content.clone() { + TileResource::Empty => (), + TileResource::TileSet(tile_set) => { + self.sender + .do_command(MoveTileSetPageCommand::new(tile_set, pages, offset)); + } + TileResource::Brush(brush) => { + self.sender + .do_command(MoveBrushPageCommand::new(brush, pages, offset)); + } + } + } + fn end_tile_drag(&mut self, offset: Vector2) { + let state = self.state.lock(); + if state.selection_palette() != self.handle { + return; + } + let Some(page) = self.page else { + return; + }; + let tiles = state + .selection_positions() + .iter() + .copied() + .collect::>(); + match self.content.clone() { + TileResource::Empty => (), + TileResource::TileSet(tile_set) => { + self.sender + .do_command(MoveTileSetTileCommand::new(tile_set, page, tiles, offset)); + } + TileResource::Brush(brush) => { + self.sender + .do_command(MoveBrushTileCommand::new(brush, page, tiles, offset)); } } - - final_size } - fn draw(&self, ctx: &mut DrawingContext) { - let grid_size = 9999.0; - - let grid_bounds = self - .widget - .bounding_rect() - .inflate(grid_size, grid_size) - .translate(Vector2::new(grid_size * 0.5, grid_size * 0.5)); - ctx.push_rect_filled(&grid_bounds, None); + fn draw( + &mut self, + mode: DrawingMode, + start: Vector2, + end: Vector2, + sub_pos: Vector2, + ui: &mut UserInterface, + ) { + let Some(page) = self.page else { + return; + }; + let state = self.state.lock(); + let stamp = &state.stamp; + match mode { + DrawingMode::Pick => (), + DrawingMode::Draw | DrawingMode::FloodFill => self.update.draw_tiles(end, stamp), + DrawingMode::Erase => { + if stamp.is_empty() { + self.update.erase(end); + } else { + self.update.erase_stamp(end, stamp); + } + } + DrawingMode::RectFill => { + self.update.clear(); + if state.random_mode { + self.update.rect_fill_random(start, end, stamp); + } else { + self.update.rect_fill(start, end, stamp); + } + } + DrawingMode::NineSlice => { + self.update.clear(); + if state.random_mode { + self.update.nine_slice_random(start, end, stamp); + } else { + self.update.nine_slice(start, end, stamp); + } + } + DrawingMode::Line => { + self.update.clear(); + if state.random_mode { + self.update.draw_line(start, end, &RandomTileSource(stamp)); + } else { + self.update.draw_line(start, end, &stamp.repeat(start, end)); + } + } + DrawingMode::Material => { + if let DrawValue::Material(value) = &state.draw_value { + self.tile_set_update.set_material(page, end, value.clone()); + } + } + DrawingMode::Property => { + use TileSetPropertyValue as Value; + if let Some(prop_id) = &state.active_prop { + match &state.draw_value { + DrawValue::I8(v) => self + .tile_set_update + .set_property_slice(page, end, sub_pos, *prop_id, *v), + DrawValue::I32(v) => self.tile_set_update.set_property( + page, + end, + *prop_id, + Some(Value::I32(*v)), + ), + DrawValue::F32(v) => self.tile_set_update.set_property( + page, + end, + *prop_id, + Some(Value::F32(*v)), + ), + DrawValue::String(v) => self.tile_set_update.set_property( + page, + end, + *prop_id, + Some(Value::String(v.clone())), + ), + _ => (), + } + } + } + DrawingMode::Color => { + if let DrawValue::Color(v) = &state.draw_value { + self.tile_set_update.set_color(page, end, *v); + } + } + DrawingMode::Collider => { + if let (Some(prop_id), DrawValue::Collider(v)) = + (&state.active_prop, &state.draw_value) + { + self.tile_set_update.set_collider(page, end, *prop_id, *v); + } + } + } + } + fn end_draw( + &mut self, + mode: DrawingMode, + _start: Vector2, + _end: Vector2, + _ui: &mut UserInterface, + ) { + match mode { + DrawingMode::Pick => (), + DrawingMode::Draw => self.send_update(), + DrawingMode::Erase => self.send_update(), + DrawingMode::Line => self.send_update(), + DrawingMode::FloodFill => self.send_update(), + DrawingMode::RectFill => self.send_update(), + DrawingMode::NineSlice => self.send_update(), + DrawingMode::Material => self.send_tile_set_update(), + DrawingMode::Property => self.send_tile_set_update(), + DrawingMode::Color => self.send_tile_set_update(), + DrawingMode::Collider => self.send_tile_set_update(), + } + } + fn accept_material_drop(&mut self, _material: MaterialResource, _ui: &UserInterface) { + // TODO + todo!(); + } + fn push_cell_rect(&self, position: Vector2, thickness: f32, ctx: &mut DrawingContext) { + let size = self.tile_size; + let position = Vector2::new(position.x as f32 * size.x, position.y as f32 * size.y); + let rect = Rect { position, size }.inflate(thickness * 0.5, thickness * 0.5); + ctx.push_rect(&rect, thickness); + } + fn push_cell_rect_filled(&self, position: Vector2, ctx: &mut DrawingContext) { + let size = self.tile_size; + let position = Vector2::new(position.x as f32 * size.x, position.y as f32 * size.y); + let rect = Rect { position, size }; + ctx.push_rect_filled(&rect, None); + } + fn push_subcell_rect(&self, position: Subposition, thickness: f32, ctx: &mut DrawingContext) { + let size = self.tile_size; + let subsize = size / 3.0; + let position = Vector2::new( + position.tile.x as f32 * size.x + position.subtile.x as f32 * subsize.x, + position.tile.y as f32 * size.y + position.subtile.y as f32 * subsize.y, + ); + let rect = Rect { + position, + size: subsize, + }; + ctx.push_rect(&rect, thickness); + } + fn push_subcell_rect_filled(&self, position: Subposition, ctx: &mut DrawingContext) { + let size = self.tile_size; + let subsize = size / 3.0; + let position = Vector2::new( + position.tile.x as f32 * size.x + position.subtile.x as f32 * subsize.x, + position.tile.y as f32 * size.y + position.subtile.y as f32 * subsize.y, + ); + let rect = Rect { + position, + size: subsize, + }; + ctx.push_rect_filled(&rect, None); + } + fn push_erase_area(&self, thickness: f32, ctx: &mut DrawingContext) { + let Some(cursor_position) = self.cursor_position else { + return; + }; + let state = self.state.lock(); + let stamp = &state.stamp; + if stamp.is_empty() { + self.push_x(cursor_position, thickness, ctx); + } else { + for pos in stamp.keys() { + self.push_x(cursor_position + pos, thickness, ctx); + } + } + } + fn push_x(&self, position: Vector2, thickness: f32, ctx: &mut DrawingContext) { + let size = self.tile_size; + let position = Vector2::new(position.x as f32 * size.x, position.y as f32 * size.y); + ctx.push_line(position, position + size, thickness); + ctx.push_line( + position + Vector2::new(size.x, 0.0), + position + Vector2::new(0.0, size.y), + thickness, + ); + } + fn commit_color(&self, color: Color, ctx: &mut DrawingContext) { ctx.commit( self.clip_bounds(), - self.widget.background(), + Brush::Solid(color), CommandTexture::None, None, ); + } + fn draw_material_background(&self, ctx: &mut DrawingContext) { + if self.kind != TilePaletteStage::Tiles || !self.editable { + return; + } + let TileResource::TileSet(tile_set) = &self.content else { + return; + }; + let Some(page) = self.page else { + return; + }; + let mut tile_set = tile_set.state(); + let Some(page) = tile_set.data().and_then(|t| t.pages.get(&page)) else { + return; + }; + let TileSetPageSource::Material(mat) = &page.source else { + return; + }; + let tile_size = mat.tile_size; + let mut material = mat.material.state(); + let Some(material) = material.data() else { + return; + }; + let Some(tex) = material.texture("diffuseTexture") else { + return; + }; + let TextureKind::Rectangle { width, height } = tex.data_ref().kind() else { + return; + }; + let width = width as f32 * self.tile_size.x / (tile_size.x as f32); + let height = height as f32 * self.tile_size.y / (tile_size.y as f32); + let rect = Rect { + position: Vector2::default(), + size: Vector2::new(width, height), + }; + ctx.transform_stack.push( + ctx.transform_stack.transform() + * Matrix3::new_nonuniform_scaling(&Vector2::new(1.0, -1.0)), + ); + ctx.push_rect_filled(&rect, None); + ctx.commit( + self.clip_bounds(), + Brush::Solid(self.material_color), + CommandTexture::Texture(tex.into_untyped()), + None, + ); + ctx.transform_stack.pop(); + } +} - ctx.push_grid(self.zoom, self.tile_size, grid_bounds); +impl Control for PaletteWidget { + fn draw(&self, ctx: &mut DrawingContext) { + let bounds = self.bounding_rect(); + ctx.push_rect_filled(&bounds, None); ctx.commit( self.clip_bounds(), - Brush::Solid(Color::repeat_opaque(60)), + self.widget.background(), CommandTexture::None, None, ); - } + let page = self.page.unwrap_or_default(); + let transform = self.tile_to_local(); + let inv_transform = invert_transform(&transform); + let bounds = bounds.transform(&inv_transform); + ctx.transform_stack + .push(self.visual_transform() * transform); + + self.draw_material_background(ctx); + + let stage = self.stage(); + if stage == TilePaletteStage::Tiles && self.page.is_some() + || stage == TilePaletteStage::Pages + { + self.content.tile_render_loop(stage, page, |pos, data| { + if self.overlay.covers(pos) { + return; + } + if self.update.contains_key(&pos) { + return; + } + let Some(handle) = TileDefinitionHandle::try_new(page, pos) else { + return; + }; + if self.tile_set_update.contains_key(&handle) { + return; + } + let t = self.tile_size; + let position = Vector2::new(pos.x as f32 * t.x, pos.y as f32 * t.y); + let rect = Rect { position, size: t }; + draw_tile(rect, self.clip_bounds(), &data, ctx); + }); + } - fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { - self.widget.handle_routed_message(ui, message); + if let Some(tile_set) = self.content.get_tile_set() { + let mut tile_set = tile_set.state(); + if let Some(tile_set) = tile_set.data() { + for (pos, v) in self.update.iter() { + let Some((t, h)) = v else { + continue; + }; + let Some(data) = tile_set.get_transformed_render_data(*t, *h) else { + continue; + }; + let t = self.tile_size; + let position = Vector2::new(pos.x as f32 * t.x, pos.y as f32 * t.y); + let rect = Rect { position, size: t }; + draw_tile(rect, self.clip_bounds(), &data, ctx); + } + for (handle, v) in self.tile_set_update.iter() { + let pos = handle.tile(); + let Some(handle) = v.substitute_transform_handle(*handle) else { + continue; + }; + let data = tile_set + .get_tile_render_data(TilePaletteStage::Tiles, handle) + .unwrap_or_else(TileRenderData::missing_data); + let Some(data) = v.modify_render(&data) else { + continue; + }; + let t = self.tile_size; + let position = Vector2::new(pos.x as f32 * t.x, pos.y as f32 * t.y); + let rect = Rect { position, size: t }; + draw_tile(rect, self.clip_bounds(), &data, ctx); + } + for (pos, data) in self.overlay.iter() { + let t = self.tile_size; + let position = Vector2::new(pos.x as f32 * t.x, pos.y as f32 * t.y); + let rect = Rect { position, size: t }; + draw_tile(rect, self.clip_bounds(), data, ctx); + } + } + } - if let Some(SelectableMessage::Select(true)) = message.data() { - if message.direction() == MessageDirection::FromWidget && !message.handled() { - let selected_node = message.destination(); + ctx.push_grid(self.zoom, self.tile_size, bounds); + self.commit_color(Color::BLACK, ctx); - let new_selection = if ui.keyboard_modifiers().control { - let mut selection = self.selection.clone(); - selection.push(selected_node); - selection - } else { - vec![selected_node] + // Transform areas + if stage == TilePaletteStage::Tiles && self.content.is_transform_page(page) { + let area_size = Vector2::new(self.tile_size.x * 4.0, self.tile_size.y * 2.0); + ctx.push_grid(self.zoom, area_size, bounds); + self.commit_color(Color::ORANGE, ctx); + } + + let line_thickness = 1.0 / self.zoom; + let left = bounds.left_bottom_corner().x; + let right = bounds.right_bottom_corner().x; + let top = bounds.left_top_corner().y; + let bottom = bounds.left_bottom_corner().y; + + // Axis lines + ctx.push_line( + Vector2::new(left, 0.0), + Vector2::new(right, 0.0), + line_thickness, + ); + ctx.push_line( + Vector2::new(0.0, top), + Vector2::new(0.0, bottom), + line_thickness, + ); + self.commit_color(Color::RED, ctx); + + for (pos, c) in self.highlight.iter() { + self.push_subcell_rect_filled(*pos, ctx); + self.commit_color(*c, ctx); + } + for pos in self.highlight.keys() { + self.push_subcell_rect(*pos, line_thickness, ctx); + } + self.commit_color(Color::BLACK, ctx); + + if let Some(pos) = self.cursor_position { + if self.slice_mode { + let pos = Subposition { + tile: pos, + subtile: self.slice_position, }; + self.push_subcell_rect_filled(pos, ctx); + } else { + self.push_cell_rect_filled(pos, ctx); + } + self.commit_color(Color::WHITE.with_new_alpha(150), ctx); + } + + if self.editable + && self.kind == TilePaletteStage::Tiles + && self.state.lock().drawing_mode == DrawingMode::Erase + { + self.push_erase_area(line_thickness, ctx); + self.commit_color(Color::RED, ctx); + } - self.set_selection(&new_selection, ui); + // Selection highlight + if stage == TilePaletteStage::Pages { + if let Some(active) = self.page { + self.push_cell_rect(active, line_thickness * 3.0, ctx); + self.commit_color(Color::GREEN_YELLOW, ctx); + } + } + let state = self.state.lock(); + if state.selection_palette() == self.handle { + for sel in state.selection_positions() { + self.push_cell_rect(*sel, line_thickness * 2.0, ctx) } - } else if let Some(WidgetMessage::MouseDown { pos, button }) = message.data() { + self.commit_color(Color::WHITE, ctx); + } + + ctx.transform_stack.pop(); + + ctx.draw_text( + self.clip_bounds(), + Vector2::new(2.0, 2.0), + &self.position_text, + ); + } + + fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { + self.widget.handle_routed_message(ui, message); + + if let Some(WidgetMessage::MouseDown { pos, button }) = message.data() { + ui.capture_mouse(self.handle()); if *button == MouseButton::Middle { - self.mode = Mode::Panning { + self.mode = MouseMode::Panning { initial_view_position: self.view_position, click_position: *pos, }; - ui.capture_mouse(self.handle()); } else if *button == MouseButton::Left && !message.handled() { - if message.destination() != self.handle { - if ui.keyboard_modifiers().alt { - self.mode = Mode::Dragging(self.make_drag_context(ui)); - } else { - self.mode = Mode::Selecting { - click_position: *pos, - }; - } - } else { - self.set_selection(&[], ui); + if let Some(mode) = self.drawing_mode() { + let mouse_pos = self.calc_mouse_position(*pos); + self.begin_motion(mode, mouse_pos, ui); } } - } else if let Some(WidgetMessage::MouseUp { button, .. }) = message.data() { - if *button == MouseButton::Middle { - if matches!(self.mode, Mode::Panning { .. }) { - self.mode = Mode::None; - ui.release_mouse_capture(); - } - } else if *button == MouseButton::Left { - match self.mode { - Mode::Selecting { .. } => { - self.mode = Mode::None; - } - Mode::Dragging(ref drag_context) => { - ui.send_message(PaletteMessage::move_tiles( - self.handle, - MessageDirection::FromWidget, - drag_context - .entries - .iter() - .map(|entry| { - let tile_view = - ui.try_get_of_type::(entry.node).unwrap(); - - (tile_view.id, tile_view.local_position) - }) - .collect::>(), - )); - - self.mode = Mode::None; - } - _ => (), + } else if let Some(WidgetMessage::MouseUp { pos, button, .. }) = message.data() { + ui.release_mouse_capture(); + if *button == MouseButton::Left { + if let Some(mode) = self.drawing_mode() { + let mouse_pos = self.calc_mouse_position(*pos); + self.end_motion(mode, mouse_pos, ui); } } + self.mode = MouseMode::None; } else if let Some(WidgetMessage::MouseMove { pos, .. }) = message.data() { - let local_cursor_pos = self.point_to_local_space(*pos); - - match self.mode { - Mode::None => {} - Mode::Selecting { click_position } => { - let local_click_position = self.point_to_local_space(click_position); - let grid_click_position = self.local_to_grid_pos(local_click_position); - let current_grid_position = self.local_to_grid_pos(local_cursor_pos); - - let mut rect = OptionRect::default(); - - rect.push(grid_click_position); - rect.push(current_grid_position); - - let selection_bounds = rect.unwrap(); - - let mut selection = Vec::new(); - for tile in self.tiles.iter() { - let tile_ref = ui.try_get_of_type::(*tile).unwrap(); - if selection_bounds.contains(tile_ref.local_position) { - selection.push(*tile); - } - } - self.set_selection(&selection, ui); - } - Mode::Dragging(ref drag_context) => { - for entry in drag_context.entries.iter() { - let new_position = entry.initial_position - + (local_cursor_pos - drag_context.initial_cursor_position); - - let grid_position = self.local_to_grid_pos(new_position); - - ui.send_message(TileViewMessage::local_position( - entry.node, - MessageDirection::ToWidget, - grid_position, - )); - } - } - Mode::Panning { - initial_view_position, - click_position, - } => { - self.view_position = initial_view_position + (*pos - click_position); - self.update_transform(ui); - } + if let MouseMode::Panning { + initial_view_position, + click_position, + } = &self.mode + { + self.view_position = initial_view_position + (*pos - click_position); } + let mouse_pos = self.calc_mouse_position(*pos); + self.slice_position = mouse_pos.subgrid; + self.set_cursor_position(Some(mouse_pos.grid)); + if let Some(drawing_mode) = self.drawing_mode() { + self.continue_motion(drawing_mode, mouse_pos, ui); + } + } else if let Some(WidgetMessage::MouseEnter { .. }) = message.data() { + self.overlay.active = true; + } else if let Some(WidgetMessage::MouseLeave { .. }) = message.data() { + self.overlay.active = false; + self.set_cursor_position(None); } else if let Some(WidgetMessage::MouseWheel { amount, pos }) = message.data() { - let cursor_pos = (*pos - self.screen_position()).scale(self.zoom); - + let tile_pos = self.screen_point_to_tile_point(*pos); self.zoom = (self.zoom + 0.1 * amount).clamp(0.2, 2.0); - - let new_cursor_pos = (*pos - self.screen_position()).scale(self.zoom); - - self.view_position -= (new_cursor_pos - cursor_pos).scale(self.zoom); - - self.update_transform(ui); + let new_pos = self.tile_point_to_screen_point(tile_pos); + self.view_position += pos - new_pos; + } else if let Some(WidgetMessage::Drop(dropped)) = message.data() { + if let Some(item) = ui.node(*dropped).cast::() { + if let Some(material) = item.resource::() { + self.accept_material_drop(material, ui); + } + } } else if let Some(msg) = message.data::() { if message.direction() == MessageDirection::ToWidget { match msg { - PaletteMessage::AddTile(tile) => { - if !self.tiles.contains(tile) { - ui.send_message(WidgetMessage::link( - *tile, - MessageDirection::ToWidget, - self.handle, - )); - self.tiles.push(*tile); - } - } - PaletteMessage::RemoveTile(tile) => { - if let Some(position) = self - .tiles - .iter() - .position(|existing_tile| existing_tile == tile) - { - ui.send_message(WidgetMessage::remove( - *tile, - MessageDirection::ToWidget, - )); - - self.tiles.remove(position); - - if let Some(pos_in_selection) = self - .selection - .iter() - .position(|selected| *selected == *tile) - { - self.selection.remove(pos_in_selection); - } - } + PaletteMessage::SetPage { source, page } => { + self.set_page(source.clone(), *page, ui) } + PaletteMessage::SelectAll => self.select_all(), + PaletteMessage::Delete => drop(self.delete_tiles(ui)), + PaletteMessage::MaterialColor(color) => self.material_color = *color, + PaletteMessage::SyncToState => self.sync_to_state(), _ => (), } } } else if let Some(WidgetMessage::KeyDown(key)) = message.data() { - if *key == KeyCode::Delete && !message.handled() { - let tiles = self - .tiles_to_brush(&self.selection, ui) - .tiles - .into_iter() - .map(|tile| tile.id) - .collect::>(); - - if !tiles.is_empty() { - ui.send_message(PaletteMessage::delete_tiles( - self.handle, - MessageDirection::FromWidget, - tiles, - )); - - message.set_handled(true); - } - }; - } else if let Some(WidgetMessage::Drop(widget)) = message.data() { - if message.destination() == self.handle - && message.direction() == MessageDirection::FromWidget - { - if let Some(tile_set_tile) = ui.try_get_of_type::(*widget) { - let local_cursor_position = self.point_to_local_space(ui.cursor_position()); - let grid_cursor_position = self.local_to_grid_pos(local_cursor_position); - - ui.send_message(PaletteMessage::insert_tile( - self.handle, - MessageDirection::FromWidget, - tile_set_tile.definition_handle, - grid_cursor_position, - )); - } + if *key == KeyCode::Delete && !message.handled() && self.delete_tiles(ui) { + message.set_handled(true); } } } @@ -490,19 +1164,41 @@ impl Control for PaletteWidget { pub struct PaletteWidgetBuilder { widget_builder: WidgetBuilder, - tiles: Vec>, + tile_resource: TileResource, + sender: MessageSender, + state: TileDrawStateRef, + kind: TilePaletteStage, + editable: bool, } impl PaletteWidgetBuilder { - pub fn new(widget_builder: WidgetBuilder) -> Self { + pub fn new( + widget_builder: WidgetBuilder, + sender: MessageSender, + state: TileDrawStateRef, + ) -> Self { Self { widget_builder, - tiles: Default::default(), + tile_resource: TileResource::Empty, + sender, + state, + kind: TilePaletteStage::default(), + editable: false, } } - pub fn with_tiles(mut self, tiles: Vec>) -> Self { - self.tiles = tiles; + pub fn with_resource(mut self, tile_resource: TileResource) -> Self { + self.tile_resource = tile_resource; + self + } + + pub fn with_kind(mut self, kind: TilePaletteStage) -> Self { + self.kind = kind; + self + } + + pub fn with_editable(mut self, editable: bool) -> Self { + self.editable = editable; self } @@ -512,14 +1208,29 @@ impl PaletteWidgetBuilder { .widget_builder .with_allow_drop(true) .with_clip_to_bounds(false) - .with_children(self.tiles.iter().cloned()) .build(), - tiles: self.tiles, + sender: self.sender, + state: self.state, + overlay: PaletteOverlay::default(), + content: self.tile_resource, + kind: self.kind, + editable: self.editable, + material_color: DEFAULT_MATERIAL_COLOR, + page: None, + cursor_position: None, + slice_position: Vector2::default(), + slice_mode: true, + position_text: FormattedTextBuilder::new(ctx.inner().default_font.clone()) + .with_brush(Brush::Solid(Color::WHITE)) + .build(), + selecting_tiles: FxHashSet::default(), + highlight: FxHashMap::default(), + update: TransTilesUpdate::default(), + tile_set_update: TileSetUpdate::default(), view_position: Default::default(), zoom: 1.0, tile_size: Vector2::repeat(32.0), - selection: Default::default(), - mode: Mode::None, + mode: MouseMode::None, })) } } @@ -533,118 +1244,46 @@ impl TileViewMessage { define_constructor!(TileViewMessage:LocalPosition => fn local_position(Vector2), layout: false); } -#[derive(Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)] -#[type_uuid(id = "c8ff0080-fb29-480a-8a88-59ee4c58d60d")] -pub struct BrushTileView { - widget: Widget, - #[component(include)] - selectable: Selectable, - definition_handle: TileDefinitionHandle, - local_position: Vector2, - tile_set: TileSetResource, -} - -define_widget_deref!(BrushTileView); - -impl Control for BrushTileView { - fn draw(&self, drawing_context: &mut DrawingContext) { - let tile_set = self.tile_set.data_ref(); - if let Some(tile_definition) = tile_set.tiles.try_borrow(self.definition_handle) { - if let Some(texture) = tile_definition - .material - .data_ref() - .texture("diffuseTexture") - { +fn draw_tile( + position: Rect, + clip_bounds: Rect, + tile: &TileRenderData, + drawing_context: &mut DrawingContext, +) { + let color = tile.color; + if let Some(material_bounds) = &tile.material_bounds { + if let Some(texture) = material_bounds + .material + .state() + .data() + .and_then(|m| m.texture("diffuseTexture")) + { + let kind = texture.data_ref().kind(); + if let TextureKind::Rectangle { width, height } = kind { + let size = Vector2::new(width, height); + let bounds = &material_bounds.bounds; drawing_context.push_rect_filled( - &self.bounding_rect(), + &position, Some(&[ - Vector2::new( - tile_definition.uv_rect.position.x, - tile_definition.uv_rect.position.y, - ), - Vector2::new( - tile_definition.uv_rect.position.x + tile_definition.uv_rect.size.x, - tile_definition.uv_rect.position.y, - ), - Vector2::new( - tile_definition.uv_rect.position.x + tile_definition.uv_rect.size.x, - tile_definition.uv_rect.position.y + tile_definition.uv_rect.size.y, - ), - Vector2::new( - tile_definition.uv_rect.position.x, - tile_definition.uv_rect.position.y + tile_definition.uv_rect.size.y, - ), + bounds.left_bottom_uv(size), + bounds.right_bottom_uv(size), + bounds.right_top_uv(size), + bounds.left_top_uv(size), ]), ); drawing_context.commit( - self.clip_bounds(), - Brush::Solid(Color::WHITE), + clip_bounds, + Brush::Solid(color), CommandTexture::Texture(texture.into()), None, ); } + } else { + drawing_context.push_rect_filled(&position, None); + drawing_context.commit(clip_bounds, Brush::Solid(color), CommandTexture::None, None); } - - if self.selectable.selected { - drawing_context.push_rect(&self.bounding_rect(), 1.0); - drawing_context.commit( - self.clip_bounds(), - (*self.foreground).clone(), - CommandTexture::None, - None, - ); - } - } - - fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) { - self.widget.handle_routed_message(ui, message); - self.selectable - .handle_routed_message(self.handle, ui, message); - if let Some(TileViewMessage::LocalPosition(position)) = message.data() { - if message.destination() == self.handle - && message.direction() == MessageDirection::ToWidget - { - self.local_position = *position; - self.invalidate_layout(); - } - } - } -} - -pub struct BrushTileViewBuilder { - widget_builder: WidgetBuilder, - definition_id: TileDefinitionHandle, - local_position: Vector2, - tile_set: TileSetResource, -} - -impl BrushTileViewBuilder { - pub fn new(tile_set: TileSetResource, widget_builder: WidgetBuilder) -> Self { - Self { - widget_builder, - definition_id: Default::default(), - local_position: Default::default(), - tile_set, - } - } - - pub fn with_position(mut self, position: Vector2) -> Self { - self.local_position = position; - self - } - - pub fn with_definition_id(mut self, id: TileDefinitionHandle) -> Self { - self.definition_id = id; - self - } - - pub fn build(self, ctx: &mut BuildContext) -> Handle { - ctx.add_node(UiNode::new(BrushTileView { - widget: self.widget_builder.build(), - selectable: Default::default(), - definition_handle: self.definition_id, - local_position: self.local_position, - tile_set: self.tile_set, - })) + } else { + drawing_context.push_rect_filled(&position, None); + drawing_context.commit(clip_bounds, Brush::Solid(color), CommandTexture::None, None); } } diff --git a/editor/src/plugins/tilemap/panel.rs b/editor/src/plugins/tilemap/panel.rs index 21f60b801..3f7b1c7d3 100644 --- a/editor/src/plugins/tilemap/panel.rs +++ b/editor/src/plugins/tilemap/panel.rs @@ -18,13 +18,21 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +use std::{fmt::Display, sync::Arc}; + +use fyrox::{ + core::algebra::Vector2, + gui::{grid::SizeMode, stack_panel::StackPanelBuilder, text::TextBuilder, window::Window}, + scene::tilemap::{tileset::TileSet, TileResource, *}, +}; + use crate::{ asset::item::AssetItem, command::{Command, CommandGroup, SetPropertyCommand}, fyrox::{ asset::untyped::UntypedResource, core::color::Color, - core::{pool::Handle, TypeUuidProvider, Uuid}, + core::{parking_lot::Mutex, pool::Handle, TypeUuidProvider, Uuid}, fxhash::FxHashSet, graph::{BaseSceneGraph, SceneGraph, SceneGraphNode}, gui::{ @@ -46,37 +54,71 @@ use crate::{ }, scene::{ node::Node, - tilemap::{ - brush::{BrushTile, TileMapBrush}, - tileset::{TileSet, TileSetResource}, - TileMap, - }, + tilemap::{brush::TileMapBrush, tileset::TileSetResource, TileMap}, }, }, gui::make_dropdown_list_option, load_image, message::MessageSender, plugins::tilemap::{ - commands::{ - AddBrushTileCommand, MoveBrushTilesCommand, RemoveBrushTileCommand, - SetBrushTilesCommand, - }, - palette::{ - BrushTileViewBuilder, PaletteMessage, PaletteWidget, PaletteWidgetBuilder, - TileViewMessage, - }, + palette::{PaletteMessage, PaletteWidget, PaletteWidgetBuilder, TileViewMessage}, DrawingMode, TileMapInteractionMode, }, scene::{commands::GameSceneContext, container::EditorSceneEntry}, Message, }; +use super::*; + +#[derive(Clone)] +enum BrushEntry { + FromTileMap(TileResource), + FromOther(TileResource), +} + +impl BrushEntry { + #[inline] + fn resource(&self) -> &TileResource { + match self { + BrushEntry::FromTileMap(r) => r, + BrushEntry::FromOther(r) => r, + } + } +} + +impl Display for BrushEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BrushEntry::FromTileMap(r) => { + if let Some(p) = r.path() { + write!(f, "{} (from tile map)", p.to_string_lossy()) + } else { + write!(f, "Missing Path") + } + } + BrushEntry::FromOther(r) => { + if let Some(p) = r.path() { + p.to_string_lossy().fmt(f) + } else { + write!(f, "Missing Path") + } + } + } + } +} + pub struct TileMapPanel { + pub state: TileDrawStateRef, + pub brush: TileResource, pub window: Handle, - pub palette: Handle, + pub tile_set: Option, + brushes: Vec, + tile_set_name: Handle, + preview: Handle, + pages: Handle, + palette: Handle, active_brush_selector: Handle, - edit: Handle, - pub drawing_modes_panel: Handle, + drawing_modes_panel: Handle, draw_button: Handle, erase_button: Handle, flood_fill_button: Handle, @@ -84,109 +126,67 @@ pub struct TileMapPanel { rect_fill_button: Handle, nine_slice_button: Handle, line_button: Handle, + random_button: Handle, + left_button: Handle, + right_button: Handle, + flip_x_button: Handle, + flip_y_button: Handle, } -fn generate_tiles( - tile_set_resource: &TileSetResource, - tile_map_brush: &TileMapBrush, - ctx: &mut BuildContext, -) -> Vec> { - tile_map_brush - .tiles - .iter() - .map(|tile| { - BrushTileViewBuilder::new( - tile_set_resource.clone(), - WidgetBuilder::new().with_id(tile.id), - ) - .with_definition_id(tile.definition_handle) - .with_position(tile.local_position) - .build(ctx) - }) - .collect::>() -} - -fn make_brush_entries(tile_map: &TileMap, ctx: &mut BuildContext) -> Vec> { - tile_map - .brushes() - .iter() - .flatten() - .map(|brush| make_dropdown_list_option(ctx, &brush.kind().to_string())) - .collect::>() -} - -fn selected_brush_index(tile_map: &TileMap) -> Option { - tile_map - .brushes() - .iter() - .position(|brush| brush == &tile_map.active_brush()) -} - -fn make_drawing_mode_button( - ctx: &mut BuildContext, - width: f32, - height: f32, - image: Option, - tooltip: &str, - tab_index: Option, -) -> Handle { - ButtonBuilder::new( - WidgetBuilder::new() - .with_tab_index(tab_index) - .with_tooltip(make_simple_tooltip(ctx, tooltip)) - .with_margin(Thickness::uniform(1.0)), - ) - .with_back( - DecoratorBuilder::new( - BorderBuilder::new(WidgetBuilder::new().with_foreground(BRUSH_DARKER)) - .with_pad_by_corner_radius(false) - .with_corner_radius(4.0) - .with_stroke_thickness(Thickness::uniform(1.0)), +impl TileMapPanel { + pub fn new(ctx: &mut BuildContext, state: TileDrawStateRef, sender: MessageSender) -> Self { + let tile_set_name = TextBuilder::new(WidgetBuilder::new().on_row(0)).build(ctx); + let active_brush_selector = DropdownListBuilder::new( + WidgetBuilder::new() + .on_row(1) + .with_min_size(Vector2::new(250.0, 20.0)) + .with_height(20.0), + ) + .build(ctx); + let preview = PanelPreviewBuilder::new( + WidgetBuilder::new() + .with_margin(Thickness::uniform(1.0)) + .with_min_size(Vector2::new(80.0, 100.0)), + state.clone(), + ) + .build(ctx); + let pages = PaletteWidgetBuilder::new( + WidgetBuilder::new().with_margin(Thickness::uniform(1.0)), + sender.clone(), + state.clone(), + ) + .with_kind(TilePaletteStage::Pages) + .build(ctx); + let palette = PaletteWidgetBuilder::new( + WidgetBuilder::new().with_margin(Thickness::uniform(1.0)), + sender.clone(), + state.clone(), ) - .with_selected_brush(BRUSH_BRIGHT_BLUE) - .with_normal_brush(BRUSH_LIGHT) - .with_hover_brush(BRUSH_LIGHTER) - .with_pressed_brush(BRUSH_LIGHTEST) - .build(ctx), - ) - .with_content( - ImageBuilder::new( + .with_kind(TilePaletteStage::Tiles) + .build(ctx); + let preview_frame = BorderBuilder::new( WidgetBuilder::new() - .with_background(Brush::Solid(Color::opaque(180, 180, 180))) + .on_column(1) + .with_foreground(Brush::Solid(Color::BLACK)) + .with_child(preview), + ) + .build(ctx); + let pages_frame = BorderBuilder::new( + WidgetBuilder::new() + .on_row(3) .with_margin(Thickness::uniform(2.0)) - .with_width(width) - .with_height(height), + .with_foreground(Brush::Solid(Color::BLACK)) + .with_child(pages), ) - .with_opt_texture(image) - .build(ctx), - ) - .build(ctx) -} - -impl TileMapPanel { - pub fn new(ctx: &mut BuildContext, scene_frame: Handle, tile_map: &TileMap) -> Self { - let tiles = tile_map - .tile_set() - .and_then(|tile_set| { - tile_map - .active_brush() - .map(|brush| generate_tiles(tile_set, &brush.data_ref(), ctx)) - }) - .unwrap_or_default(); - - let palette = PaletteWidgetBuilder::new(WidgetBuilder::new()) - .with_tiles(tiles) - .build(ctx); - - let active_brush_selector = - DropdownListBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(20.0)) - .with_opt_selected(selected_brush_index(tile_map)) - .with_items(make_brush_entries(tile_map, ctx)) - .build(ctx); - - let edit = ButtonBuilder::new(WidgetBuilder::new().with_width(45.0).with_height(26.0)) - .with_text("Edit") - .build(ctx); + .build(ctx); + let palette_frame = BorderBuilder::new( + WidgetBuilder::new() + .on_row(4) + .with_margin(Thickness::uniform(2.0)) + .with_foreground(Brush::Solid(Color::BLACK)) + .with_child(palette), + ) + .build(ctx); let width = 20.0; let height = 20.0; @@ -194,7 +194,7 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/brush.png")), + BRUSH_IMAGE.clone(), "Draw with active brush.", Some(0), ); @@ -202,7 +202,7 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/eraser.png")), + ERASER_IMAGE.clone(), "Erase with active brush.", Some(1), ); @@ -210,15 +210,15 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/fill.png")), - "Flood fill with random tiles from current brush.", + FILL_IMAGE.clone(), + "Flood fill with tiles from current brush.", Some(2), ); let pick_button = make_drawing_mode_button( ctx, width, height, - load_image(include_bytes!("../../../resources/pipette.png")), + PICK_IMAGE.clone(), "Pick tiles for drawing from the tile map.", Some(3), ); @@ -226,7 +226,7 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/rect_fill.png")), + RECT_FILL_IMAGE.clone(), "Fill the rectangle using the current brush.", Some(4), ); @@ -234,7 +234,7 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/nine_slice.png")), + NINE_SLICE_IMAGE.clone(), "Draw rectangles with fixed corners, but stretchable sides.", Some(5), ); @@ -242,14 +242,53 @@ impl TileMapPanel { ctx, width, height, - load_image(include_bytes!("../../../resources/line.png")), - "Draw a line using random tiles from the given brush.", + LINE_IMAGE.clone(), + "Draw a line using tiles from the given brush.", Some(6), ); + let left_button = make_drawing_mode_button( + ctx, + width, + height, + TURN_LEFT_IMAGE.clone(), + "Rotate left 90 degrees.", + Some(7), + ); + let right_button = make_drawing_mode_button( + ctx, + width, + height, + TURN_RIGHT_IMAGE.clone(), + "Rotate right 90 degrees.", + Some(8), + ); + let flip_x_button = make_drawing_mode_button( + ctx, + width, + height, + FLIP_X_IMAGE.clone(), + "Flip along x axis.", + Some(9), + ); + let flip_y_button = make_drawing_mode_button( + ctx, + width, + height, + FLIP_Y_IMAGE.clone(), + "Flip along y axis.", + Some(10), + ); + let random_button = make_drawing_mode_button( + ctx, + width, + height, + RANDOM_IMAGE.clone(), + "Toggle random fill mode.", + Some(11), + ); let drawing_modes_panel = WrapPanelBuilder::new( WidgetBuilder::new() - .with_enabled(false) .with_child(draw_button) .with_child(erase_button) .with_child(flood_fill_button) @@ -260,49 +299,69 @@ impl TileMapPanel { ) .with_orientation(Orientation::Horizontal) .build(ctx); + let modifiers_panel = WrapPanelBuilder::new( + WidgetBuilder::new() + .with_child(left_button) + .with_child(right_button) + .with_child(flip_x_button) + .with_child(flip_y_button) + .with_child(random_button), + ) + .with_orientation(Orientation::Horizontal) + .build(ctx); - let toolbar = WrapPanelBuilder::new( + let toolbar = StackPanelBuilder::new( WidgetBuilder::new() - .on_row(0) - .with_child(edit) .with_child(drawing_modes_panel) + .with_child(modifiers_panel) .with_child(active_brush_selector), ) - .with_orientation(Orientation::Horizontal) .build(ctx); - let content = GridBuilder::new(WidgetBuilder::new().with_child(toolbar).with_child( - BorderBuilder::new(WidgetBuilder::new().on_row(1).with_child(palette)).build(ctx), - )) + let header = GridBuilder::new( + WidgetBuilder::new() + .on_row(2) + .with_child(toolbar) + .with_child(preview_frame), + ) + .add_row(Row::auto()) + .add_column(Column::stretch()) + .add_column(Column::stretch()) + .build(ctx); + + let content = GridBuilder::new( + WidgetBuilder::new() + .with_child(tile_set_name) + .with_child(active_brush_selector) + .with_child(header) + .with_child(pages_frame) + .with_child(palette_frame), + ) + .add_row(Row::auto()) + .add_row(Row::auto()) .add_row(Row::auto()) .add_row(Row::stretch()) + .add_row(Row::generic(SizeMode::Stretch, 200.0)) .add_column(Column::stretch()) .build(ctx); - let window = WindowBuilder::new(WidgetBuilder::new().with_width(250.0).with_height(400.0)) + let window = WindowBuilder::new(WidgetBuilder::new().with_width(400.0).with_height(600.0)) .open(false) .with_title(WindowTitle::text("Tile Map Control Panel")) .with_content(content) .build(ctx); - ctx.sender() - .send(WindowMessage::open_and_align( - window, - MessageDirection::ToWidget, - scene_frame, - HorizontalAlignment::Right, - VerticalAlignment::Top, - Thickness::uniform(2.0), - false, - true, - )) - .unwrap(); - Self { + state, + tile_set: None, + brush: TileResource::Empty, + brushes: Vec::new(), window, + tile_set_name, + preview, + pages, palette, active_brush_selector, - edit, drawing_modes_panel, draw_button, erase_button, @@ -311,9 +370,62 @@ impl TileMapPanel { rect_fill_button, nine_slice_button, line_button, + left_button, + right_button, + flip_x_button, + flip_y_button, + random_button, + } + } + + pub fn align(&self, relative_to: Handle, ui: &UserInterface) { + if ui.node(self.window).visibility() { + ui.send_message(WidgetMessage::align( + self.window, + MessageDirection::ToWidget, + relative_to, + HorizontalAlignment::Right, + VerticalAlignment::Top, + Thickness::uniform(2.0), + )); + ui.send_message(WidgetMessage::topmost( + self.window, + MessageDirection::ToWidget, + )); + ui.send_message(WidgetMessage::focus( + ui.node(self.window).cast::().unwrap().content, + MessageDirection::ToWidget, + )); + } else { + ui.send_message(WindowMessage::open_and_align( + self.window, + MessageDirection::ToWidget, + relative_to, + HorizontalAlignment::Right, + VerticalAlignment::Top, + Thickness::uniform(2.0), + false, + true, + )); } } + pub fn to_top(&self, ui: &UserInterface) { + ui.send_message(WidgetMessage::topmost( + self.window, + MessageDirection::ToWidget, + )); + ui.send_message(WidgetMessage::focus( + ui.node(self.window).cast::().unwrap().content, + MessageDirection::ToWidget, + )); + ui.send_message(WidgetMessage::visibility( + self.window, + MessageDirection::ToWidget, + true, + )); + } + pub fn destroy(self, ui: &UserInterface) { ui.send_message(WidgetMessage::remove( self.window, @@ -321,10 +433,140 @@ impl TileMapPanel { )); } + pub fn add_brush(&mut self, brush: TileResource, _ui: &mut UserInterface) { + self.brushes.push(BrushEntry::FromOther(brush)); + } + + pub fn set_tile_map(&mut self, tile_map: &TileMap, ui: &mut UserInterface) { + // TODO + } + + pub fn set_resource(&mut self, resource: TileResource, ui: &mut UserInterface) { + self.brush = resource; + ui.send_message(PaletteMessage::set_page( + self.pages, + MessageDirection::ToWidget, + self.brush.clone(), + None, + )); + ui.send_message(PaletteMessage::set_page( + self.palette, + MessageDirection::ToWidget, + self.brush.clone(), + None, + )); + } + + pub fn set_focus(&mut self, handle: TileDefinitionHandle, ui: &mut UserInterface) { + let mut state = self.state.lock_mut(); + state.selection.source = SelectionSource::Widget(self.palette); + let tiles = state.selection_tiles_mut(); + tiles.clear(); + if let Some(handle) = + self.brush + .get_tile_handle(TilePaletteStage::Tiles, handle.page(), handle.tile()) + { + tiles.insert(handle.tile(), handle); + } + ui.send_message(PaletteMessage::set_page( + self.pages, + MessageDirection::ToWidget, + self.brush.clone(), + Some(handle.page()), + )); + ui.send_message(PaletteMessage::set_page( + self.palette, + MessageDirection::ToWidget, + self.brush.clone(), + Some(handle.page()), + )); + ui.send_message(PaletteMessage::center( + self.pages, + MessageDirection::ToWidget, + handle.page(), + )); + ui.send_message(PaletteMessage::center( + self.palette, + MessageDirection::ToWidget, + handle.tile(), + )); + } + + pub fn set_visibility(&self, visible: bool, ui: &mut UserInterface) { + ui.send_message(WidgetMessage::visibility( + self.window, + MessageDirection::ToWidget, + visible, + )); + } + + fn make_brush_entries(&self, ctx: &mut BuildContext) -> Vec> { + let Some(tile_set) = &self.tile_set else { + return Vec::default(); + }; + let make = make_dropdown_list_option; + let mut tile_set_name: String = "tile set: ".into(); + tile_set_name.push_str(tile_set.kind().to_string().as_str()); + std::iter::once(make(ctx, tile_set_name.as_str())) + .chain( + self.brushes + .iter() + .map(|brush| make(ctx, &brush.to_string())), + ) + .collect() + } + + fn find_brush_index(&self, brush: &TileResource) -> Option { + self.brushes + .iter() + .position(|b| b.resource() == brush) + .map(|i| i + 1) + } + + fn get_brush_at_index(&self, index: usize) -> Option { + if index == 0 { + Some(TileResource::TileSet(self.tile_set.clone()?)) + } else { + self.brushes + .get(index - 1) + .map(BrushEntry::resource) + .cloned() + } + } + + fn handle_button(&mut self, button: Handle, ui: &mut UserInterface) { + if button == self.draw_button { + self.state.lock_mut().drawing_mode = DrawingMode::Draw; + } else if button == self.erase_button { + self.state.lock_mut().drawing_mode = DrawingMode::Erase; + } else if button == self.flood_fill_button { + self.state.lock_mut().drawing_mode = DrawingMode::FloodFill; + } else if button == self.pick_button { + self.state.lock_mut().drawing_mode = DrawingMode::Pick; + } else if button == self.rect_fill_button { + self.state.lock_mut().drawing_mode = DrawingMode::RectFill; + } else if button == self.nine_slice_button { + self.state.lock_mut().drawing_mode = DrawingMode::NineSlice; + } else if button == self.line_button { + self.state.lock_mut().drawing_mode = DrawingMode::Line; + } else if button == self.random_button { + let mut state = self.state.lock_mut(); + state.random_mode = !state.random_mode; + } else if button == self.left_button { + self.state.lock_mut().stamp.rotate(1); + } else if button == self.right_button { + self.state.lock_mut().stamp.rotate(-1); + } else if button == self.flip_x_button { + self.state.lock_mut().stamp.x_flip(); + } else if button == self.flip_y_button { + self.state.lock_mut().stamp.y_flip(); + } + } + pub fn handle_ui_message( - self, + mut self, message: &UiMessage, - ui: &UserInterface, + ui: &mut UserInterface, tile_map_handle: Handle, tile_map: Option<&TileMap>, sender: &MessageSender, @@ -335,96 +577,33 @@ impl TileMapPanel { self.destroy(ui); return None; } - } else if let Some(WidgetMessage::Drop(dropped)) = message.data() { - if let Some(tile_map) = tile_map { - if message.destination() == self.palette { - if let Some(item) = ui.node(*dropped).cast::() { - if let Some(tile_set) = item.resource::() { - if let Some(active_brush) = tile_map.active_brush().as_ref() { - let tile_set = tile_set.data_ref(); - let tiles = tile_set - .tiles - .pair_iter() - .map(|(tile_handle, tile)| BrushTile { - definition_handle: tile_handle, - local_position: tile.position, - id: Uuid::new_v4(), - }) - .collect::>(); - - sender.do_command(SetBrushTilesCommand { - brush: active_brush.clone(), - tiles, - }); - } - } - } - } + } + if let Some(ButtonMessage::Click) = message.data() { + self.handle_button(message.destination(), ui); + } else if let Some(PaletteMessage::SetPage { .. }) = message.data() { + if message.destination() == self.pages + && message.direction() == MessageDirection::FromWidget + { + ui.send_message( + message + .clone() + .with_destination(self.palette) + .with_direction(MessageDirection::ToWidget), + ); } - } else if let Some(msg) = message.data() { - if let Some(tile_map) = tile_map { - if let Some(active_brush_resource) = tile_map.active_brush().as_ref() { - if message.destination() == self.palette - && message.direction == MessageDirection::FromWidget - { - match msg { - PaletteMessage::MoveTiles(move_data) => { - let mut commands = vec![Command::new(MoveBrushTilesCommand { - brush: active_brush_resource.clone(), - positions: move_data.clone(), - })]; - - let mut tiles_to_remove = FxHashSet::default(); - let active_brush = active_brush_resource.data_ref(); - for (id, new_tile_position) in move_data.iter() { - if let Some(tile) = active_brush.find_tile(id) { - for other_tile in active_brush.tiles.iter() { - if !std::ptr::eq(tile, other_tile) - && other_tile.local_position == *new_tile_position - { - tiles_to_remove.insert(other_tile.id); - } - } - } - } - - for tile_to_remove in tiles_to_remove { - commands.push(Command::new(RemoveBrushTileCommand { - brush: active_brush_resource.clone(), - id: tile_to_remove, - tile: None, - })); - } - - sender.do_command(CommandGroup::from(commands)); - } - PaletteMessage::DeleteTiles(ids) => { - sender.do_command(CommandGroup::from( - ids.iter() - .map(|id| { - Command::new(RemoveBrushTileCommand { - brush: active_brush_resource.clone(), - id: *id, - tile: None, - }) - }) - .collect::>(), - )) - } - PaletteMessage::InsertTile { - definition_id, - position, - } => sender.do_command(AddBrushTileCommand { - brush: active_brush_resource.clone(), - tile: Some(BrushTile { - definition_handle: *definition_id, - local_position: *position, - id: Uuid::new_v4(), - }), - }), - _ => (), - } + } else if let Some(WidgetMessage::Drop(dropped)) = message.data() { + if ui.is_node_child_of(message.destination(), self.window) { + let tile_res = if let Some(item) = ui.node(*dropped).cast::() { + if let Some(brush) = item.resource::() { + Some(TileResource::Brush(brush)) + } else { + item.resource::().map(TileResource::TileSet) } + } else { + None + }; + if let Some(tile_res) = tile_res { + self.add_brush(tile_res, ui); } } } else if let Some(DropdownListMessage::SelectionChanged(Some(index))) = message.data() { @@ -446,178 +625,95 @@ impl TileMapPanel { } } } - } else if let Some(ButtonMessage::Click) = message.data() { - if let Some(interaction_mode) = editor_scene.and_then(|entry| { - entry - .interaction_modes - .of_type_mut::() - }) { - if message.destination() == self.draw_button { - interaction_mode.drawing_mode = DrawingMode::Draw; - } else if message.destination() == self.erase_button { - interaction_mode.drawing_mode = DrawingMode::Erase; - } else if message.destination() == self.flood_fill_button { - interaction_mode.drawing_mode = DrawingMode::FloodFill; - } else if message.destination() == self.rect_fill_button { - interaction_mode.drawing_mode = DrawingMode::RectFill { - click_grid_position: Default::default(), - }; - } else if message.destination() == self.pick_button { - interaction_mode.drawing_mode = DrawingMode::Pick { - click_grid_position: Default::default(), - }; - } else if message.destination() == self.nine_slice_button { - interaction_mode.drawing_mode = DrawingMode::NineSlice { - click_grid_position: Default::default(), - }; - } else if message.destination() == self.edit { - sender.send(Message::SetInteractionMode( - TileMapInteractionMode::type_uuid(), - )); - } else if message.destination() == self.line_button { - interaction_mode.drawing_mode = DrawingMode::Line { - click_grid_position: Default::default(), - }; - } - } } - Some(self) } - pub fn update(&self, ui: &UserInterface, editor_scene: Option<&EditorSceneEntry>) { - if let Some(interaction_mode) = editor_scene - .and_then(|entry| entry.interaction_modes.of_type::()) - { - fn highlight_tool_button(button: Handle, highlight: bool, ui: &UserInterface) { - let decorator = *ui.try_get_of_type::